0

I'm trying to assign string type to "player" with TypeScript in React but it's not working simply with player: string as in Option 1.

Option 1:

Object.values(data.val()).forEach((player: string) => {
          newPlayers.push(player);
        });

The above way produces a ts err:

Argument of type '(player: string) => null' is not assignable to parameter of type '(value: unknown, index: number, array: unknown[]) => null'. Types of parameters 'player' and 'value' are incompatible. Type 'unknown' is not assignable to type 'string'.ts(2345)

but using the as-syntax, like the following, corrects it. Why is that?

Option 2:

DB_players.once('value', (data) => {
      
      // Fetch players who signed up
      const newPlayers: string[] = [];

      if (data.val() ) {
        Object.values(data.val()).forEach((player) => {
          newPlayers.push(player as string); 
        });

        setPlayers(newPlayers); // save the new player list
      }
    });
3
  • 1
    Doesnt make much sense. Why you are using map if the main purpose of the loop is pushing items to external array? Also keep in mind that player type is inherited, if only you have typed data.val(). Commented Oct 19, 2020 at 19:22
  • I'm using map to loop through an object (from Firebase) to take only the values and push them into newPlayers. And then use newPlayers to update the state. Commented Oct 19, 2020 at 19:42
  • 1
    If you aren't returning anything you should always use forEach Is there already any data in newPlayers before this loop? If not, then newPlayers should be the array that create from this map. But the main issue is that the type of data.val() is unknown[], so fix that. Commented Oct 19, 2020 at 19:52

2 Answers 2

2

The type of player in your map function is not something that you should need to assign. The type comes from the element type of the array which you are mapping.

Typescript thinks that the values of the object returned by data.val() are of type unknown. This is the error that you need to fix. Preferably, you should fix it higher up in the code where the data object is created by saying that data.val() returns a Record<string, string> or other type with string values. As a bandaid, you can tell typescript that you are using it as an array of strings by writing (Object.values(data.val()) as string[]) and your error will go away.

Unrelated to the typescript error, @kind user is right that you should not use map if you aren't returning anything. If newPlayers is an existing array that already has data in it which you are adding to then you can keep your push, but do it in a forEach loop instead. If this is where you are creating the newPlayers array and it has one entry for every player in data.val then you could create it from the return value of a map -- except that you aren't actually doing any mapping here, you're just returning the same value.

This one-liner does the same thing as your loop, which is append all the names in data.val() to an existing array. The ... means that they will be added as individual entries.

newPlayers.push( ...Object.values(data.val()) as string[] );

If you post more of the code -- where data comes from and where newPlayers is initialized then I can give you a better solution.

Edit:

For creating the array, you can just use the object values directly.

const newPlayers = Object.values(data.val()) as string[];

or

setPlayers( Object.values(data.val() ) as string[] );
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you Linda for your response. The newPlayers variable is already empty before the looping. I did not declare a type for data as ts didn't impose it so I thought it's something that has to do with Firebase. I'll update the post to show you the whole function.
Then I think all you need is const newPlayers: string[] = Object.values(data.val());
I'm not familiar with firebase specifically, but usually when you are fetching data you can use a generic to specify the data type, like const data = fetchData<MyInterface>( args ) so that data is a promise of type MyInterface
Yep, that fixed it. So I used const newPlayers: string[] = Object.values(data.val());and mapto return player.
Thank you, Linda
0

The problem is Object.values(data.val()) don't return an array of strings.

When you skip the type of player in your arrow function it is implicitely considered as any, which is compatible with the type you try to iterate over.

const newPlayers: string[] = [];
let toto = {a: "test", b: "hello"};
Object.values(toto).map((player: string) => {
    newPlayers.push(player);
});

Playground

3 Comments

This is true but the important question for @Badr to figure out is why doesn't Object.values return strings in their code. In your example the player: string is unnecessary because it does return strings based on the values of your object toto.
Without knowing what data or data.val() looks like, I can't know. It can be forced by using (Object.values(data.val()) as string[] for example but I think it would be better to fix the type of data.val() as a Record<somethingIDontKnow, string>
I saw your answer @LindaPaiste and voted it up, it's complete :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.