Created
December 11, 2023 19:08
-
-
Save nyteshade/aa3d265ec2327f60d09616113a159a1d to your computer and use it in GitHub Desktop.
Experimentation with a non-react specific useState approximation
This file contains 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
/** | |
* The function `usePseudoState` is a custom hook that allows for the creation of | |
* pseudo state variables in React. When `useState` becomes available, it should be | |
* applied to this object output by calling the `init` method with the `useState` | |
* function. | |
* | |
* The object returned from this function invocation | |
* | |
* @param initialValue - The `initialValue` parameter is the initial value that | |
* will be assigned to the pseudo state. | |
* @returns The function `usePseudoState` returns an object with several properties | |
* and methods. | |
*/ | |
export | |
function usePseudoState( | |
initialValue, | |
{ reactUseStateFn, mockState } = { reactUseStateFn: undefined, mockState: false } | |
) { | |
const mockedSymbol = Symbol.for('usePseudoState::mocked'); | |
const pseudoState = { | |
// Tries to fetch the first value from the reactState current value, if the | |
// react state is not initialized then it will return the initialValue | |
get() { return this.reactState ? this.reactState?.[0] : this.initialValue }, | |
// Tries to set the first value of the reactState current value, if the react | |
// state is not initialized then it will do nothing | |
set(value) { this.reactState?.[1](value) }, | |
// The initial value of the pseudo state, also applied to react state if | |
// init() is called on this object | |
initialValue, | |
// The react useState hook values. If reactUseStateFn is provided, it will | |
// be initialized immediately. Otherwise it allows for lazy initialization | |
// using the init method. | |
reactState: undefined, | |
// Provided a useState function, from within a React component, the internal | |
// reactState will be initialized and the pseudoState will be ready for use | |
init(useStateFn) { | |
const value = this.reactState?.[0] ?? this.initialValue | |
this.reactState = useStateFn(value) | |
}, | |
// Returns true if the reactState is initialized, false otherwise | |
get valid() { return this.reactState !== undefined }, | |
// Returns true if the reactState is valid, but is also mocked | |
get mocked() { return this.valid && this.reactState[mockedSymbol] === true }, | |
// The toStringTag property is used by the Object.prototype.toString() method | |
// indicating that this object is a UsePseudoState object | |
get [Symbol.toStringTag]() { return 'UsePseudoState' }, | |
}; | |
// If the reactUseStateFn is provided, then the pseudoState will be initialized | |
if (reactUseStateFn && (typeof reactUseStateFn === 'function')) { | |
pseudoState.init(reactUseStateFn) | |
} | |
// The `if (mockState)` block is used to simulate the behavior of React's | |
// `useState` hook when it is not available. It is used for testing purposes | |
// only. | |
if (mockState) { | |
pseudoState.reactState = [ | |
initialValue, | |
(newValue) => { pseudoState.reactState[0] = newValue } | |
] | |
pseudoState.reactState[mockedSymbol] = true | |
} | |
// Any reference to .length will still show three elements, even though we are going | |
// get tricky with the first element | |
const simulatedLiveState = [undefined, pseudoState.set.bind(pseudoState), pseudoState] | |
// Secretly maps the first element of the array to the pseudoState.get() method | |
// and any attempt to set the first element of the array to the pseudoState.set() | |
Object.defineProperty(simulatedLiveState, '0', accessorDescriptor( | |
()=>pseudoState.get(), | |
(value)=>pseudoState.set(value) | |
)) | |
return simulatedLiveState | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment