diff --git a/lib/pages/creature-page/core.tsx b/lib/pages/creature-page/core.tsx index facb0d9..66b2dcc 100644 --- a/lib/pages/creature-page/core.tsx +++ b/lib/pages/creature-page/core.tsx @@ -42,6 +42,7 @@ import { VocabularyWidget } from "../../vocabulary-widget"; import { createDistribution } from "../../distribution"; import { ComponentWithShareableStateProps } from "../../page-with-shareable-state"; import { useDebouncedEffect } from "../../use-debounced-effect"; +import { useRememberedState } from "../../use-remembered-state"; /** * The minimum number of attachment points that any symbol used as the main body @@ -264,9 +265,15 @@ export const CreaturePageWithDefaults: React.FC< ComponentWithShareableStateProps > = ({ defaults, onChange }) => { const svgRef = useRef(null); - const [randomlyInvert, setRandomlyInvert] = useState(true); + const [randomlyInvert, setRandomlyInvert] = useRememberedState( + "creature-page:randomlyInvert", + true + ); const [compCtx, setCompCtx] = useState(defaults.compCtx); - const [complexity, setComplexity] = useState(INITIAL_COMPLEXITY_LEVEL); + const [complexity, setComplexity] = useRememberedState( + "creature-page:complexity", + INITIAL_COMPLEXITY_LEVEL + ); const [creature, setCreature] = useState(defaults.creature); const defaultCtx = useContext(CreatureContext); const newRandomCreature = () => { @@ -286,7 +293,8 @@ export const CreaturePageWithDefaults: React.FC< ...defaultCtx, ...compCtx, }); - const [alwaysInclude, setAlwaysInclude] = useState( + const [alwaysInclude, setAlwaysInclude] = useRememberedState( + "creature-page:alwaysInclude", EMPTY_SVG_SYMBOL_DATA ); const design: CreatureDesign = useMemo( diff --git a/lib/randomizer-widget.tsx b/lib/randomizer-widget.tsx index 03cd583..55ed533 100644 --- a/lib/randomizer-widget.tsx +++ b/lib/randomizer-widget.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { PaletteAlgorithmWidget } from "./palette-algorithm-widget"; import { Random } from "./random"; import { @@ -7,6 +7,7 @@ import { RandomPaletteAlgorithm, } from "./random-colors"; import { SvgCompositionContext } from "./svg-composition-context"; +import { useRememberedState } from "./use-remembered-state"; type SvgCompositionColors = Pick< SvgCompositionContext, @@ -31,10 +32,15 @@ export type RandomizerWidgetProps = { export const RandomizerWidget: React.FC = (props) => { type RandType = "colors" | "symbols" | "colors and symbols"; - const [paletteAlg, setPaletteAlg] = useState( - DEFAULT_RANDOM_PALETTE_ALGORITHM + const [paletteAlg, setPaletteAlg] = + useRememberedState( + "randomizer-widget:paletteAlg", + DEFAULT_RANDOM_PALETTE_ALGORITHM + ); + const [randType, setRandType] = useRememberedState( + "randomizer-widget:randType", + "colors and symbols" ); - const [randType, setRandType] = useState("colors and symbols"); const randomize = () => { if (randType === "colors" || randType === "colors and symbols") { props.onColorsChange(createRandomCompositionColors(paletteAlg)); diff --git a/lib/use-remembered-state.ts b/lib/use-remembered-state.ts new file mode 100644 index 0000000..0dd386a --- /dev/null +++ b/lib/use-remembered-state.ts @@ -0,0 +1,39 @@ +import { useCallback, useState } from "react"; + +/** + * This is where we remember the most recently-set values + * for `useRememberedState`. + */ +const shortTermMemory = new Map(); + +/** + * This is like React's `useState()`, but it also takes a "key" which + * uniquely identifies the state's value globally across the whole + * application. + * + * The most recent value is always remembered for the lifetime of the + * current application (but not across page reloads) and is returned + * instead of the initial state if possible. + * + * This effectively allows us to have user interface elements that + * "remember" their most recent value, even if the UI itself was + * unmounted at some point. + */ +export function useRememberedState( + key: string, + initialState: S | (() => S) +): [S, (value: S) => void] { + const remembered = shortTermMemory.get(key); + const [value, rawSetValue] = useState( + remembered === undefined ? initialState : remembered + ); + const setValue = useCallback( + (value: S) => { + shortTermMemory.set(key, value); + rawSetValue(value); + }, + [key, rawSetValue] + ); + + return [value, setValue]; +}