Amin Jafarlou
Amin Jafarlou

Reputation: 511

TypeScript: How to make sure two props of a functional component, which are arrays, have the same length?

I'm trying to make a functional component in react which accepts two separate strings as props. Is there a way to make sure that these two arrays must have the same length, using TypeScript? In other words, when we are using this component inside other components, unless the props are the same length, we will receive an error.

To give you a simple example, imagine something like this:

export const TableView = ({ s1, s2 }: { s1: string[]; s2: string[] }) => {
  return (
    <table>
      <thead>
        <tr>
          {s1.map((s, index) => {
            return <th>{s1[index]}</th>;
          })}
        </tr>
      </thead>
      <tbody>
        <tr>
          {s2.map((s, index) => {
            return <td>{s2[index]}</td>;
          })}
        </tr>
      </tbody>
    </table>
  );
};

I want to make sure I see an error when I use it with strings having different length.

      <TableView s1={["1", "2", "3"]} s2={["1", "2", "3", "4"]} />
      {/* show an error here */}


Upvotes: 3

Views: 1177

Answers (3)

jcalz
jcalz

Reputation: 328788

In order to make the compiler keep track of the lengths of the s1 and s2 elements of the input parameter, you should make TableView generic in the type of s1, and then use that to check s2. The call signature for TableView should therefore look something like this:

<T extends string[]>({ s1, s2 }: {
    s1: [...T];
    s2: [...{ [I in keyof T]: string; }];
}) => JSX.Element

Here we are saying that T is some array type of strings, and that s1 is essentially of type T. The syntax [...T] uses variadic tuple type syntax to give the compiler a hint that we'd like it to interpet T as a tuple type and not just an array type. Tuple types have a known order and length, while plain array types do not. You care about the length, so you want a tuple type.

It would be nice if we could write [...T] for s2 also, but unfortunately for you, the compiler will tend to infer the elements of T as being string literal types like "1" and "2" instead of string. If so, saying that s2 is [...T] would be placing the restriction that each element of s2 would have to be exactly equal to the corresponding element of s1. We don't want to reject {s1: ["1", "2"], s2: ["2", "1"]}, so we can't do that.

So instead we say that s2 is a mapped tuple type with the same number of elements of s1 but where each value type can be a string.

That's the constraint you want.


Let's test it:

<TableView s1={["1", "2", "3"]} s2={["4", "3", "2", "1"]} /> // error!
// --------------------------➨ ~~
// Source has 4 element(s) but target allows only 3.

<TableView s1={["1", "2", "3"]} s2={["4", "3", "2"]} /> // okay

Looks good!

Playground link to code

Upvotes: 5

sasi k
sasi k

Reputation: 132

I think you can use something like below before return

if(sl.length !== s2.length) {
  throw "Arrays are of different length"; //which throws custom exception
 /*Or you can return anything that suits your needs*/
}
return(
  //here goes your code
)

Upvotes: 0

Jimmy Soussan
Jimmy Soussan

Reputation: 722

You can simply put the condition in your jsx like so :

export const TableView = ({ s1, s2 }: { s1: string[]; s2: string[] }) => {
  return (
  {s1.length === s2.length ?
    <table>
      <thead>
        <tr>
          {s1.map((s, index) => {
            return <th>{s1[index]}</th>;
          })}
        </tr>
      </thead>
      <tbody>
        <tr>
          {s2.map((s, index) => {
            return <td>{s2[index]}</td>;
          })}
        </tr>
      </tbody>
    </table>
   :
   <p> Error arrays are not the same length ...</p>
   }
  );
};

Upvotes: 0

Related Questions