Add useRememberedState(). (#212)
This adds a new `useRememberedState()` React hook that effectively allows us to have user interface elements that "remember" their most recent value, even if the UI itself was unmounted at some point. (It only works for the lifetime of the page, however, so it doesn't remember values across page reloads; this is intentional, as I didn't want to have to worry about serialization or schema migration.) The hook is now used in the randomizer widget and for some of the creature page widgets, to ensure that their settings are preserved more often than not.pull/215/head
rodzic
beb12345a1
commit
ebd8ad0493
|
@ -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<CreatureDesign>
|
||||
> = ({ defaults, onChange }) => {
|
||||
const svgRef = useRef<SVGSVGElement>(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<SvgSymbolData>(
|
||||
const [alwaysInclude, setAlwaysInclude] = useRememberedState<SvgSymbolData>(
|
||||
"creature-page:alwaysInclude",
|
||||
EMPTY_SVG_SYMBOL_DATA
|
||||
);
|
||||
const design: CreatureDesign = useMemo(
|
||||
|
|
|
@ -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<RandomizerWidgetProps> = (props) => {
|
||||
type RandType = "colors" | "symbols" | "colors and symbols";
|
||||
const [paletteAlg, setPaletteAlg] = useState<RandomPaletteAlgorithm>(
|
||||
DEFAULT_RANDOM_PALETTE_ALGORITHM
|
||||
const [paletteAlg, setPaletteAlg] =
|
||||
useRememberedState<RandomPaletteAlgorithm>(
|
||||
"randomizer-widget:paletteAlg",
|
||||
DEFAULT_RANDOM_PALETTE_ALGORITHM
|
||||
);
|
||||
const [randType, setRandType] = useRememberedState<RandType>(
|
||||
"randomizer-widget:randType",
|
||||
"colors and symbols"
|
||||
);
|
||||
const [randType, setRandType] = useState<RandType>("colors and symbols");
|
||||
const randomize = () => {
|
||||
if (randType === "colors" || randType === "colors and symbols") {
|
||||
props.onColorsChange(createRandomCompositionColors(paletteAlg));
|
||||
|
|
|
@ -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<string, any>();
|
||||
|
||||
/**
|
||||
* 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<S>(
|
||||
key: string,
|
||||
initialState: S | (() => S)
|
||||
): [S, (value: S) => void] {
|
||||
const remembered = shortTermMemory.get(key);
|
||||
const [value, rawSetValue] = useState<S>(
|
||||
remembered === undefined ? initialState : remembered
|
||||
);
|
||||
const setValue = useCallback(
|
||||
(value: S) => {
|
||||
shortTermMemory.set(key, value);
|
||||
rawSetValue(value);
|
||||
},
|
||||
[key, rawSetValue]
|
||||
);
|
||||
|
||||
return [value, setValue];
|
||||
}
|
Ładowanie…
Reference in New Issue