# Brainstorming of `Closures` ## Problem how to update the Counter ```typescript= import {serverAdd} from './logic'; const Counter = qComponent({ onMount: qHook(() => { return { count: await serverAdd(props.start, 1) } }), onRender: qHook((props) => ( <> <span>{state.count}</span> <button on:click={qHook(() => { state.count = await serverAdd(state.count, 1) })}>+</button> </> )) }) ``` Problems: - `serverAdd` function must be extracted, which imposes extra mental load on the developer. ## `useEffect` ```typescript= import {serverAdd} from './logic'; const CounterEffect = qEffect<{recompute: number}, {value: number}>({...}); const Counter = qComponent<{}, {counterEffect: typeof CounterEffect}>({ onMount: qHook(() => { const state = useState({counter: 0, dirty: number}) state.counterEffect = useEffect(ServerCounter, state, {recompute: 'dirty'}); return state; }), onRender: qHook((props) => ( <> <span>{state.counterEffect.value}</span> <button on:click={qHook(() => { // Invalidate the effect, which will cause a reCompute => onRender. state.dirty++; })}>+</button> </> )) }) ``` ## Unified qHook() semantics Logic within a qHook() should work the same regardess of its context - Right now, onMount qhooks have a lightly different semantic regarding the return value and the props ---- ```typescript= import {serverAdd} from './logic'; const CounterOnMount:OnMount<typeof Counter> = () => { return { count: await serverAdd(props.start, 1) } }; const CounterOnRender:OnRender<typeof Counter> = (props, state) => ( <> <span>{state.count}</span> <button on:click={(props: PropsOf<typeof Counter>, state: StateOf<typeof Counter>, args: {value: number}) => { state.count = await serverAdd(state.count, args.value) }}>+</button> <button on:click={(props, state) => { state.count = await serverAdd(state.count, 1) } as OnEvent<typeof Counter>}>+</button> <button on:click={qHook<typeof Counter>((props, state) => { state.count = await serverAdd(state.count, 1) })}>+</button> <button on:click={() => { state.count = await serverAdd(state.count, 1) }>+</button> </> ); const Counter = qComponent({ onMount: CounterOnMount, onRender: CounterOnRender, }) ``` ```typescript= // Option: A const Counter = qComponent<{value: number}, {count: number}>({ onMount: (props) => ({count: props.value}), onRender: (props, state) => ( <> <span>{state.count}</span> <button on:click={() => state.count++}>+</button> </> ) }) // Option: B const Counter = qComponent(function(props: {value: number}, state: {count: number}) { onMount(() => ({count: props.value})); onRender(() => ( <> <span>{state.count}</span> <button on:click={() => state.count++}>+</button> </> )); }); // Option: C // Core hooks - useState() -_saveState() - on(event, fn) => - onResume => on('qResume', fn); - onUnmount => on('qUnmount', fn); - useAnyWatch() // Rerun when any state changes (fine for Render()) - useSmartWatch() // Rerun when the watched state changes (what useEffect() needs ) - onRender() - useEffect() - useMemo() // computed value function useMemo(computeFn, args) { const state = useState({value: undefined}); useWatch(() => { state.foo }) useEffect(() => { state.value = computeFn(..args); }, args) return state: } function useCounter = () => { const state = useState({count: 0}); let intervalId; onResume(() => { intervalId = setInterval(() => { state.count++; }, 1000); }); onUnmount(() => { intervalId && intervalId = clearInterval(intervalId); }); return state; } function useCounterV2 = () => { const state = useState({count: 0}); let intervalId; onResume(() => { intervalId = setInterval(() => { state.count++; }, 1000); }); onUnmount(() => { intervalId && intervalId = clearInterval(intervalId); }); return state; } const Counter = qComponent(function({value}: {value: number, increment: number}) { const state = useState({count: props.value}); const foo = foo() // using useState() internally; const state = useState({count: 0}); const bar = useCounter(); // Issue what if we call helper function `foo` to invoke onRender? // (We could look at stacktrace and dissallow it.) onRender(() => ( <> <span>{state.count}{props.value}</span> {buttons.map((btn, index) => ( <button on:click={() => state.count += index}>{index}</button> <button on:click={((i) => state.count += i}).bind(i)>{index}</button> <button {...on('click', index, (idx) => state.count+= idx)}>{index}</button> ))} </> )); }); // After optimizer. const Counter = qComponent({ onMount: qHook(() => { const state = useState({count: props.value}); const foo = foo(); // Compiler generates: _qSaveState(':state', state); _qSaveState(':foo', foo); const bar = useEffect(() => { async function doStuff() { await fetch() }; const state = _useState(':state'); const foo = _useState(':foo'); doStuff(state.count + foo.value); }); _qSaveState(':bar', bar); }), onRender: qHook(() => { const props = _useProps(); const state = _useState(':state'); const foo = _useState(':foo'); return ( <> <span>{state.count}{props.value}</span> <button on:click={qHook(() => { const state = useState(':state'); state.count++; }>+</button> </> ); }); }) // After optimizer v2 const Counter = qComponent({ onMount: qHook(() => { const state = useState({count: props.value}); const foo = foo(); // Compiler generates: _qSaveState(':state', state); _qSaveState(':foo', foo); const bar = useEffect(() => { async function doStuff() { await fetch() }; const state = _useState(':state'); const foo = _useState(':foo'); doStuff(state.count + foo.value); }); _qSaveState(':bar', bar); }), onRender: qHook((props, state, foo) => { return ( <> <span>{state.count}{props.value}</span> {buttons.map((btn, index) => ( <button on:click={qHook((state, index) => state.count += index}).with(state, index)>{index}</button> ))} <button on:click={qHook(() => { const state = useState(':state'); state.count++; }>+</button> </> ); }).with(':s:state', ':s:foo', ':p') }) // Option: D const Counter = qComponent(function(props: {value: number, increment: number}) { // Issue what if we call helper function `foo` to create state? // (We could look at stacktrace and dissallow it.) const state = useState({count: props.value}); const fooState = foo(); // Issue what if we call helper function `foo` to invoke onRender? // (We could look at stacktrace and dissallow it.) useRender(qHooo() => ( <> <span>{state.count}</span> <button on:click={click(state, () => state.count++)}>+</button> <button on:click={() => { fooState.count++; }>+</button> <button on:click={with( props.increment, (increment: number) => { const state = useState(0); state.count += increment })}>+</button> </> )); }); ``` --- ```typescript= const Counter = qComponent((props: {}) =>{ const state = useState({count: 0}); onRender(() => ( <> <span>{state.count}</span> {btns.map((btn) => ( <button on:click={() => state.count += btn.delta}>{btn.name}</button> ))} </> )); }); ``` ```typescript= const CounterOnRender = (state) => { return ( <> <span>{state.count}</span> {btns.map((btn) => ( <button on:click={qHook("./single#onClick", "onClick", [state, btn])}> {btn.name} </button> ))} </> ); } const onClick = (state, btn) => { state.count += btn.delta; }); const Counter = qComponent(function (props){ let state = useState({count: 0}); onRender(qHook("./single#CounterOnRender", "CounterOnRender", [state])); }); ```