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
Atul Varma 2021-08-15 15:44:54 -04:00 zatwierdzone przez GitHub
rodzic beb12345a1
commit ebd8ad0493
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 60 dodań i 7 usunięć

Wyświetl plik

@ -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(

Wyświetl plik

@ -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));

Wyświetl plik

@ -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];
}