Search code examples
arraysreactjsreact-nativereact-state-management

React-Native Updating state array


I have a state holding an array of objects (players) that I retrive from my API and then I'm rendering the users avatar on screen using the map function

   {players.map(player => {
      return (
        <TouchableOpacity
          key={player._id}
          style={selectedPlayers.inclues(player) ? styles.userAvatarContainerSelected : styles.userAvatarContainer}
          onPress={() => handlePlayerAvatarClick(player)}
        >
          <Image
            source={{
              uri: player.avatar_url
            }}
            style={styles.userAvatar}
          />
        </TouchableOpacity>
      );
    })}

My problem here is that when I tap the button I want to update a state of a state called selectedPlayers so I can update the style accordingly and then retrive the state back to the API. I'm inicializing the state as const [playersAvatars, setPlayersAvatars] = useState([]);

And the handlePlayerAvatarClick function is:

function handlePlayerAvatarClick(newPlayer) {

    if (newPlayer in selectedPlayers) {
      const arrayWithRemovedPlayer = selectedPlayers.filter(player => player._id.equals(newPlayer._id));

      setSelectedPlayers(arrayWithRemovedPlayer);
    }

    setSelectedPlayers(...selectedPlayers, newPlayer);
  }

I'm getting an error saying that selectedPlayers.includes() is not a function I don't get the problem, if I run Array.isArray(selectedPlayers) it returns true so, in theory, I could use the array functions like includes().

Any idea on what the issue is?

Thank you for your time!


Solution

  • You did not pass the correct value to setSelectedPlayers and should have done setSelectedPlayers([...selectedPlayers, newPlayer]).

    Here is an optimised version where I use the callback version of state setter setSomeState(oldvalue=>newValue:

    //using React.memo will make Player a pure
    //  component and won't re render if props didn't change
    const Player = React.memo(function Player({
      player,
      isSelected,
      handlePlayerAvatarClick,
    }) {
      const r = React.useRef(0);
      r.current++;
      return (
        <li
          onClick={handlePlayerAvatarClick(player)}
          style={{ cursor: 'pointer' }}
        >
          rendered:{r.current} times, name: {player.name}, is
          selected:
          {isSelected.toString()}
        </li>
      );
    });
    const players = [
      { id: 1, name: '1', completed: false },
      { id: 2, name: '2', completed: false },
    ];
    
    function App() {
      const [
        selectedPlayers,
        setSelectedPlayers,
      ] = React.useState([]);
      //use callback so the handler never changes
      const handlePlayerAvatarClick = React.useCallback(
        (player) => () =>
          setSelectedPlayers((selectedPlayers) =>
            selectedPlayers.includes(player)
              ? selectedPlayers.filter((p) => p !== player)
              : [...selectedPlayers, player]
          ),
        []
      );
      return (
        <ul>
          {players.map((player) => (
            <Player //player is pure and won't re render if nothing changed
              key={player.id}
              player={player}
              isSelected={selectedPlayers.includes(player)}
              handlePlayerAvatarClick={handlePlayerAvatarClick}
            />
          ))}
        </ul>
      );
    }
    ReactDOM.render(<App />, document.getElementById('root'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    
    
    <div id="root"></div>