javascriptreactjsreduxreact-redux

Can I use a variable index in a useSelector call?


Suppose I have a long list of playerData in my redux store, and some players' data gets updated very often. Let's say I have a Player component and inside of it I want to access that player's specific data. To do so, I would need to access a specific index of the playerData array.

Selector functions are supposed to be pure functions, and my understanding is that pure functions cannot alter their return values based on outside data. Am I correct in believing that the following three examples are therefore invalid?

//A
const Player = ({ index }) => {
    const thisPlayerData = useSelector(state => state.playerDataArray[index]);
    return <div>{thisPlayerData.name}</div>
}
//B
const ActivePlayer = ({}) => {
    const [activePlayerIndex, setActivePlayerIndex] = useState(0);
    const activePlayerData = useSelector(state => state.playerDataArray[activePlayerIndex]);
    return <div>{activePlayerData.name}</div>
}
//C
const WinnerPlayer = ({}) => {
    const winningPlayerIndex = useSelector(state => state.winningPlayerIndex);
    const winnerPlayerData = useSelector(state => state.playerData[winningPlayerIndex]);
    return <div>{winnerPlayerData.name}</div>
}

If those three are invalid, the only way I can think to access a player's data is by selecting the entire playerData array and then getting a specific index afterwards.

// Using example B as a jumping off point...
const ActivePlayer = ({}) => {
    const [activePlayerIndex, setActivePlayerIndex] = useState(0);
    const allPlayerDatas = useSelector(state => state.playerDataArray);
    const activePlayerData = allPlayerData[activePlayerIndex];
    return <div>{activePlayerData.name}</div>
}

I would like to avoid this if possible, as that would (if I'm understanding correctly) cause the component to rerender every single time any playerData is changed, not just when the playerData it's trying to display is changed.

Ideally, I would like the component only to rerender when the index or the playerDataArray[index] changes. Is there a way to accomplish that?


Solution

  • Pure function means that calling the function with the same arguments, would always return the same result, and that doesn't have side effects. Calling a selector with the current state, and a prop doesn't make it impure - if the state and the prop are the same, the return value won't change.

    All your examples are valid. In the redux docs useSelecor() examples you have the "Using props via closure to determine what to extract" example:

    export const TodoListItem = (props) => {
      const todo = useSelector((state) => state.todos[props.id])
      return <div>{todo.text}</div>
    }
    

    In your scenarion both values actually come from the state, so you can generate a single selector. Creating 2 simple selectors, and combine them:

    const getWinningPlayerIndex = state => state.winningPlayerIndex;
    
    const getPlayerData = state => state.playerData;
    
    const getWinnerData = state => getPlayerData(state)[getWinningPlayerIndex(state)];
    
    
    const WinnerPlayer = () => {
        const winnerPlayerData = useSelector(getWinnerData);
        return <div>{winnerPlayerData.name}</div>
    }