reactjsreact-hooksreact-spring

How to remove an element from useSprings array by id


I'm using react spring's useSprings hook. My implementation of this hook takes a variable number for the count, and then has an associated configuration.

My hook implementation looks like this:

    const [activeTokens, setActiveTokens] = useState([]);
    const [tokenSprings, setTokenSprings] = useSprings(activeTokens.length, (index) => {
            return {
                from: { transform: `translate3d(0px, 0px, 0)`},
                to: async (next) => {
                    var rotate = randomInt(800, 7200);
                    await next({
                        transform: `translate3d(1px, 1px, 0)`,
                        config: { mass: 0.4, tension: 5, friction: 3 },
                    })

                },
            }
        }
    });

I'm able to add tokens and render the springs as expected. The issue I'm running into is when I want to remove specific tokens from the DOM. The jsx looks as follows:

                    
tokenSprings.map((spring, index) => (
    <animated.div className="text-white text-2xl absolute cursor-pointer" key={index} style={spring}>
        <div onClick={() => handleTokenClick(index)}>
            <FontAwesomeIcon icon={faMoneyBill}></FontAwesomeIcon>
        </div>
    </animated.div >

My handleTokenClick function looks as follows:

function handleTokenClick(tokenId) {
    setActiveTokens(activeTokens => activeTokens.filter(token => token.id !== tokenId));
}

This obviously won't work, because I do not have any token.id accessible from the tokenSprings array. I scoured react spring docs, but I could not find a way to include an id that i'd specify in the tokenSprings array for each element, that i can later reference in the jsx.

Is this possible? If not, how is it best to reference the actual tokenSpring element which I want to remove from DOM?


Solution

  • There does appear to be a mixup in what the argument passed to the handleTokenClick callback handler represents.

    handleTokenClick is coded to take a token id for filtering purposes

    function handleTokenClick(tokenId) {
      setActiveTokens(activeTokens => activeTokens.filter(
        token => token.id !== tokenId
      ));
    }
    

    but is passed the mapped array index

    tokenSprings.map((spring, index) => (
      <animated.div
        className="text-white text-2xl absolute cursor-pointer"
        key={index}
        style={spring}
      >
        <div onClick={() => handleTokenClick(index)}> // <-- index is passed
          <FontAwesomeIcon icon={faMoneyBill} />
        </div>
      </animated.div>
    ))
    

    Unless any of the element objects in the activeTokens array happen to have id properties that match an array index, it's likely that the filter condition will always evaluate false and no element is removed from the array.

    To resolve you should refactor the handleTokenClick handler to take an index and filter by the index value.

    const handleTokenClick = (tokenIndex) {
      setActiveTokens(activeTokens => activeTokens.filter(
        (token, index) => index !== tokenIndex
      ));
    }
    

    Setting/Updating a clicked property.

    Even if you are only updating a nested property of a specific element you necessarily need to create shallow copies of all state, and nested state, that is being updated for React's reconciliation process. Map the previous activeTokens array to a new array, and then for the element object you want to update shallow copy it into a new object reference.

    const handleTokenClick = (tokenIndex) {
      setActiveTokens(activeTokens => activeTokens.map( // <-- new map reference
        (token, index) => index == tokenIndex
          ? {                // <-- new object reference
              ...token,      // <-- shallow copy properties
              clicked: true, // <-- updated property
            }
          : token
      ));
    }