Proper State Management in React

Introduction

There’s often confusion around how to approach data management when creating react components and apps. People get familiar with one concept, maybe State, then use it over and over regardless of the requirements.

There was a post (now hidden) on a popular react forum where the user commented,

“I hate props, I never use them”

(Popular react forum user)

This received thousands of comments from other confused developers who had had problems with props in the past, so therefore never used them. That got me thinking that we need a bit of clarity about what type of data management to use when. From constants and simple props to state, reducers and context.

There’s a tool for every job. If you try to use a hammer to screw something to the wall, you’ll end up with a hole in the wall and the hammer in the bin.

The beta React docs explain the concepts here, sensationally, so it may be worth reading their Managing State documentation before continuing.


Props, Constants & State

CodeSandbox for all examples
https://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/PropsConstState.tsx

Props

WhatA property that is passed into a component via its arguments
Does it change?The component receiving the prop cannot change the props value directly (immutable) but the parent component can change the values passed in.
Do changes cause re-renderNormally
When to useTo abstract functionality of a component, to allow re-use in different areas. For example, if we want to render the same UI twice but with different values for the title.
CodeSandbox exampleSee src/components/HeadingWithProp.tsx
Resourceshttps://reactjs.org/docs/components-and-props.html

Constants

WhatStandard Typescript variables beginning with const. Regenerated each time the component re-renders. 
Does it change?No. (If you want it to, use state).
Note they are also not properly “immutable”, as they only store the reference to the value.
Do changes cause re-renderNo
When to useAny variable within a component’s scope that won’t typically change once set. Often used when a value is used more than once but can also just be used to be more declarative.
CodeSandbox exampleSee src/components/HeadingWithPropAndConst.tsx

In this example, if we changed our component to always have ” – Example” appended to the title and the browser’s tab title to be updated to the same value, we could create our new heading const at the top of the component and use within. Any change to the title prop forces a re-render and heading’s value will be created from scratch each time. 
Resourceshttps://reactjs.org/docs/components-and-props.html

State

WhatData that changes over the lifetime of a component. 
Can be further passed to child components as props.
Does it change?Yes, regularly.
Do changes cause re-renderYes. The component and child components will re-render.
When to useAny variable within a component’s scope that WILL typically change once set, such as a flag isModalOpen, which is true when a modal is open and false otherwise.
CodeSandbox exampleSee src/components/HeadingWithState.tsx
In this example, we added the ability for the user to manipulate the value of the suffix used in the full title. The component re-renders accordingly when doing so.
ResourcesProps v State

Lift the State

WhatMoving the control/ownership of state variables and how to update them to the lowest common ancestor. E.g. if two sibling components need access to the same value, move the useState declaration to the parent and pass as props.
When to useWhen you have multiple components at a similar level, which need access to the same value.
When NOT to useWhen the closest common ancestor is several levels above the lowest child component, this will cause prop-drilling. Prefer either composition or context, in this situation.
CodeSandbox examplehttps://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/examples/LiftTheState.tsx
ResourcesReact docs – https://reactjs.org/docs/lifting-state-up.html

Composition

WhatInstead of passing props to a child only for the child to pass to its child, pass the grandchild component with the props, directly to the child instead.
When to useWhen you’re passing props down, purely to pass them down another level.
When NOT to useWhen the child needs the prop too.
When the child that needs it is several levels below the parent. (Prefer context)
CodeSandbox examplehttps://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/examples/Composition.tsx

Prop Drilling issues? useContext

WhatInstead of passing props down several levels, store them in context and use the useContext hook to access them.
When to useWhen several components need access to the values, at different levels areas of the page.
When NOT to useWhen the value can be inferred by other state / variables. Use const.
When the value is only used by one component (Use const if it doesn’t change over time, useState if it does).
When you use the value in closely related components, for example 2 sibling components or a parent and its children, lift the state.
When you are only using the value in a component and its grandchildren and can use better composition.
CodeSandbox examplehttps://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/examples/Context.tsx

When to use Reducers

Components with many state updates spread across many event handlers can get overwhelming. For these cases, you can consolidate all the state update logic outside your component in a single function, called a “reducer.”

WhatInstead of event callbacks from setting state, reducers provide methods to dispatching actions, which in turn update the values.
When to useWhen your state is updated by many different event handlers, for example an API loads the data, it’s set. Clicking on a button may change a value within one item. Clicking on another may change the status of one of the items directly. Each of these may previously have manipulated the same “state” object, which gets messy.
When NOT to useIf you only ever do one action to the value, i.e. setting it to be an object. Prefer to useState
CodeSandbox & ResourcesReact’s own CodeSandbox – https://codesandbox.io/s/zmogx?file=/App.js&from-sandpack=true
React Docs on extracting state logic into a reducer
React Docs on scaling up reducer + context together

“When it’s just an independent element of state you’re managing: useState”

“When one element of your state relies on the value of another element of your state in order to update: useReducer”

Kent C Dodds – https://kentcdodds.com/blog/should-i-usestate-or-usereducer

When to useMemo / useCallback

WhatuseMemo and useCallback provide memoized values that only process, if the dependencies change, otherwise they provide the cached version from the previous call. Loads of docs online say how great they are for performance optimisation. Unfortunately, they miss out a very key point.

They only benefit you, when the logic inside takes more time/resource than the additional caching layer does. So for simple functions/logic, it’s actually more expensive to useMemo / useCallback than to just process the logic.
When to useWhen the logic is deeply complex (difficult computational mathematics maybe) or takes a long time (an API call that may not be required to be called a second time). 
When NOT to useDo not use this by default. It will actually make the code LESS EFFICIENT for most instances. AHA (Avoid Hasty Abstractions).
ResourcesKCD When to useMemo / UseCallback
Good article by Kevin Van Ryckegem with example benchmarks

When to use Redux/Flux

Redux is incredibly powerful but also brings a large overhead when not used correctly.

Since March 2020, I’ve recommended not to use Redux by default and applying clean code principals (like those above) to solve the problems. If and only when this is insufficient, then consider Redux.

Redux’s documentation naturally, is a bit biased. It states that you should use Redux when:

Redux’s DocumentationOur guidance
You have large amounts of application state that are needed in many places in the appThis applies to 90% of web projects. However, writing clean code following the approaches above, allow us to manage our data without the overheads.
The app state is updated frequently over timeThis is literally just any state variable. Having state that changes over time doesn’t in itself mean we need Redux. See React’s official docs:

State and lifecycle
Reacting to input with State
The logic to update that state may be complexReducers (potentially coupled with Immer) are brilliant solutions for this. At the point where we’re starting to struggle, maybe this is the turning point where we need Redux but current experience from the teams suggests reducers should do the job.
The app has a medium or large-sized codebase, and might be worked on by many peopleLiterally every project. Not a reason in itself to use Redux. 

Kent C Dodds talks about Redux at the start of his article Application State Management with React, in how the main reason for its usage was to resolve prop-drilling (better solved by lifting the state/good composition/context depending on the situation) or to allow complex updates to values, which can be solved with well written reducers.

Redux TLDR

Redux can be powerful but also adds complexity.

It is often overused and can encourage bad practices when misused.

References / Resources

https://beta.reactjs.org/learn/managing-state – React

https://kentcdodds.com/blog/application-state-management-with-react – Kent C. Dodds

Props v State Kent C. Dodds

Should I useState or useReducer?  Kent C. Dodds

State Reducer pattern with hooks   Kent C. Dodds

Redux Documentation – Redux