Pure functions only perform a calculation and nothing more. It makes your code easier to understand, debug, and allows React to automatically optimize your components and Hooks correctly.
Note
This reference page covers advanced topics and requires familiarity with the concepts covered in theKeeping Components Pure page.
- Why does purity matter?
- Components and Hooks must be idempotent
- Side effects must run outside of render
- Props and state are immutable
- Return values and arguments to Hooks are immutable
- Values are immutable after being passed to JSX
Why does purity matter?
One of the key concepts that makes React,React ispurity. A pure component or hook is one that is:
- Idempotent – Youalways get the same result every time you run it with the same inputs – props, state, context for component inputs; and arguments for hook inputs.
- Has no side effects in render – Code with side effects should runseparately from rendering. For example as anevent handler – where the user interacts with the UI and causes it to update; or as anEffect – which runs after render.
- Does not mutate non-local values: Components and Hooks shouldnever modify values that aren’t created locally in render.
When render is kept pure, React can understand how to prioritize which updates are most important for the user to see first. This is made possible because of render purity: since components don’t have side effectsin render, React can pause rendering components that aren’t as important to update, and only come back to them later when it’s needed.
Concretely, this means that rendering logic can be run multiple times in a way that allows React to give your user a pleasant user experience. However, if your component has an untracked side effect – like modifying the value of a global variableduring render – when React runs your rendering code again, your side effects will be triggered in a way that won’t match what you want. This often leads to unexpected bugs that can degrade how your users experience your app. You can see anexample of this in the Keeping Components Pure page.
How does React run your code?
React is declarative: you tell Reactwhat to render, and React will figure outhow best to display it to your user. To do this, React has a few phases where it runs your code. You don’t need to know about all of these phases to use React well. But at a high level, you should know about what code runs inrender, and what runs outside of it.
Rendering refers to calculating what the next version of your UI should look like. After rendering,Effects areflushed (meaning they are run until there are no more left) and may update the calculation if the Effects have impacts on layout. React takes this new calculation and compares it to the calculation used to create the previous version of your UI, thencommits just the minimum changes needed to theDOM (what your user actually sees) to catch it up to the latest version.
Deep Dive
One quick heuristic to tell if code runs during render is to examine where it is: if it’s written at the top level like in the example below, there’s a good chance it runs during render.
functionDropdown(){
constselectedItems =newSet();// created during render
// ...
}
Event handlers and Effects don’t run in render:
functionDropdown(){
constselectedItems =newSet();
constonSelect =(item)=>{
// this code is in an event handler, so it's only run when the user triggers this
selectedItems.add(item);
}
}
functionDropdown(){
constselectedItems =newSet();
useEffect(()=>{
// this code is inside of an Effect, so it only runs after rendering
logForAnalytics(selectedItems);
},[selectedItems]);
}
Components and Hooks must be idempotent
Components must always return the same output with respect to their inputs – props, state, and context. This is known asidempotency.Idempotency is a term popularized in functional programming. It refers to the idea that youalways get the same result every time you run that piece of code with the same inputs.
This means thatall code that runsduring render must also be idempotent in order for this rule to hold. For example, this line of code is not idempotent (and therefore, neither is the component):
functionClock(){
consttime =newDate();// 🔴 Bad: always returns a different result!
return<span>{time.toLocaleString()}</span>
}
new Date()
is not idempotent as it always returns the current date and changes its result every time it’s called. When you render the above component, the time displayed on the screen will stay stuck on the time that the component was rendered. Similarly, functions likeMath.random()
also aren’t idempotent, because they return different results every time they’re called, even when the inputs are the same.
This doesn’t mean you shouldn’t use non-idempotent functions likenew Date()
at all – you should just avoid using themduring render. In this case, we cansynchronize the latest date to this component using anEffect:
import{useState,useEffect}from'react';functionuseTime(){// 1. Keep track of the current date's state. `useState` receives an initializer function as its// initial state. It only runs once when the hook is called, so only the current date at the// time the hook is called is set first.const[time,setTime] =useState(()=>newDate());useEffect(()=>{// 2. Update the current date every second using `setInterval`.constid =setInterval(()=>{setTime(newDate());// ✅ Good: non-idempotent code no longer runs in render},1000);// 3. Return a cleanup function so we don't leak the `setInterval` timer.return()=>clearInterval(id);},[]);returntime;}exportdefaultfunctionClock(){consttime =useTime();return<span>{time.toLocaleString()}</span>;}
By wrapping the non-idempotentnew Date()
call in an Effect, it moves that calculationoutside of rendering.
If you don’t need to synchronize some external state with React, you can also consider using anevent handler if it only needs to be updated in response to a user interaction.
Side effects must run outside of render
Side effects should not runin render, as React can render components multiple times to create the best possible user experience.
Note
Side effects are a broader term than Effects. Effects specifically refer to code that’s wrapped inuseEffect
, while a side effect is a general term for code that has any observable effect other than its primary result of returning a value to the caller.
Side effects are typically written inside ofevent handlers or Effects. But never during render.
While render must be kept pure, side effects are necessary at some point in order for your app to do anything interesting, like showing something on the screen! The key point of this rule is that side effects should not runin render, as React can render components multiple times. In most cases, you’ll useevent handlers to handle side effects. Using an event handler explicitly tells React that this code doesn’t need to run during render, keeping render pure. If you’ve exhausted all options – and only as a last resort – you can also handle side effects usinguseEffect
.
When is it okay to have mutation?
Local mutation
One common example of a side effect is mutation, which in JavaScript refers to changing the value of a non-primitive value. In general, while mutation is not idiomatic in React,local mutation is absolutely fine:
functionFriendList({friends}){
constitems =[];// ✅ Good: locally created
for(leti =0;i <friends.length;i++){
constfriend =friends[i];
items.push(
<Friendkey={friend.id}friend={friend}/>
);// ✅ Good: local mutation is okay
}
return<section>{items}</section>;
}
There is no need to contort your code to avoid local mutation.Array.map
could also be used here for brevity, but there is nothing wrong with creating a local array and then pushing items into itduring render.
Even though it looks like we are mutatingitems
, the key point to note is that this code only does solocally – the mutation isn’t “remembered” when the component is rendered again. In other words,items
only stays around as long as the component does. Becauseitems
is alwaysrecreated every time<FriendList />
is rendered, the component will always return the same result.
On the other hand, ifitems
was created outside of the component, it holds on to its previous values and remembers changes:
constitems =[];// 🔴 Bad: created outside of the component
functionFriendList({friends}){
for(leti =0;i <friends.length;i++){
constfriend =friends[i];
items.push(
<Friendkey={friend.id}friend={friend}/>
);// 🔴 Bad: mutates a value created outside of render
}
return<section>{items}</section>;
}
When<FriendList />
runs again, we will continue appendingfriends
toitems
every time that component is run, leading to multiple duplicated results. This version of<FriendList />
has observable side effectsduring render andbreaks the rule.
Lazy initialization
Lazy initialization is also fine despite not being fully “pure”:
functionExpenseForm(){
SuperCalculator.initializeIfNotReady();// ✅ Good: if it doesn't affect other components
// Continue rendering...
}
Changing the DOM
Side effects that are directly visible to the user are not allowed in the render logic of React components. In other words, merely calling a component function shouldn’t by itself produce a change on the screen.
functionProductDetailPage({product}){
document.title =product.title;// 🔴 Bad: Changes the DOM
}
One way to achieve the desired result of updatingdocument.title
outside of render is tosynchronize the component withdocument
.
As long as calling a component multiple times is safe and doesn’t affect the rendering of other components, React doesn’t care if it’s 100% pure in the strict functional programming sense of the word. It is more important thatcomponents must be idempotent.
Props and state are immutable
A component’s props and state are immutablesnapshots. Never mutate them directly. Instead, pass new props down, and use the setter function fromuseState
.
You can think of the props and state values as snapshots that are updated after rendering. For this reason, you don’t modify the props or state variables directly: instead you pass new props, or use the setter function provided to you to tell React that state needs to update the next time the component is rendered.
Don’t mutate Props
Props are immutable because if you mutate them, the application will produce inconsistent output, which can be hard to debug as it may or may not work depending on the circumstances.
functionPost({item}){
item.url =newUrl(item.url,base);// 🔴 Bad: never mutate props directly
return<Linkurl={item.url}>{item.title}</Link>;
}
functionPost({item}){
consturl =newUrl(item.url,base);// ✅ Good: make a copy instead
return<Linkurl={url}>{item.title}</Link>;
}
Don’t mutate State
useState
returns the state variable and a setter to update that state.
const[stateVariable,setter] =useState(0);
Rather than updating the state variable in-place, we need to update it using the setter function that is returned byuseState
. Changing values on the state variable doesn’t cause the component to update, leaving your users with an outdated UI. Using the setter function informs React that the state has changed, and that we need to queue a re-render to update the UI.
functionCounter(){
const[count,setCount] =useState(0);
functionhandleClick(){
count =count +1;// 🔴 Bad: never mutate state directly
}
return(
<buttononClick={handleClick}>
You pressed me{count} times
</button>
);
}
functionCounter(){
const[count,setCount] =useState(0);
functionhandleClick(){
setCount(count +1);// ✅ Good: use the setter function returned by useState
}
return(
<buttononClick={handleClick}>
You pressed me{count} times
</button>
);
}
Return values and arguments to Hooks are immutable
Once values are passed to a hook, you should not modify them. Like props in JSX, values become immutable when passed to a hook.
functionuseIconStyle(icon){
consttheme =useContext(ThemeContext);
if(icon.enabled){
icon.className =computeStyle(icon,theme);// 🔴 Bad: never mutate hook arguments directly
}
returnicon;
}
functionuseIconStyle(icon){
consttheme =useContext(ThemeContext);
constnewIcon ={...icon};// ✅ Good: make a copy instead
if(icon.enabled){
newIcon.className =computeStyle(icon,theme);
}
returnnewIcon;
}
One important principle in React islocal reasoning: the ability to understand what a component or hook does by looking at its code in isolation. Hooks should be treated like “black boxes” when they are called. For example, a custom hook might have used its arguments as dependencies to memoize values inside it:
functionuseIconStyle(icon){
consttheme =useContext(ThemeContext);
returnuseMemo(()=>{
constnewIcon ={...icon};
if(icon.enabled){
newIcon.className =computeStyle(icon,theme);
}
returnnewIcon;
},[icon,theme]);
}
If you were to mutate the Hook’s arguments, the custom hook’s memoization will become incorrect, so it’s important to avoid doing that.
style =useIconStyle(icon);// `style` is memoized based on `icon`
icon.enabled =false;// Bad: 🔴 never mutate hook arguments directly
style =useIconStyle(icon);// previously memoized result is returned
style =useIconStyle(icon);// `style` is memoized based on `icon`
icon ={...icon,enabled:false};// Good: ✅ make a copy instead
style =useIconStyle(icon);// new value of `style` is calculated
Similarly, it’s important to not modify the return values of Hooks, as they may have been memoized.
Values are immutable after being passed to JSX
Don’t mutate values after they’ve been used in JSX. Move the mutation to before the JSX is created.
When you use JSX in an expression, React may eagerly evaluate the JSX before the component finishes rendering. This means that mutating values after they’ve been passed to JSX can lead to outdated UIs, as React won’t know to update the component’s output.
functionPage({colour}){
conststyles ={colour,size:"large"};
constheader =<Headerstyles={styles}/>;
styles.size ="small";// 🔴 Bad: styles was already used in the JSX above
constfooter =<Footerstyles={styles}/>;
return(
<>
{header}
<Content/>
{footer}
</>
);
}
functionPage({colour}){
constheaderStyles ={colour,size:"large"};
constheader =<Headerstyles={headerStyles}/>;
constfooterStyles ={colour,size:"small"};// ✅ Good: we created a new value
constfooter =<Footerstyles={footerStyles}/>;
return(
<>
{header}
<Content/>
{footer}
</>
);
}