0

I'm trying to switch based if a string can be found inside an array, Is there a better way? The error I'm getting is Type 'boolean' is not comparable to type 'string'.ts(2678)

  const determinLayout = (path: string) => {

     const adminPaths = ['/admin', '/finance'];
     const marketingPaths = ['/marketing', '/deals'];

     switch (path) {
        case (adminPaths.indexOf(path) > -1)
        return {
          layout: 'admin',
        };
      case (marketingPaths.indexOf(path) > -1):
        return {
          layout: 'marketing',
        };
      default:
        return {
          layout: 'visitor',
        };
    }
  };
Bill
  • 4,614
  • 13
  • 77
  • 132
  • `adminPaths.indexOf(path) > -1` returns a boolean and `path` is a string. The error is correct - you can't compare the two. A `switch(A) { case B: doSomething() }` is equivalent to `if(A == B) { doSomething() }`. I'm not sure you really want to use a `switch` here. – VLAZ Nov 23 '19 at 15:36
  • 1
    This tests if `path`(that you passed to switch()) is equal to `adminPaths.indexOf(path) > -1`. `if`and `else` are your friends. – JB Nizet Nov 23 '19 at 15:37
  • 1
    The issue is that you've misunderstood how the switch works, it works like so, you have some expression inside the switch (here it is 'path'), then you have cases, a case is used if it equals the expression inside the switch, so here you are asking if (adminPaths.indexOf(path) > -1) which is a boolean, is equal to path, which is a string, so that's the error made sense of. As for your question, well there's several ways to go, but I would suggest just having several if statements, like if(adminPaths.includes(path)) { return { layout: "admin" }; } and so on. – Countingstuff Nov 23 '19 at 15:38

2 Answers2

2

A switch statement is not the right choice here. From the docs:

The switch statement evaluates an expression, matching the expression's value to a case clause, and executes statements associated with that case, as well as statements in cases that follow the matching case.

You should rewrite your code using if statements, instead.

const determinLayout = (path: string) => {

  const adminPaths = ['/admin', '/finance'];
  const marketingPaths = ['/marketing', '/deals'];

  // try to find given path in admin paths
  if (adminPaths.indexOf(path) > -1) {
    return {
      layout: 'admin',
    };
  }

  // try to find given path in marketing paths
  if (marketingPaths.indexOf(path) > -1) {
    return {
      layout: 'marketing',
    };
  }

  // if not found, consider this a visitor path
  return {
    layout: 'visitor',
  };
};

Side note: you also have a typo in your method name, you should rename determinLayoutdetermineLayout.

Another side note: consider using the more expressive syntax adminPaths.includes(path) instead of indexOf, which is supported by modern browsers. More info in this stackoverflow answer.

szaman
  • 2,159
  • 1
  • 14
  • 30
  • is there a reason to use two `if's` and not use an `if` and an `else-if` then `else` – Bill Nov 23 '19 at 15:51
  • if there any benefit to checking if path was even passed like `if (path) {...` – Bill Nov 23 '19 at 15:55
  • @Bill not much benefit between `if` or `if/else` - more of a style choice. Since you have a `return` inside the `if` it doesn't matter whether you do `else`, since with a `return` you are not going to hit it. I personally prefer skipping the `else` since it keeps the code slightly cleaner but I know others prefer to keep it in place to be explicit that you are going to hit only one of these. – VLAZ Nov 23 '19 at 15:58
  • 1
    @Bill yes, you could first do something like `if(!path) { // return null or throw exception here }` which will save checking the individual arrays if path was passed as null for some reason. you could also use else-if and else, it would be exactly the same. i personally find this syntax cleaner and easier for adding comments in front of each section. – szaman Nov 23 '19 at 15:58
0

The issue is that you are not using the switch statement correctly. If you have the following:

switch(A) {
  case B: 
   return doOne();
  case C:
   return doTwo();
  default:
   doThree();
}

it can be represented by the following if/else chain:

if (A == B) {
  return doOne();
} else if (A == C) {
  return doTwo();
} else {
  return doThree();
}

So the argument you give to switch is always checked against the values given to each case. Since you have switch(path) then the expectation is that each of the case statements will hold a value you want to compare directly to path but you have an expression that returns a boolean, which is why the compiler stops you from comparing them.

You can do what you want if you invert the expectations of how switch is supposed to be used. Instead of giving switch a value and trying to match it with a case, you can start with switch(true) and give each case an expression that produces a boolean:

const determinLayout = (path: string) => {
    const adminPaths = ['/admin', '/finance'];
    const marketingPaths = ['/marketing', '/deals'];

    switch (true) {
      case (adminPaths.indexOf(path) > -1):
      return {
        layout: 'admin',
      };
    case (marketingPaths.indexOf(path) > -1):
      return {
        layout: 'marketing',
      };
    default:
      return {
        layout: 'visitor',
      };
  }
};

TypeScript Playground link

However, this can start becoming hard to maintain if you add more cases. It's also harder to read by other developers and it's harder to prove that you will match exactly one case or not. Overall, it's not recommended to use it but in rare occasions it is useful.

Instead of this, you can re-structure the code similar to multiple dispatch but in order to return exactly one thing:

//declare an interface for each configuration item
interface PathConfig {
  layout: string,
  matchingPaths: string[]
}

const determinLayout = (path: string) => {
  //have a collection of all
  const pathsConfig: PathConfig[] = [
    { layout: "admin", matchingPaths: ['/admin', '/finance'] },
    { layout: "marketing", matchingPaths: ['/marketing', '/deals'] }
  ];

  //attempt to find a configuration where `path` is matching
  const config = pathsConfig.find(({ matchingPaths }) => matchingPaths.indexOf(path) > -1);

  //if found return the corresponding layout
  if (config) {
    return {
      layout: config.layout
    };
  }

  //otherwise return a default
  return {
    layout: 'visitor',
  };
};

TypeScript Playground link

The good thing here is that now the algorithm for finding and returning an item is entirely divorced from the configuration. You can add or remove configuration items and you don't need to change how you try to retrieve them, whereas with a switch you need to add/remove a case every time. You can even externalise pathsConfig and load it from a file or elsewhere, which will enable you to modify the items there at runtime. You may not need it, but it's a benefit nonetheless.

The whole thing can be shortened using destructuring and default values:

//declare an interface for each configuration item
interface PathConfig {
  layout: string,
  matchingPaths: string[]
}
// assume these are loaded already
declare const pathsConfig: PathConfig[];
declare const defaultLayout: string;

//...

const determinLayout = (path: string) => {
  //attempt to find a configuration where `path` is matching
  //destructure to get `layout` or use the default
  const { layout = defaultLayout } = pathsConfig
    .find(({ matchingPaths }) => matchingPaths.indexOf(path) > -1) || {};

  //return the corresponding layout
  return { layout };
};

TypeScript Playground link

VLAZ
  • 26,331
  • 9
  • 49
  • 67