ylem
ylem copied to clipboard
Proposal: Support Hooks
Hooks is direction that React is going, regardless of any issues that some of us might have with them.
The API inspiration for our current project comes from recompose and redux; the recompose library is being discontinued in favor of hooks, and one of the primary redux developers was on the hooks project in order create redux-replacement hooks like useReducer
. While redux is not being discontinued, it is no longer necessary. For this reason, I propose that we release ylem v3 that primarily supports hooks, though old apis are maintained and decprecated at ylem/legacy
.
After reviewing how hooks work, I think doing this would greatly simplify the code base, as we are no longer mucking with React internals. It might be possible to support a similar api as before, though it would remove a lot of these improvements.
Ylem will only have a future if it supports hooks. To that end, there are two decisions that need to be made regarding hook implementation.
Providing the Store
The Store
class must be provided to the hook in some manner. This can either be a 1 step process or a 2 step process:
1 step
The benefit here is simplicity: It is a single line.
function Foo() {
const store = useYlemStore(Store);
return (<div>{store.name}</div>);
}
2 step
This separates the above line into two line. The first could be in the component, or it could be exported by the Store file itself (asa import { useStore } from './store'
). In that case, the benefit is that all ylem-specific code is nowhere near the component; the component just gets data from a hook and doesn't need to know how it works.
const useStore = createStoreHook(Store);
function Foo() {
const store = useStore();
return (<div>{store.name}</div>);
}
Providing the Data
Data will need to be provided to ylem, for initializing and updating the store; this will most likely come from props
. For simplicity, I will assume option 2 above.
props
I propose that the primary method is for the hook to take props. In lieu of other configuration, the props would be used for initializing the store and changes to props will be diffed and those patches will be applied to the store.
function Foo(props) {
const store = useStore(props);
return (<div>{store.name}</div>);
}
As is the standard with useEffect
and other hooks, we should accept a second argument that is used for validating if the store needs to be updated. This can simply be proxied to the underlying useEffect
call, so it should require very little work on our end.
function Foo(props) {
const store = useStore(props, [ props.name ]);
return (<div>{store.name}</div>);
}
Function
I propose that the same hook can also take a function. This function would be called with the current store instance. This function can do some combination of three things:
- Modify the store instance.
const store = useStore((store) => { store.name = props.name; })
- Return a plain object, which will be diffed and patched as if that object were passed directly (see above section).
const store = useStore(() => ({ data: props }))
- Return a promise that resolves to a plain object, it will behave as the object does above. This option will allow us to tie into react's upcoming suspense feature.
Additional Pieces
Observing without creating a store
Whether or not this is exposed to user-land, there will be a hook which just handles the observing/rerendering. This takes no data and returns no data. Is there a use case for exposing this?
function Foo() {
useYlem();
return (<div>Hello</div>);
}
Model Provider
Many react projects make use of providers, allow some piece of information to be defined at a level higher than it is consumed. One possibility in our case is to have a ModelProvider
.
It would look something like this, allowing one to use a string in place of a store constructor. This would work with either method of providing the store constructor.
function Foo() {
return (
<ModelProvider MyStore={MyStore}>
<Bar />
</ModelProvider>
);
}
function Bar() {
const store = useYlemStore('MyStore');
return (<div>{store.name}</div>);
}
function Bar() {
const Store = useModel('MyStore');
// do something with Store
return (<div>{store.name}</div>);
}