1

I have a problem with react-spring useTransition animation when I update a property of my animated element.

The list of objects that I pass to my useTransition is:

const habits = [
  { id: "V3WV", name: "Go to gym", completions: [] },
  { id: "13t33", name: "Meditate", completions: [] },
  { id: "dghs4", name: "Practice guitar", completions: [] }
];

and my useTransition is:

const [habitsToShow, setHabitsToShow] = useState(habits);

const transitions = useTransition(habitsToShow, {
    from: {
      marginTop: -100,
      opacity: 0,
      transform: "translate3d(-150px,-40px,0)"
    },
    enter: {
      marginTop: 0,
      opacity: 1,
      transform: "translate3d(0px,0px,0)"
    },
    leave: {
      marginTop: 0,
      opacity: 0,
      transform: "translate3d(-150px,-40px,0)"
    },
    update: null
  });

My "enter" and "leave" animations work okay but when I try to add a new completion to the state object's "completions" array with:

const addCompletion = (habit) => {
    const indexOfHabit = habitsToShow.map((e) => e.id).indexOf(habit.id);

    // This doesn't mutate the state directly but causes re-render / "enter" of habit
    setHabitsToShow(
      habitsToShow.map((habit, i) =>
        i === indexOfHabit
          ? { ...habit, completions: [...habit.completions, 1] }
          : habit
      )
    );
  };

the object I'm trying to update gets replaced and the "enter" animation runs which is not the desired behaviour. The desired behaviour is that NO animation happens but the "completions"-counter gets updated in the element in the DOM. Does someone have a solution for this?

Here is a codesandbox to illustrate the behaviour: https://codesandbox.io/s/usetransition-react-spring-todo-jcikt

And here is the animated element:

{transitions((props, item, state, index) => {
        return (
          <animated.div
            key={item.name}
            style={{
              ...props,
              border: "1px solid black",
              width: "70%",
              marginTop: "5px",
              marginBottom: "5px"
            }}
            className="box"
          >
            <div key={item.name}>
              <h1>{item.name}</h1>
              <h3>{`Completed: ${item.completions.length} times`}</h3>
              <div>
                <button onClick={() => handleRemove(index)}>Remove</button>
                <button onClick={() => addCompletion(item)}>Completed?</button>
              </div>
            </div>
          </animated.div>
        );
      })}

1 Answers1

0

useTransition uses reference equality to differentiate between items in its input array. If you create a new object via spread syntax, then as far as it is concerned that is a new item.

You should use the keys property to indicate that the item is the same:

const transitions = useTransition(habitsToShow, {
  keys: habit => habit.id,
  ...
});

More information at the react-spring docs.

lawrence-witt
  • 8,094
  • 3
  • 13
  • 32