State snapshotting and restoring
The @hookstate/snapshotable
extension allows to take state stanshots and restore from it.
Whole state modified (vs the default snapshot)?: false
Whole state modified (vs the second snapshot)?: false
Rollback to:
Capture as:
Modified (vs the default snapshot)?: false
Modified (vs the second snapshot)?: false
Rollback to:
Capture as:
Modified (vs the default snapshot)?: false
Modified (vs the second snapshot)?: false
Rollback to:
Capture as:import React from 'react';import { useHookstate, State, extend, InferStateExtensionType } from '@hookstate/core';import { identifiable } from '@hookstate/identifiable';import { Clonable, clonable } from '@hookstate/clonable';import { Comparable, comparable } from '@hookstate/comparable';import { Initializable, initializable } from '@hookstate/initializable';import { Snapshotable, snapshotable } from '@hookstate/snapshotable';import { logged } from '@hookstate/logged';// define hookstate extension which is composed of a number of standard extensionsfunction extensions<S, E>() {return extend<S, E, Clonable, Comparable, Snapshotable<'second'>, Initializable>(clonable(// basic cloning, you may use lodash deep clone, instead// or a custom cloner, if there a state holds// values of custom classesv => (v === undefined) ? undefined : JSON.parse(JSON.stringify(v))),comparable(// basic comparison, you may use lodash comparison, instead// or a custom compare implementation, if there a state holds// values of custom classes(v1, v2) => {// String is to handle v1 === undefinedreturn String(JSON.stringify(v1)).localeCompare(JSON.stringify(v2))}),// if generic type is ommited, the snapshot key can be any string// otherwise, only those names which are providedsnapshotable<'second'>({// optionally add extensions to snapshot statessnapshotExtensions: (key) => extend(identifiable(key), logged())}),initializable((s) => {// optional one off initialization after a state is createds.snapshot() // take the initial snapshot with default keys.snapshot('second') // take the initial snapshot with 'second' key}))}// inter type defintion of extension methodstype Extended = InferStateExtensionType<typeof extensions>export const ExampleComponent = () => {const state = useHookstate(['First Task', 'Second Task'],extensions())return <><ModifiedStatus state={state} />{state.map((taskState, taskIndex) =><TaskEditor key={taskIndex} taskState={taskState} />)}<p><button onClick={() => state.merge(['Untitled'])}>Add task</button></p></>}function TaskEditor(props: { taskState: State<string, Extended> }) {const state = useHookstate(props.taskState);// subscribe to snapshot updates, so modified() result is renrender when snapshot is changedconst defaultSnapshot = useHookstate(state.snapshot(undefined, 'lookup')).get({ noproxy: true })const secondSnapshot = useHookstate(state.snapshot('second', 'lookup')).get({ noproxy: true })return <p><inputvalue={state.get()}onChange={e => state.set(e.target.value)}/><br/>Modified (vs the default snapshot)?: {state.modified().toString()} <br/>Modified (vs the second snapshot)?: {state.modified('second').toString()} <br/>Rollback to:<button disabled={defaultSnapshot === undefined} onClick={() => state.rollback()}>the <b>default</b> snapshot</button><button disabled={secondSnapshot === undefined} onClick={() => state.rollback('second')}>the <b>second</b> snapshot</button><br />Capture as:<button onClick={() => state.snapshot()}>the <b>default</b> snapshot</button><button onClick={() => state.snapshot('second')}>the <b>second</b> snapshot</button></p>}function ModifiedStatus(props: { state: State<string[], Extended> }) {const state = useHookstate(props.state)// subscribe to snapshot updates, so modified() result is renrender when snapshot is changeduseHookstate(state.snapshot(undefined, 'lookup')).get({ noproxy: true })useHookstate(state.snapshot('second', 'lookup')).get({ noproxy: true })return <p>Whole state modified (vs the default snapshot)?: {state.modified().toString()} <br/>Whole state modified (vs the second snapshot)?: {state.modified('second').toString()} <br />Rollback to:<button onClick={() => state.rollback()}>the <b>default</b> snapshot</button><button onClick={() => state.rollback('second')}>the <b>second</b> snapshot</button><br />Capture as:<button onClick={() => state.snapshot()}>the <b>default</b> snapshot</button><button onClick={() => state.snapshot('second')}>the <b>second</b> snapshot</button></p>;}