A simple guide to proper state management in React

A common problem developers are faced when working on a React application is figuring out the best way to manage state between all their components. I am not saying that it is difficult to manage state in React, I mean to say that it isn’t always easy to figure out which way is the best way to manage state in your application. I will go over three common ways to manage state in your app, and help guide your design decisions around each one.

1) React Component Props

This is the most basic way to manage state for your components, you simply pass the state via props. Of course things can get pretty complicated as you add more and more components that rely on the same shared state. For most use cases, this will probably be the best solution – it’s clean, simple, and keeps your components reusable. If your component is a “dumb” UI component, then it ideally should take in state as props and render it.

function ButtonWithText(buttonText) {
	return <button>{buttonText}</button>
}
// Usage
<ButtonWithText buttonText={“save”}></ButtonWithText>

Probably the most common issue I have seen with managing state this way is prop drilling (Kent C. Dobbs has a great post on prop drilling). In my experience, prop drilling is not an issue that surfaces immediately, but happens over time as components are gradually refactored and split into multiple components. For example, imagine a not-too-uncommon scenario where you have a single component, but then later on you realize that part of the component would be useful elsewhere in your application, so then you do the reasonable thing and split out the reusable stuff into a separate component. One or two of these refactors and we end up with textbook prop drilling. This leads to cluttered components that are aware of state that they don’t actually care about, causing hits to both the maintainability and readability of your code.

(The following code example is from Kent C. Dobbs aforementioned article, it was more concise than anything I could come up with)

function Toggle() {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(o => !o)
  return <Switch on={on} onToggle={toggle} />
}

function Switch({on, onToggle}) {
  return (
    <>
      <SwitchMessage on={on} />
      <SwitchButton onToggle={onToggle} />
    </>
  )
}

function DisableOnSubmit({on}) {
  return <>The button is {on ? ‘on’ : ‘off’}</>
}

function SwitchButton({onToggle}) {
  return <button onClick={onToggle}>Toggle</button>
}

In these code examples, we are using function components and React Hooks. If you have not yet tried out React Hooks, I would highly recommend it (it’s surprisingly easy to convert class components to instead use Hooks).

Prop drilling only really becomes an issue once your app grows and your component hierarchy becomes cumbersome. In some cases you can prevent deep prop-drilling by re-combining components that didn’t really need to be split in the first place. In other cases, you might want to consider trying one of the next two options.

2) React Context

React Context was added to React to help solve the problem of sharing state between multiple components, especially between ones that are not close in the component hierarchy. React Context is a great option because it is very straight forward to use and has native support, as it is part of React itself.

function ParentComponent() {
    [text, setText] = useState("");
    // Create a Context
    const TextContext = React.createContext(null);
    return (
        <TextContext.Provider
            value = {{
                displayText: text,
                updateText: setText,
            }}
        >
          <ChildComponent />  
          <AnotherChildComponent />
        </TextContext.Provider>
	)
}

function ChildComponent() {
    // With React Hooks, 'useContext' will cause your component to re-render everytime
	// the context value changes
	const textContext = useContext(TextContext);
    return (
    	<FancyButton onClick={() => textContext.updateText("clicked!")} />
	);
}

function AnotherChildComponent() {
    const textContext = useContext(TextContext);
    return (
    	<p>{textContext.text}</p>
    );
}

In a more fleshed out real world example, you could really see how this could simplify things by allowing you to just cut out all the extraneous prop passing. However, the major downside to using React Context is that it reduces the reusability of the components that use it. A simple component takes state in via props, this allows it to be reused in any location that is able to provide it the state it needs to properly render – including an entirely different application. When your component now depends React Contexts, your component may still be reusable in parts of your app (depending on the use case), it will be much less likely to be reusable outside of your specific use case.

3) Redux state management

Redux is a separate library that allows you to maintain a centralized store for your app’s shared state while also providing a unidirectional data flow for the state managed by your Redux store. There other libraries that achieve similar results (MobX, Relay + GraphQL, Jumpsuit), but Redux is the most popular one.

(If you haven’t yet worked with Redux before, I would highly recommend going through Redux’s Getting Started guide, I found it to be a good starting point when I was first learning Redux.)

Rather than diving into the more technical details of how to implement Redux into your application, I want to outline the various pros and cons of it, so you can have a better idea if it is the right tool for your app.

