Search code examples
reactjsreact-contextreact-spring

React Spring Transition with React Context animates after component has already updated


I have a React component that should subscribe to a React Context and render some text. When the context updates the text should update, but when this update occurs I want to animate the component out with the old value and animate it in with the new value. I'm new to React Spring but figured that Transition in their Render-props API would do the trick here to fire animation events on mount and unmount respectively.

The From, Enter and Leave seem to work and the animations do play when the context changes, however it will change the contents of the context first, then render "Enter -> Leave" of the old element simultaneously as it renders "From -> Enter" on the new element, resulting in duplicated elements and incorrect text rendered when the "old element" is leaving.

My mind is telling me I need to make better use of lifetime-cycles to handle the duplicated elements and perhaps abstract the context value using props from the parent component rather than using the context value directly, but I could be dead wrong here.

So, my question is how do I get the Leave animation to play with the old text value and then play the Enter animation with the new text value and only have one element shown at a time?

Also, this is my first question here, so please let me know if I'm doing this wrong.

const TextComponent = () => {
  const contextData = useContext(Context);

  return (
    <Transition
      items={contextData.textToRender}
      from={{ opacity: 0, transform: 'translate3d(0%, -15%, 0)' }}
      enter={{ opacity: 1, transform: 'translate3d(0%, 0%, 0)' }}
      leave={{ opacity: 0, transform: 'translate3d(-15%, 0%, 0)' }}
    >
      {(condition) =>
        condition &&
        ((styles) => (
          <div style={styles}>
            <p className='station-text'>{contextData.textToRender}</p>
          </div>
        ))
      }
    </Transition>
  );
};

Solution

  • Bounced ideas solutions with a friend and we found a solution (mostly he found the solution to be honest).

    Firstly, the rendering of correct data was a simple oversight by me, so instead of using data directly from context, we actually use the data we pass to the transition API.

    <p className='station-text'>{text}</p>
    

    My friend then found a feature request for the "staggered rendering" that I was looking for. https://github.com/pmndrs/react-spring/issues/1064

    So we focused on finding a workaround and realized that we could send arrays to the various animation states in the API.

    enter={[{ display: 'none' }, { display: 'block', opacity: 1, transform: 'translate3d(0%, 0%, 0)' }]}
    

    This solves my use case and answers my question(s). Below is a code block that works well with both single items or array items, and whenever we update the context data the correct animation will be played and new components will be created with the new data, as intended.

    <Transition
          items={[contextData.textToRender.firstString, contextData.textToRender.secondString]}
          from={{ opacity: 0, transform: 'translate3d(0%, -15%, 0)' }}
          enter={[{ display: 'none' }, { display: 'block', opacity: 1, transform: 'translate3d(0%, 0%, 0)' }]}
          leave={[{ display: 'block', opacity: 0, transform: 'translate3d(-15%, 0%, 0)' }, { display: 'none' }]}
        >
          {(text) =>
            text &&
            ((styles) => (
              <div style={styles}>
                <p className='station-text'>{text}</p>
              </div>
            ))
          }
     </Transition>