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. State management in react is complex. 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.
State Management in React
CodeSandbox for all examples
https://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/PropsConstState.tsx
Props
What | A 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-render | Normally |
When to use | To 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 example | See src/components/HeadingWithProp.tsx |
Resources | https://reactjs.org/docs/components-and-props.html |
Constants
What | Standard 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-render | No |
When to use | Any 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 example | See 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. |
Resources | https://reactjs.org/docs/components-and-props.html |
State
What | Data 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-render | Yes. The component and child components will re-render. |
When to use | Any 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 example | See 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. |
Resources | Props v State |
Lift the State
What | Moving 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 use | When you have multiple components at a similar level, which need access to the same value. |
When NOT to use | When 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 example | https://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/examples/LiftTheState.tsx |
Resources | React docs – https://reactjs.org/docs/lifting-state-up.html |
Composition
What | Instead 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 use | When you’re passing props down, purely to pass them down another level. |
When NOT to use | When the child needs the prop too. When the child that needs it is several levels below the parent. (Prefer context) |
CodeSandbox example | https://codesandbox.io/s/frosty-hooks-xtj6i?file=/src/examples/Composition.tsx |
Prop Drilling issues? useContext
What | Instead of passing props down several levels, store them in context and use the useContext hook to access them. |
---|---|
When to use | When several components need access to the values, at different levels areas of the page. |
When NOT to use | When 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 example | https://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.”
What | Instead of event callbacks from setting state, reducers provide methods to dispatching actions, which in turn update the values. |
---|---|
When to use | When 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 use | If you only ever do one action to the value, i.e. setting it to be an object. Prefer to useState |
CodeSandbox & Resources | React’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
What | useMemo 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 use | When 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 use | Do not use this by default. It will actually make the code LESS EFFICIENT for most instances. AHA (Avoid Hasty Abstractions). |
Resources | KCD 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 Documentation | Our guidance |
---|---|
You have large amounts of application state that are needed in many places in the app | This 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 time | This 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 complex | Reducers (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 people | Literally 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