Overview of Cotton Box state management
Cotton Box is a minimal state management library designed for React and other environments.
website·Indexed 30 days ago
https://glyph-cat.github.io/cotton-boxA minimal state management library for React and beyond. It provides a suite of state managers including SimpleStateManager, AsyncStateManager, SimpleFiniteStateManager, and the base StateManager class, along with utilities like AsyncSetStateFn.
InvalidStateTransitionError is thrown when an illegal transition occurs between two states. It provides details about the source state, target state, and the name of the state manager involved.// Constructor: new InvalidStateTransitionError(fromState: string, toState: string, stateManager?: string)
try {
// transition logic
} catch (e) {
if (e instanceof InvalidStateTransitionError) {
console.error(`Error in ${e.stateManagerName}: ${e.fromState} -> ${e.toState}`);
}
}StateManager<State> class provides a way to manage application state with support for asynchronous initialization. It extends SimpleStateManager and adds an isInitializing property to track the initialization status.const ExampleState = new StateManager('initial_value', { name: 'myStateManager' });useRef for lazy initialization can be unstable in StrictMode because effects may fire multiple times before the first cleanup occurs. The safe and stable approach is to perform both instantiation and cleanup within a single useEffect and manage the instance via useState. This requires a fallback UI (e.g., a loading state) since the state manager will not be available on the first render.function App(): ReactNode {
const [ExampleState, setExampleState] = useState<StateManager<string>>(null)
useEffect(() => {
const newExampleState = new StateManager<string>('...')
setExampleState(newExampleState)
return () => {
newExampleState.dispose()
setExampleState(null)
}
}, [])
if (!ExampleState) { return <>Loading...</> }
console.log(ExampleState.get())
return '...'
}allowedStateTransitions array, an InvalidStateTransitionError is thrown.class PlayableCharacter {
readonly state = new SimpleFiniteStateManager('idle', [
['idle', 'walking'],
['idle', 'attacking'],
['walking', 'idle'],
['attacking', 'idle'],
], {
name: 'PlayableCharacter',
})
}
const newCharacter = new PlayableCharacter();
newCharacter.set('walking'); // Valid: idle -> walking
newCharacter.set('attacking'); // Invalid: walking -> attacking (throws InvalidStateTransitionError)npm install cotton-box-core # Replace with actual package name from DocConstants.CORE_PACKAGE_NAME<!-- UNPKG Development --> <script src="https://www.unpkg.com/cotton-box-core@<VERSION>/dist/umd/index.js" crossorigin></script>
<!-- UNPKG Production --> <script src="https://www.unpkg.com/cotton-box-core@<VERSION>/dist/umd/index.min.js" crossorigin></script>
.dispose() method to clean up StateManagers that are not global. It is recommended to wait for initialization to complete using .wait() before performing teardown.class GameRoom {
state = new StateManager<GameRoomState>(GameRoomState.CREATED);
async dispose(): Promise<void> {
// Wait for initialization to complete before teardown
await this.state.wait((state) => state >= GameRoomState.INITIALIZED);
this.state.set(GameRoomState.DISPOSED);
this.state.dispose();
}
}SimpleStateManager<State> class is used to manage a state value. It requires an initial default state and an optional options object.const ExampleState = new SimpleStateManager('initial value', { name: 'MyStateManager' });name property is optional and used for debugging. The lifecycle object allows defining init, didSet, and didReset hooks. visibility can be set using StateManagerVisibility.ENVIRONMENT.const ExampleState = new AsyncStateManager('defaultState', {
name: 'example-state',
lifecycle: {
init({ commit, commitNoop, defaultState }) {
// Initialization logic
},
didSet({ state, defaultState }) {
// Logic after state is set
},
didReset() {
// Logic after state is reset
},
},
visibility: StateManagerVisibility.ENVIRONMENT,
suspense: true,
});init method allows you to perform asynchronous setup (e.g., loading from localStorage) and commit the resulting value to the state. It takes an initialization function that receives StateManagerInitArgs.await ExampleState.init(({ commit }) => {
const someValue = localStorage.get('some-value');
commit(someValue);
});Core methods .get, .set, .reset, and .dispose remain unchanged. Key changes include:
RelinkSource is replaced by AsyncStateManager or StateManager.default property in the options object.key is replaced by an optional name property.visibility is now a boolean/enum instead of a simple boolean.deps array is replaced by manual awaiting of .isInitializing.wait(false) in the init hook.hydrate(...) is replaced by .init(...).useRelinkValue is replaced by useStateValue.