Created
August 6, 2021 16:24
-
-
Save nfarina/b733842e1541008634541b0503c882b4 to your computer and use it in GitHub Desktop.
A version of React's `useState` that resets the value to initial whenever the given dependency array changes. Very helpful when you need to reset some internal state as the result of getting new props.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { DependencyList, Dispatch, SetStateAction, useState } from "react"; | |
/** | |
* This is like useState() but with the added feature of returning the initial | |
* value whenever the dependency list changes. This is super useful for allowing | |
* components to "reset" some internal state as a result of getting new props. | |
*/ | |
export function useResettableState<S>( | |
initial: S | (() => S), | |
deps: DependencyList, | |
): [S, Dispatch<SetStateAction<S>>] { | |
const [innerValue, setInnerValue] = useState(initial); | |
const [prevDeps, setPrevDeps] = useState(deps); | |
// If the deps changed, reset our state to initial. | |
// Calling setState during render is rare but supported! | |
// https://github.com/facebook/react/issues/14738#issuecomment-461868904 | |
if (depsChanged(deps, prevDeps)) { | |
setPrevDeps(deps); | |
setInnerValue(initial); | |
} | |
return [innerValue, setInnerValue]; | |
} | |
function depsChanged( | |
previous: DependencyList | undefined, | |
current: DependencyList | undefined, | |
): boolean { | |
if (previous === undefined && current === undefined) return false; | |
if (previous === undefined || current === undefined) return true; | |
if (previous.length !== current.length) { | |
console.error( | |
"useResettableState(): Dependency array size changed between renders!", | |
); | |
return false; | |
} | |
// Lengths are the same; must compare values. | |
for (let i = 0; i < previous.length; i += 1) { | |
if (previous[i] !== current[i]) { | |
return true; | |
} | |
} | |
// Unchanged! | |
return false; | |
} |
Yes I remember considering this at first as well but there was some pitfall (that I should have documented) - if you encounter it please let me know so I can note it here!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For your information, this is what I finally came to, a twisted version of yours:
This way, immediately it resets, not at the 2nd render as I was mentioning.