In this recap, I’m going to demonstrate a top-down data flow approach to managing state with React. First I’ll go over a couple of approaches one might take and then refactor to make things nicer and less likely to have bugs.
Let’s say you want to add a button that, when clicked, will toggle your app between a light and dark style theme.
A familiar, but somewhat naïve approach
Let’s start with a very simple App structure as shown below.
- App
- Header
- Logo
- ThemeToggle
- Body
- Footer
- Header
As you can see, our ThemeToggle component is nested two levels deep, first within App and then inside of Header and as a sibling to Logo.
For our theme to work, we’ll create a CSS class on our App component that will control whether or not our app is in light or dark mode and we’ll take care of the rest with some styling.
- App.dark or App.light
- Header
- Logo
- ThemeToggle
- Body
- Footer
- Header
From here we can simply target the background and foreground colors.
$dark: #222; $light: #eee; .dark { background-color: $dark; color: $light; } .light { background-color: $light; color: $dark; }
Now let’s see how we can handle toggling between the two themes. Your first instinct might be to simply find the app element and add or remove a class based on what is currently present. Maybe something like this…
element.addEventListener('click', function() { let app = document.querySelector('.app'); if( app.classList.contains('dark') ) { app.classList.remove('dark'); app.classList.add('light'); } else { app.classList.remove('light'); app.classList.add('dark'); } });
Ouch! Or slightly easier to read with jQuery:
$(element).on('click', function() { var $app = $('.app'); // assumes app is initialized with one or the other $app.toggleClass('dark light'); });
Even with the more concise approach, you can now imagine how the complexity will grow exponentially with more and more instances of this pattern. This global app state is now controlled from within the nested toggle component.
This is a small example, but imagine dozens of these nested components interacting with different pieces of global state while potentially causing side effects on one another. The difficulty to manage all of this only increases, as I’m sure most front-end developers know.
A cleaner approach
Now let’s update some things. First let’s look at how we can pull the conditional logic out and make the component a pure function.
const ThemeToggle = ({handleThemeToggle}) => ( <div onClick={handleThemeToggle}></div> )
By making this a presentational component, we no longer have to deal with what happens to our app when this toggle is clicked! We just allow a click handler to be passed in as a prop, then allow that handler to take care of whatever it wants to do from within its own usage.
In other words, whatever is using this component likely has more information on how it should be configured and our ThemeToggle component doesn’t care what that is, so it can be reusable. It just maps to whatever is passed in. Since the container component can access state, we’ll deal with this one level up.
Now we can convert our App class component to a functional component as well and leverage the useState hook to manage state. If you’re already familiar with Redux, you could look into useReducer. This top-down data flow approach is really based on the assumption that you’re learning Redux and wanting to move toward that approach to managing state.
const [theme, setTheme] = useState('dark'); const handleThemeToggle = () => { setTheme(theme === 'dark' ? 'light' : 'dark'); } <Header> <Logo /> <ThemeToggle handleThemeToggle={handleThemeToggle} /> </Header>
Now our app is much cleaner and less bug prone. All we do is read and write state. If/when state ever changes, the component will re-render. Everything works as before but now it is much easier to identify what is happening and where things are being controlled – from the top down.
A challenge for you is to update this app to be able to handle more than just two themes. For the full code and to see one way I was able to make this work, checkout the app from github.
You’ll notice some other optimizations going with this app. To name a few, there are a couple more hooks introduced, useEffect and useCallback. I’m also introducing a performance optimization with React.memo(). You could look into using a hook for this useMemo().
Take it slow and don’t feel like you have to know everything before you can get your feet wet. Performance optimizations aren’t always more efficient and might not be necessary but in this case, it is helping to avoid unnecessary re-renders. We can look into performance optimizations in more depth in another article.
Hopefully this has been a helpful introduction you can build upon. If you’d like to continue the discussion about working in React, feel free to post questions or your own tips in the comments below.