Frontend State Management
When building a project there are a number of options to handling data and state in the frontend. The entire internet operates on HTTP. HTTP stands for Hypertext Transfer Protocol. For those not familiar with IT and websites, just know that HTTP is stateless (meaning it cannot hold information) and that every website you look at is built from a series of requests and responses.
The user requests data (i.e. typing in a website URL and hitting enter), and then a server responds with the appropriate data. What is amazing is that every “request” for data is independent of the request beforehand. There is no record of what has been requested, and what has been served.
Imagine ordering food at a restaurant and the waiter cannot remember any of your previous requests, but they reliably fulfill your request every time. Pretty quickly you would think about how to optimize your requests. Maybe you want to bundle them together...but there’s a lot of other factors to consider. How much could a waiter carry back on a tray at one time? Would requesting too much at once mean that the kitchen would be overwhelmed, if the kitchen could handle the request would you have to wait an unreasonable amount of time? Just like restaurants have a system for handling customers, the internet has a system for handling clients. While this is not a perfect analogy, think of the customer as the ‘client’, the waiter as the API, the kitchen as the server, and the table where you are sitting in the browser. The browser makes requests possible for users, and for developers, there are quite a few options to handle state so that the servers can handle the load and the users have a good experience.
This blog is going to discuss two options- Redux and Context. Both have their pros and cons, but we will walk through some simple examples of when either option could be effective.
Context
In React 16.8, ‘hooks’ were introduced. This allowed for more flexible state management in React components without having to create classes. Previously, if a component in React had to update any state, a class component was required which included a ton of boilerplate code and according to React’s official documentation “classes confuse both people and machines”. Now we can update and manage state with hooks. Hooks rely on an API in the React library which allows for functional components to handle more complicated tasks and allows components to be more sharable and reusable.
Part of this update included ‘Context’. Context allows a developer to have access to data globally within their application. In previous versions of React if a button on a form needed to be displayed in multiple languages it could be tedious to make that happen. Most likely, it was a different button that allowed the user to change the language. Then that language-changing button needed to update the state at the page level (through a series of magical `componentDidUpdate` calls, and then once it is updated at the page level, that piece of information (the user’s preferred language) is passed down from component to component (sometimes needing to be passed up to a dozen times) in a process affectionally known to developers as ‘prop-drilling.’
With the introduction of hooks, that button on the form can now tap into the context of the application and check which language the user prefers and display the text in that language. This process not only makes development easier, but is also optimized for performance.
Pros:
- Lightweight. This is already built into newer versions of React so it is great that you do not have to install any additional packages to be up and running.
- Documentation. The React Docs provide easy-to-follow examples of how to use hooks and context in your application, and there are plenty of online tutorials and videos of people walking through the concepts.
- Flexible. You can set all sorts of data types into Context and you are off and running.
Cons:
- Performance. If used correctly, this should not be an issue, however, Context should be used in addition to local state in React components, not a complete replacement. A change to Context will re-render all other child components that are affected by that change. So for a language, or a theme, or something like that it is not a big deal because you want it to re-render. But if a user is simply inputting their name into a form and the value of the form is held in Context, every single key stroke will trigger a re-render of all components. This may be invisible to the user, but it can easily cause lagging and performance issues.
Example
Here’s a simple example of using context to keep the theme of your website stored globally, but also having a way to update the theme from any component. This assumes that you have only two themes (light and dark)
import React, { useContext, useState } from 'react'
const ThemeContext = React.createContext(null)
const ThemeUpdateContext = React.createContext(null)
export const useTheme = () => {
return useContext(ThemeContext)
}
export const useThemeUpdate = () => {
return useContext(ThemeUpdateContext)
}
export const ThemeProvider = ({ children }) => {
const [lightTheme, setLightTheme] = useState(true)
const toggleTheme = () => {
setLightTheme(theme => !theme)
}
return (
<ThemeContext.Provider value={lightTheme}>
<ThemeUpdateContext.Provider value={toggleTheme}>
{children}
</ThemeUpdateContext.Provider>
</ThemeContext.Provider>
)
}
export const ThemeConsumer = ThemeContext.Consumer
If you are using Next.js (our favorite React framework), your `_app.tsx` should look something like this:
import { AppProps } from 'next/app'
import { ThemeProvider } from '../context/themeContext'
// import css files here
function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
)
}
But no matter your framework, if you want the logic and state available to all components, then just wrap the Provider around the entire application.
Now when you want to have a checkbox that can toggle the theme it is as easy as grabbing the Context from your Context folder and your simple checkbox input can control the state in your entire application.
import React from 'react'
import { useTheme, useThemeUpdate } from '../../context/themeContext'
export const ThemToggle: React.FunctionComponent = () => {
const lightTheme = useTheme()
const toggleTheme = useThemeUpdate()
return (
<div >
<input checked={lightTheme} type="checkbox" onChange={toggleTheme}/>
</div>
)
}
Redux
Redux is for many the gold standard of state management in React applications. At 4-Ti we use it in several projects. Redux is a state container for Javascript apps and allows for a single source of truth so that different parts of your application have access to the same data and can mutate it in a predictable way.
Redux is predictable, yes, but it is a decision to made carefully. If you have a static website that has a bit of user interaction that needs an interface that responds to that interaction, then you may not need Redux. However, if you are building a full-stack application with a RESTful API connected to a database and relying on third-party APIs, then Redux could be a great option.
According to the Redux Documentation, Redux Toolkit is the recommended approach to implementing Redux Logic. We use the same approach in our projects and here’s why...it is so easy to implement! Like early versions of React, the earlier versions of Redux required a lot of out of boiler-plate code, but the logic is simplified using the Redux Toolkit. Here are some pro’s and cons of this state management system:
Pros:
- Reliable. The library is very reliable and predictable. It is well maintained and there is an active community creating updates and improvements as well as addressing issues.
- Insightful. Redux Toolkit allows developers to see when a request has been fulfilled, when it is pending, and when it has been fulfilled or rejected. It is easy to tap into these different stages of the request, and this nugget of information can be used to create a better user experience. It becomes easier to show a user an error message that they can act on, or display a loading spinner to indicate that clicking a button actually did something.
Cons:
- Maintainability. If you have a smaller application, think hard before implementing Redux because as easy as it is to use, it will become another piece of your code that you need to implement and maintain.
- Learning Curve. It takes a developer some time to become familiar to how Redux works. While this is true of a lot of frameworks and libraries, I will say from personal experience that Redux was one of the more challenging libraries to wrap my head around, while the implementation is not overly complicated.
I won’t go too deep into the implementation of Redux, there are much better tutorials for that, but it can be good to have some information about different frontend state management systems.