2

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?

jamesdlivesinatree
  • 1,016
  • 3
  • 11
  • 36

3 Answers3

1

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
  ));
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks for your input here and my apologies, that was a line of code leftover from trying similar to what you proposed. The reason why filtering by index does not work is because the index doesn't necessarily correspond to the token I want to remove. IE: If I have an array of 10 items, and I click the index 2, it will remove the 3rd element in the array. However, I can click the same token again, and it will again remove another token which has now taken place at the 3rd element in the array (index 2) – jamesdlivesinatree Jan 15 '23 at 20:25
  • I'm looking for a solution that allows me to correlate the token ids that I assign so that I may remove the exact token from the `tokenSprings` array.. right now, `tokenSprings` has no identifiers that I may use in `handleTokenClick`. can i do this somehow in my `useSprings` implementation? – jamesdlivesinatree Jan 15 '23 at 20:27
  • @jamesdlivesinatree I also thought there'd be an issue with using the index, but the `useSpring` demonstration code also uses the index and there didn't appear to be an issue. I didn't see any way to use anything *other than* the index, but that's not to say there isn't any other way to correlate an index to some other data. Would it be possible for you to create a *running* [codesandbox](https://codesandbox.io/) demo of your code that we could inspect live? – Drew Reese Jan 16 '23 at 18:03
  • yes, I'm going to try using `useSpringRef` first. If that doesn't work, I'll comment a codesandbox demo here. Thanks again for helping me out! – jamesdlivesinatree Jan 16 '23 at 19:51
  • yes, this is indeed the ideal way to use the `clicked` property implementation. thank you! – jamesdlivesinatree Jan 18 '23 at 21:01
0

Seem you have mistake, in line <div onClick={() => handleTokenClick(index)}> you passed index array to param of handleTokenClick , but when you define function function handleTokenClick(tokenId) you assigned name for param is tokenId instead index. I mean you was confused between tokenId and index

[Solve] So, you shoud modify define function handleTokenClick:

function handleTokenClick(index) {
    const cloneActiveTokens= activeTokens; // clone Active Token
    cloneActiveTokens.splice(index, 1); // remove 1 element from index, array will be mutated
    setActiveTokens(cloneActiveTokens);
}
Yui-Yukino
  • 26
  • 1
  • 3
  • I commented on the similar answer above. What you noticed was my error indeed, but from a prior implementation using indexes. I'd like to somehow reference the tokenId within the tokenSprings array. – jamesdlivesinatree Jan 15 '23 at 20:42
  • i read document of useSpring but cannot see how to pass data to useSprings, but you can trick it look like : `const tokenId = activeTokens[index].tokenId` – Yui-Yukino Jan 16 '23 at 08:13
0

I ended up solving this by adding a clicked attribute to my activeTokens, and then conditionally adding a display:none style if clicked === true

const handleTokenClick = (tokenIndex) {
    activeTokens[tokenIndex].clicked = true;
    setActiveTokens(activeTokens)
}
<animated.div className={`text-white text-2xl absolute cursor-pointer ${token.clicked === true ? "hidden" : ""}`} key={token.id} style={tokenSprings[index]}>
    <div onClick={() => handleTokenClick(index)}>
        <FontAwesomeIcon icon={faMoneyBill}></FontAwesomeIcon>
    </div>
</animated.div >
jamesdlivesinatree
  • 1,016
  • 3
  • 11
  • 36
  • 1
    Careful with this, this implementation directly mutates the `activeTokens` state. Would be better to use function update and map the `activeTokens` array to a new array and then also create a shallow copy of the specific token object *and then* set the `clicked` property. I can add an example to my answer. – Drew Reese Jan 18 '23 at 17:23