Skip to content

Alternatives

Video Summary

In the useMemo lesson, we saw how the useMemo hook can cache the result of an expensive computation, like when generating prime numbers:

function App() {
const [selectedNum, setSelectedNum] = React.useState(100);
const time = useTime();
const allPrimes = React.useMemo(() => {
const result = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
result.push(counter);
}
}
return result;
}, [selectedNum]);
return (
<>
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>
<form>
<label htmlFor="num">Your number:</label>
<input
type="number"
value={selectedNum}
onChange={(event) => {
// To prevent computers from exploding,
// we'll max out at 100k
let num = Math.min(100_000, Number(event.target.value));
setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>
);
}

This is perfectly valid, but if we take a step back and look at this component... Are we really solving this problem in the best way?

This App component appears to be managing two entirely unrelated tasks:

  1. Running a digital clock.
  2. The prime number stuff, picking a number and showing the related primes.

What if we split them off into two separate components?

For example:

Code Playground

import React from 'react';

import Clock from './Clock';
import PrimeCalculator from './PrimeCalculator';

function App() {
return (
<>
<Clock />
<PrimeCalculator />
</>
);
}

export default App;
preview
console
  1. PrimeCalculator render
  2. PrimeCalculator render

I've extracted two new components, Clock and PrimeCalculator. Because they're siblings now, updates in Clock won't affect PrimeCalculator.

Here's a graph showing this dynamic. Each box represents a component instance, and they flash when they re-render. Try clicking the “Increment” button to see it in action:

App

Clock

time: 22:35:27

PrimeCalculator

selectedNum: 10

We hear a lot about lifting state up, but sometimes, the better approach is to push state down! Each component should have a single responsibility, and in the example above, App was doing two totally-unrelated things.

Now, this won't always be an option. In a large, real-world app, there's lots of state that needs to be lifted up pretty high, and can't be pushed down.

I have another trick up my sleeves for this situation.

Let's look at an example. Suppose we need the time variable to be lifted up, above PrimeCalculator:

Code Playground

import React from 'react';
import getHours from 'date-fns/getHours';

import Clock from './Clock';
import PrimeCalculator from './PrimeCalculator';

function App() {
const time = useTime();

// Come up with a suitable background color,
// based on the time of day:
const backgroundColor = getBackgroundColorFromTime(time);

return (
<div style={{ backgroundColor }}>
<Clock time={time} />
<PrimeCalculator />
</div>
);
}

const getBackgroundColorFromTime = (time) => {
const hours = getHours(time);
if (hours < 12) {
// A light yellow for mornings
return 'hsl(50deg 100% 90%)';
} else if (hours < 18) {
// Dull blue in the afternoon
return 'hsl(220deg 60% 92%)'
} else {
// Deeper blue at night
return 'hsl(220deg 100% 80%)';
}
}

function useTime() {
const [time, setTime] = React.useState(new Date());
React.useEffect(() => {
const intervalId = window.setInterval(() => {
setTime(new Date());
}, 1000);
return () => {
window.clearInterval(intervalId);
}
}, []);
return time;
}

export default App;
preview
console
  1. PrimeCalculator render
  2. PrimeCalculator render
  3. PrimeCalculator render
  4. PrimeCalculator render
  5. PrimeCalculator render
  6. PrimeCalculator render
  7. PrimeCalculator render
  8. PrimeCalculator render
  9. PrimeCalculator render
  10. PrimeCalculator render
  11. PrimeCalculator render
  12. PrimeCalculator render
  13. PrimeCalculator render
  14. PrimeCalculator render

We use the time variable to control the background color. To make this possible, we've lifted the time state up to the parent App component.

With this change, PrimeCalculator will once again render whenever time changes, once per second.

We could solve this with the useMemo hook once more, but I have another idea. What if we memoize the entire component?

// PrimeCalculator.js
function PrimeCalculator() {
// Unchanged
}
export default React.memo(PrimeCalculator);

As we learned in the “Why React Re-Renders” lesson, React.memo wraps around our component like a forcefield, protecting it from unrelated updates. Our PrimeCalculator component will only re-render when its props or state changes.

And because it doesn't have any props, the only way this component can re-render is if the selectedNum state changes.

There's an interesting perspective-shift here: Before, we were memoizing the result of a specific computation, calculating the prime numbers. In this case, however, I've memoized the entire component instead.

Either way, the expensive calculation stuff will only re-run when the user picks a new selectedNum. But we've optimized the parent component rather than the specific slow lines of code.

Here's an updated graph, showing what's happening here:

App

time: 22:35:27

Clock

PrimeCalculator

selectedNum: 10

Pure Component

To be clear, I'm not suggesting that useMemo is bad, or that we should always try and find alternatives. The point I'm hoping to make is that we should pay attention to application structure; when we refactor code to be more readable, we often get performance benefits for free, and it might turn out that useMemo is not necessary!

That said, useMemo is still a valuable tool in the toolbox. On this course platform, I use useMemo a couple dozen times!

I realize that this lesson is pretty advanced — over time, in your React journey, you'll start to see opportunities to refactor code and improve structure, but it takes a lot of practice to reach this point. I'm hoping that some of these ideas will percolate in the back of your mind, as we continue to work through the course.

Here's the interactive graphs from the video above:

Sibling Clock and PrimeCalculator:

App

Clock

time: 22:35:27

PrimeCalculator

selectedNum: 10

Pure PrimeCalculator with lifted time state:

App

time: 22:35:27

Clock

PrimeCalculator

selectedNum: 10

Pure Component