The Cons:

  • Decent amount of boilerplate code — when switching to Redux, you will need to create quite a few more classes to achieve arguably the same functionality that you had before. In the long run it will be worth it, but I found it frustrating at first.
  • Non-trivial learning curve — Redux has a steep enough learning curve where it is not uncommon for some developers to make mistakes that can counteract the benefits of using the library in the first place. Mistakes like using Redux unnecessarily (when props could suffice) is not uncommon, especially with more junior developers.

The Pros:

  • Structured solution to a complex problem — before utilizing Redux (and React), our application tried to maintain a form of consistent shared state in a truly complex mess that became unmanageable due to mutations to that state being difficult to properly track and sync across all components. Redux greatly simplified this for us with its well-defined and structured approach.
  • Easy to test and debug — with Redux, state in your application becomes predictable because of changes to the store are limited to the reducer functions. There is great tooling for Redux that allow you to go back in time and see how your app looked with "old" state (redux-devtools).

Redux is a fantastic tool for helping manage state in larger React applications, however, it can be a bit heavy-handed for simpler use cases.

Closing Thoughts

My general process is that I default to using simple props for my state management until I find a compelling reason not to. Of course there are cases where the component I am building will clearly need to use state that is already managed in either an existing Context or in the Redux store. I would encourage you to spend some time thinking about how state will be managed for the next component you create, even before you start writing any code!

If you want updates from me on my future blog posts or on my future projects, please sign up for my email list below!

Processing…
Success! You're on the list.

Designing and Implementing a Ranking Algorithm

I recently had the desire and need to create a ranking algorithm for a side project I was working on. I wanted to keep both the design and implementation fairly simple for my project, so I think this post will be great for people wanting to get their toes wet.

The ranking algorithm I ended up building is used for ranking user-created content – similar to the ranking of posts on sites like Reddit or Hacker News. So one might describe it as a ‘hotness ranking‘ opposed to a ‘relevancy ranking’ used in search engines.

My goal is to walk through the basics of designing a ranking algorithm and then sharing my experiences and findings from implementing my algorithm. My implementation was done for a web application using Node.js and MongoDB.

Designing the ranking algorithm

When starting to design my algorithm, I naturally wanted to understand how other sites’ ranking algorithms worked, fortunately I found a couple of blog posts that provided great introductions for ranking algorithms used by both Reddit and HackerNews. I would also recommend reading this blog post that describes the design process around Reddit’s ‘best’ comment ranking algorithm.

Continue reading “Designing and Implementing a Ranking Algorithm”

Real-world programming interview question #1

As programmers, we like to solve problems. In school, we thoroughly enjoy working through solutions for our homework problems. When interviewing for a developer job, we have to solve some complex programming problems (on the spot). As software developers, we still work through complex problems, but suddenly our solutions have more weight because they are solving real business problems. I enjoy solving problems that come from real-world business context; I find that my motivation to solve these problems is greater. Today at work, I came across a problem and worked through a solution that reminded me of a challenging problem one might see at school or in an interview.

Here is the problem:

You are processing potentially thousands of units of inventory and you are writing code to take inventory data from a supplier and syncing it with a distributor. The inventory is comprised of rental properties which have restrictions on specific days that are ‘closed to arrival’ and days that are ‘closed to departure’. Unfortunately, the format that the supplier stores this information is very different from the format that the distributor’s API expects. It is your job to transform this data to be in the proper format for the distributor’s API to handle.

Continue reading “Real-world programming interview question #1”

Node.js for side projects

There is one thing that unites people who work on software: we like to create things that work. Nothing is more satisfying than finishing a feature or project that simply does what it was intended to do. When it comes to my side projects, I love learning new frameworks, technologies, and languages. But in the end, the most rewarding and satisfying part is finishing the project and releasing something to the wild. Let’s be honest, if you work full-time and worry about things like exercising, chores, and possibly sleeping – then you understand me when I say it is not always easy finding time to complete a side project. We can help ourselves by planning ahead and perhaps utilizing some fancy project management tools. But what about our technology stack? If our end goal is to finish and release our project, then it makes sense to pick a technology stack that is well-suited for rapid development.

Node.js has become a popular server-side platform used to power the web servers for many modern web applications. When developing a Node.js application, you will be writing everything in Javascript and you will be able to run your applications on any type of server (Windows, Mac, or Linux). Node.js consists of a large pool of tightly-scoped modules and packages that you can utilize. What is great though is that you only use what you need, keeping your application as lightweight as possible. At the end of the day, side projects should still be enjoyable, and Node.js applications are fun to write.

Continue reading “Node.js for side projects”