I think I was getting confused by React.memo, from the docs:
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
useMemo appears to work fine with an array as a dependancy:
https://codesandbox.io/s/mystifying-cori-jr0vi?file=/src/App.js:0-647
import React, { useState, useMemo } from "react";
import "./styles.css";
export default function App() {
console.log("App rendered");
const [st, setStr] = useState("0");
const [arr, setArr] = useState([1]);
const thingToRender = useMemo(() => {
console.log("thingToRender ran");
return `Array length is ${arr.length}`;
}, [arr]);
return (
<div className="App">
<button onClick={() => setStr(`${Math.random()}`)}>Change str</button>
<h1>{st}</h1>
<p>{thingToRender}</p>
<button onClick={() => setArr([...arr, Math.round(Math.random() * 10)])}>
Change arr
</button>
</div>
);
}
However it's worth being aware that it wont work if you map (or other methods that create a new array):
https://codesandbox.io/s/silent-silence-91j8s?file=/src/App.js
import React, { useState, useMemo } from "react";
import "./styles.css";
export default function App() {
console.log("App rendered");
const [st, setStr] = useState("0");
const [arr, setArr] = useState([1]);
const arr2 = arr.map((item) => item);
const thingToRender = useMemo(() => {
console.log("thingToRender ran");
return `Array length is ${arr2.length}`;
}, [arr2]);
return (
<div className="App">
<button onClick={() => setStr(`${Math.random()}`)}>Change str</button>
<h1>{st}</h1>
<p>{thingToRender}</p>
<button onClick={() => setArr([...arr, Math.round(Math.random() * 10)])}>
Change arr
</button>
</div>
);
}