Why you should stop using the “container / presentational” pattern in Redux
TL;DR:
The redux style guide has an important rule: “Connect More Components to Read Data from the Store”. This rule has some important performance aspects that > are covered with an example in this post.
The “Container/Presentational” Pattern used to be the best practice when using redux or any other store management package.
It was explained thoroughly by Dan Abramov and Michael Chan .
If we want to keep it short, a container
usually holds the data fetching logic and data access to store. On the other hand, a presentational
component is more generic and just present the data it gets as props
.
This pattern was common before React Hooks were introduced and probably every developer using React with classes has seen or used it.
But in the world of hooks, this approach can cause serious performance issues (we will cover those later on).
Moreover, the Redux best practices now suggests we should connect more components to read directly from the store (which is quite the opposite to this pattern).
“Prefer having more UI components subscribed to the Redux store and reading data at a more granular level. This typically leads to better UI performance, as fewer components will need to render when a given piece of state changes.” (The redux docs)
But, what’s the reason behind this rule?
Our example app
I’ve built a small app with a sidebar fetching all the Pokemons (with a Select that decides how many Pokemons to fetch), and a right box showing the selected Pokemon. Clicking on a Pokemon name at the left, will change the image and fetch the relevant data.
The UI looks like this:
In the first approach, I decided to use connect
on a container
, query all the data from the store and also mapDispatchToProps
so I’ll be able to dispatch some data fetching.
I passed the data to my components using props (the example uses styled-components but the actual styles were left behind):
export const PokemonsPage = ({ asyncGetAllPokemons, asyncGetPokemonDetails, setSelectedPokemon, pokemons, selectedPokemonId, selectedPokemon, }) => { return ( <PokemonsPageWrapper> <PokemonsSidebar asyncGetAllPokemons={asyncGetAllPokemons} pokemons={pokemons} setSelectedPokemon={setSelectedPokemon} /> <PokemonView selectedPokemonId={selectedPokemonId} asyncGetPokemonDetails={asyncGetPokemonDetails} selectedPokemon={selectedPokemon} /> </PokemonsPageWrapper> ); }; export default connect( ({ pokemons }) => ({ pokemons: pokemons.data, selectedPokemonId: pokemons.selectedPokemonId, selectedPokemon: pokemons.selectedPokemonData, }), { asyncGetAllPokemons, setSelectedPokemon, asyncGetPokemonDetails, } )(PokemonsPage);
My components just fetch the data and show it based on the props.
I opened the React profiler, recorded a click on a Pokemon name and saw the renders that it triggered.
Here’s the screenshot:
What do we see here?
When clicking on a Pokemon name on the left sidebar, we initiated two renders. In the first one we see the PokemonsSidebar
was rendered and it took 1ms. But, why was it even rendered? It didn’t change, nor did it’s props. The reason for that is that the parent component was rendered because of a change in the redux store, so all of it’s children should also render.
We can also see that the whole Render duration
was 13.6ms, that’s because inside the PokemonsSidebar
we have 150 li
‘s that rendered again for no reason because their parent re-rendered.
In the second approach, I refactored my code to useSelector
and useDispatch
. My presentational components will be connected directly to the store and dispatch the actions.
Here’s my component’s code:
export const PokemonView = () => { const selectedPokemonId = useSelector( ({ pokemons }) => pokemons.selectedPokemonId ); const selectedPokemon = useSelector( ({ pokemons }) => pokemons.selectedPokemonData ); const dispatch = useDispatch(); useEffect(() => { if (selectedPokemonId) { dispatch(asyncGetPokemonDetails(selectedPokemonId)); } }, [selectedPokemonId, dispatch]); return ( <PokemonCard> {selectedPokemon && ( <SmallImage src={`https://pokeres.bastionbot.org/images/pokemon/${selectedPokemon.id}.png`} alt={selectedPokemon.name} /> )} <div>{selectedPokemon?.name}</div> </PokemonCard> ); };
export const PokemonsSidebar = () => { const pokemons = useSelector(({ pokemons }) => pokemons.data); const [pokemonsNumber, setPokemonsNumber] = useState(10); const dispatch = useDispatch(); useEffect(() => { dispatch(asyncGetAllPokemons(pokemonsNumber)); }, [dispatch, pokemonsNumber]); return ( <SidebarWrapper> <PokemonsNumberDropdown setPokemonsNumber={setPokemonsNumber} pokemonsNumber={pokemonsNumber} /> <ul> {pokemons.map((pokemon) => ( <PokemonItem key={pokemon.name} onClick={() => { dispatch(setSelectedPokemon(pokemon)); }} > {pokemon.name} </PokemonItem> ))} </ul> </SidebarWrapper> ); };
Let’s see the profiler screenshot:
As we can see here, the PokemonsSidebar
didn’t re-render because it wasn’t subscribed to the selectedPokemon
data from the store, thus all of it’s children didn’t re-render. The Render duration
this time is only 0.9ms (compared to 13.6ms)!
These numbers may sound negligible but that’s just an example. My app isn’t complicated and there’s just one component that shouldn’t render (and it’s children). What if I had more components? and my components had a heavy render?
Why does it happen?
Since we’re using function components, whenever our parent component renders, the whole tree is marked for render . React then recurses over all children and tries to render them. If our function components aren’t wrapped with React.memo
they will automatically re-render even if their props or state didn’t change. We need to bear in mind that memoization also has some performance impact so wrapping all of our components with React.memo
isn’t always the best approach.
Disclaimer
As you can understand from the last section, this isn’t a Redux only issue, but rather a React behavior. The “container/presentational” pattern can still be a valid solution as long as the impact is taken into consideration.
It’s important to note that this isn’t just a useSelector
vs connect
issue. I’ve seen examples creating a container with lots of useSelector
s just aiming to mimic this “container” approach and I’ve also seen examples of connect
on many components.
Summary
In this post, we’ve covered an important rule that was somewhat hidden inside the whole Redux style guide. If you still haven’t read the style guide, I urge you to do it. It contains many rules that will make your work with Redux straightforward and clear.
I hope everyone’s feeling well and keeping themselves safe!
If you have any questions, I’m available on Twitter .
Feel free to ask or comment, I’d love to hear your feedback!
Thanks,
Matan.
Read More:
The repo containing this example
The Redux best practices
Presentational and Container Components by Dan Abramov
Container Components by Michael Chan