Does the cell lifecycle re-run if `Success` props are updated?

Does the entire cell lifecycle get re-run if a prop on the Success state is updated?

I’ve got a cell the fetches a list of items from my API. The user can click to select items in that list. I’m tracking the selected state in the parent page/component, and passing that state down to the cell as a selectedItem prop, along with another prop that is the setSelectedItem function. I’m noticing that when the selectedItem changes it looks like the entire cell is re-rendering, including firing off the GraphQL query, showing the loading state, etc.

Is this intended/expected? Is there a way for me to update the props to my Success component without re-running the entire Cell lifecycle?

This would definitely cause a re-render. You could use something like React.memo() and explicitly tell it to not re-render: https://reactjs.org/docs/react-api.html#reactmemo

I understand why the Success component itself needs to re-render, but is there a way to prevent the entire Query/Loading/Success cycle in the Cell from re-running if I haven’t changed anything that would cause the GraphQL query to run? Maybe I’m missing something, but it seems like it could be useful to be able to have the query only run once (unless query variables change), and allow updating the props on the Success component to just re-render the Success component with the existing query results.

1 Like

That sounds interesting, I would be interested in hearing how you think that would work internally.

I just looked to see how Redwood processes Cell components, and it confirmed what I guessed - it looks like Cells get compiled into single components that encapsulate all of the Loading, Empty, Error, and Success states and run the GraphQL query on component render. Since it ends up as a single component, any props passed to the Cell that change will cause a re-render of the entire resulting Cell component, including the full lifecycle and the GraphQL query.

I also now more clearly understand why it has to be this way - all props passed to the Cell are consumed in many places, including the beforeQuery function and all 4 lifecycle states.

I can think of two ways to possibly accomplish what I want:

  1. If Cells accepted different props for each “piece” (e.g. queryProps, errorProps, successProps, etc), and used ContextProvider/useContext to consume only the expected props at each phase of the lifecycle, perhaps that could work.
  2. I could possibly do something like this myself by making a ContextProvider in the Cell’s parent component, then consume that context only within my Success component.

I’ll try the second approach and see if I can do it in a clean way.

The ContextProvider approach seems to work. I created a Context object with a value called successProps, and wrap my Cell in that. Then within my Success component I can call useContext and get those successProps out. When these props change, only the Success state component re-renders.

It doesn’t feel super elegant, but it works, and it allows me to lift the selected state up to the parent component so that the Cell can focus on its job of rendering the query data lifecycle.

If you folks have any thoughts on whether this is a reasonable approach, I’d love to hear them!

I like the idea of not doing the whole gql dance if it’s not needed :slight_smile:

I was wondering… Could babel look at the props used in QUERY and then generate two different rendering paths? If only props not used in QUERY are changed, then it’d choose a rendering path that doesn’t wrap everything in <Query>

Yup, Totally! I’m hoping that we can do this in the HOC - but we probably need to parse the query to “grab” the variables. Not a train smash, since we would very likely want to add diagnostics to the Cells.

2 Likes

Hey, it was great being able to find this old post to explain the behavior we are seeing!

Has any progress been made on this?

For anyone else finding this thread, I found a workaround. I filtered out the prop I wanted to passthrough in the beforeQuery, which keeps it from the refetching, but the props still makes it to the success component. Sample code below:

export const beforeQuery = (props) => {
  const { passThroughProp, ...queryProps } = props
  return {
    variables: queryProps,
    fetchPolicy: 'cache-and-network',
    nextFetchPolicy: 'cache-first',
  }
}

Thanks!

That’s seems like a great work around! Thanks for sharing that. No idea it existed. I’ve got a use case with layouts and set where I am hoping this will work.