Remove the `resultantColors` state, and calculate it whenever `search` changes:
<!-- begin snippet: js hide: false console: true babel: true babelPresetReact: true babelPresetTS: false -->
<!-- language: lang-js -->
const { useMemo, useState } = React;
const mockColors = ['white', 'blue', 'yellow', 'green', 'orange', 'purple'];
function App() {
const [search, setSearch] = useState('');
const handleInputChange = (e) => {
setSearch(e.target.value);
};
const resultantColors = useMemo(() =>
search == ''
? mockColors
: mockColors.filter(color =>
color.toLowerCase().includes(search.toLowerCase())
)
, [search]);
return (
<div style={{ border: '1px solid blue', padding: 20, margin: 20 }}>
Filter Example with UseEffect <br />
<label htmlFor="colors">Filter Color:</label>
<input
type="search"
name="search"
value={search}
id="colors"
onChange={handleInputChange}
placeholder="filter colors"
/>
{resultantColors.length > 0 ? (
resultantColors.map((value, index) => (
<>
<li key={`${value} + ${index}`}>{value}</li>
</>
))
) : (
<p>No items - search existing value </p>
)}
</div>
);
}
ReactDOM
.createRoot(root)
.render(<App />);
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<!-- end snippet -->
The simplest and most effective way is to not use `useRef` at all. Just use a **callback ref** that creates a new array of refs on every render.
```js
function useArrayRef() {
const refs = []
return [refs, el => el && refs.push(el)]
}
```
### Demo
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
<div id="root"></div>
<script type="text/babel" defer>
const { useEffect, useState } = React
function useArrayRef() {
const refs = []
return [refs, el => el && refs.push(el)]
}
const App = () => {
const [elements, ref] = useArrayRef()
const [third, setThird] = useState(false)
useEffect(() => {
console.log(elements)
}, [third])
return (
<div>
<div ref={ref}>
<button ref={ref} onClick={() => setThird(!third)}>toggle third div</button>
</div>
<div ref={ref}>another div</div>
{ third && <div ref={ref}>third div</div>}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<!-- end snippet -->
### Update:
Here is a Typescript version of the hook that creates memoized callback refs and supports passing a custom key to each element ref.
```typescript
import { MutableRefObject, useRef } from 'react'
type UseElementsRefResult<T extends HTMLElement = HTMLElement> = [
/**
* Map of DOM node refs identified by an auto-created key or custom one.
*/
MutableRefObject<{ readonly [K: string | number]: T }>,
/**
* Creates a memoized callback ref for a DOM node.
* If you expect the rendering order of nodes to change, use a custom key.
*
* @param customKey Key used to set/unset the node ref.
*/
(customKey?: string | number) => (el?: T) => void,
]
/**
* Hook that creates callback refs to manage DOM node refs of multiple elements.
* Ensures refs remain stable even with re-renders. Use custom keys if the rendering order may change.
*/
export const useElementsRef = <T extends HTMLElement = HTMLElement>(): UseElementsRefResult<T> => {
const elementRefs = useRef({
current: {} as { [K: string | number]: T },
callbacks: {} as Record<string | number, (el?: T) => void>,
})
// Resets index on every render; stable refs as long as the render order remains unchanged
let currentIndex = 0
return [
elementRefs.current,
(customKey) => {
const { current: elements, callbacks } = elementRefs.current
const autoKey = currentIndex++
const key = customKey ?? autoKey
// Memoize callback for stable ref assignment
if (!callbacks[key]) {
callbacks[key] = (el) => {
// React is setting the ref for this node
if (el != null) {
elements[key] = el
return
}
// React is clearing the node ref
delete elements[key]
}
}
return callbacks[key]
},
]
}
```
### Demo
<!-- begin snippet: js hide: false console: true babel: true babelPresetReact: true babelPresetTS: true -->
<!-- language: lang-js -->
const { render } = ReactDOM
const { useEffect, useRef, useState } = React
const useElementsRef = () => {
const elementRefs = useRef({
current: {},
callbacks: {},
})
let currentIndex = 0
return [
elementRefs.current,
(customKey) => {
const { current: elements, callbacks } = elementRefs.current
const autoKey = currentIndex++
const key = customKey ?? autoKey
// Memoize callback for stable ref assignment
if (!callbacks[key]) {
callbacks[key] = (el) => {
// React is setting the ref for this node
if (el != null) {
elements[key] = el
return
}
// React is clearing the node ref
delete elements[key]
}
}
return callbacks[key]
},
]
}
const App = () => {
const [elementsRef, createElementRef] = useElementsRef()
const [third, setThird] = useState(false)
useEffect(() => {
console.log(elementsRef.current)
}, [third])
return (
<div>
<div ref={createElementRef()}>
<button ref={createElementRef()} onClick={() => setThird(!third)}>toggle third div</button>
</div>
<div ref={createElementRef('custom-key')}>another div</div>
{ third && <div ref={createElementRef('third-div')}>third div</div>}
</div>
);
}
render(<App />, document.getElementById("root"))
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<!-- end snippet -->