# 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]));
});
```