Understanding Pure Functions in React Functional Components: Importance and How to Implement
You've probably heard the term "pure function" before, but if not, it's a function that follows two principles:
No Side Effects: a pure function must not alter any objects or variables that exist before it's invoked
Deterministic: a pure function must always return the same output when given the same set of inputs
In other words, you can tell if a function is "pure" if it's consistent and doesn't affect anything outside its scope.
An add
function is a basic example of a pure function:
function add(a, b) {
return a + b;
}
In the example above, the add
function always returns the same result when you give it a specific set of inputs. 2 + 3
will always equal 5
(if for some reason it doesn't, then we probably have bigger problems).
React assumes functional components are pure
If your React components aren't pure functions, that'll likely lead to unpredictable and buggy applications. In other words, given the same set of props (i.e. inputs), your component should always return the same JSX (i.e. output).
Let's take a look at an example:
In the example above, the Banner
component takes a name
prop and displays a welcome message.
Given a specific value for the name
prop, the JSX the Banner
component returns will always be the same. If you pass the name Jack
to the Banner
component, it's impossible for the text to be anything other than Hello Jack!
.
Impure components lead to inconsistent JSX
An impure component can lead to unexpected behavior:
This time, the Banner
component takes a time
variable defined outside of it and displays it along with the name. We use time % 12
to convert the time to a range between 1 - 12
and then add AM
or PM
to indicate whether it's morning or night.
However, because the first Banner
component modifies the time
component, the second Banner
component shows AM
instead of PM
. When the first Banner
component renders, it sets the time
variable to 11
(i.e. 23 % 12 === 11
), which the second Banner
component then uses.
Note: If you run this with StrictMode enabled, it will actually show
11 AM
for both Banners because React renders each component twice in adev
environment.
Access variables via props instead of via closures
In the example above, the Banner
component has access to the time
variable via a closure. Instead of doing this, you can fix the problem by just passing the time as a prop instead:
Mutating local variables is okay!
Remember, pure functions must not alter any objects or variables that exist before they're invoked. This means a pure function can update an object or variable that's created when it's invoked. In other words, you can update local variables all day long without making the function impure!
The following is perfectly acceptable:
type BannerProps = { name: string; time: number };
function Banner(props: BannerProps) {
let time = props.time;
if (time > 23) {
time = 0;
}
const isPM = time > 11;
time = time % 12;
return (
<p>
Hello {props.name}, it's {time} {isPM ? "PM" : "AM"}!
</p>
);
}
In this example, if the time
variable is greater than 23
, we reset it back to 0
. You can see that throughout the component, we're updating the time
variable in several places. This is completely fine because the time
variable was initialized inside the Banner
component.
Why are pure components important in React?
Here are a few reasons why keeping your React components pure is beneficial:
Predictability: Pure components are predictable, making debugging easier.
Performance: Pure components are cacheable, which lets React optimize rendering (i.e. skip renders when inputs haven't changed).
Server-Side Rendering: Pure components are deterministic, so your server can reuse them for many requests.
Concurrent Mode: React can optimize rendering by pausing and resuming the render cycle without issues because pure components don’t rely on side effects.
It's important to make sure your components are pure. If you don't, your applications will be harder to maintain and less reliable in the long run!