Open thadeu opened 5 years ago
/**
* const [loggedIn, setLoggedIn] = useHookStore('loggedIn')
* console.log(loggedIn)
* @param {*} props
*/
export function useHookStore(prop) {
const selected = store.getState()[prop]
const setter = value => store.setState({ [prop]: { ...selected, ...value } })
return [selected, setter]
}
example:
import { useHookStore } from 'unistore'
const [ablySocket, setAblySocket] = useHookStore('ablySocket')
console.log(ablySocket)
I like it! It'd be a little unfortunate to drop the action binding stuff though. Maybe something like this?
import { createContext } from 'preact';
import { useState, useContext, useMemo, useEffect } from 'preact/hooks';
const StoreContext = createContext(store);
export const Provider = StoreContext.Provider;
function runReducer(state, reducer) {
if (typeof reducer==='function') return reducer(state);
const out = {};
if (Array.isArray(reducer)) for (let i of reducer) out[i] = state[i];
else if (reducer) for (let i in reducer) out[i] = state[reducer[i]];
return out;
}
function bindActions(store, actions) {
if (typeof actions=='function') actions = actions(store);
const bound = {};
for (let i in actions) bound[i] = store.action(actions[i]);
return bound;
}
export function useStore(reducer, actions) {
const { store } = useContext(StoreContext);
const [state, set] = useState(runReducer(store.getState(), reducer));
useEffect(() => store.subscribe(state => {
set(runReducer(state, reducer));
}));
const boundActions = useMemo(bindActions, [store, actions]);
return [state, boundActions];
}
Usage:
import { Provider, useStore } from 'unistore/preact/hooks';
const ACTIONS = {
add(state) {
return { count: state.count + 1 };
}
};
function Demo() {
const [state, actions] = useStore(['count'], ACTIONS);
return <button onclick={actions.add}>{state.count}</button>
}
render(<Provider value={store}><Demo /></Provider>, root);
Another neat alternative would be to split the hooks into useStoreState()
and useActions()
:
const ACTIONS = {
add: ({ count }) => ({ count: count + 1 })
};
function Demo() {
const [count] = useStoreState(['count']);
const add = useActions(ACTIONS.add);
// or to bind them all:
const { add } = useActions(ACTIONS);
return <button onclick={add}>{count}</button>
}
That pattern would save some levels of nested components. @developit any plans on creating them? I'm right in the situation of refactoring App with hooks, that'd be a great help. I can come up with PR I guess.
I like it! It'd be a little unfortunate to drop the action binding stuff though. Maybe something like this?
import { createContext } from 'preact'; import { useState, useContext, useMemo, useEffect } from 'preact/hooks'; const StoreContext = createContext(store); export const Provider = StoreContext.Provider; function runReducer(state, reducer) { if (typeof reducer==='function') return reducer(state); const out = {}; if (Array.isArray(reducer)) for (let i of reducer) out[i] = state[i]; else if (reducer) for (let i in reducer) out[i] = state[reducer[i]]; return out; } function bindActions(store, actions) { if (typeof actions=='function') actions = actions(store); const bound = {}; for (let i in actions) bound[i] = store.action(actions[i]); return bound; } export function useStore(reducer, actions) { const { store } = useContext(StoreContext); const [state, set] = useState(runReducer(store.getState(), reducer)); useEffect(() => store.subscribe(state => { set(runReducer(state, reducer)); })); const boundActions = useMemo(bindActions, [store, actions]); return [state, boundActions]; }
Usage:
import { Provider, useStore } from 'unistore/preact/hooks'; const ACTIONS = { add(state) { return { count: state.count + 1 }; } }; function Demo() { const [state, actions] = useStore(['count'], ACTIONS); return <button onclick={actions.add}>{state.count}</button> } render(<Provider value={store}><Demo /></Provider>, root);
That pattern wouldn't support to preact 8.x right? so, we should be find solution for all versions, or something that work to separated versions.
@developit you have any ideia about it?
I was playing around with this tonight and came up with #153
I still can't figure out why I have ONE test failing, but it retains all the previous APIs while also giving us a hook and nearly all previous tests passing. Heavily inspired by @thadeu to make something that will feel familiar to anyone who used the old API
Only downfall is that it will be a breaking change since it uses the new Context API
Feedback welcome
I'd like to add a suggestion about the API.
Pre React Hooks it was typical to use a single connect command for a component, with multiple selectors and multiple actions per connect.
I'm not sure this behaviour should carry over to hooks. Consumers can now use multiple hooks in a component. And this could be made possible by separating out selecting content from the store, useSelector
, and creating an action, useAction
.
With connect statement you might use something like:
const mapStateToProps = state => ({user: getUserInfo(state), books: getBooks(state)})
const actions = [addBook, removeBook]
connect(mapStateToProps, actions)
With Hooks you'd be able to pull these out to seperate commands:
const userInfo = useSelector(selectUserInfo)
const books = useSelector(selectBooks)
const add = useAction(addBook)
const remove = useAction(removeBook)
This potentially allows you to pull out common hooks such as useSelectUserInfo
that might be used in multiple places.
@jahredhope You would be interested in https://github.com/facebookincubator/redux-react-hook
Exactly @yuqianma . Aligning the API with Redux will help people moving between the frameworks.
The only difference is what consumers are passing to useAction
.
I had a crack at implementing it when I commented above, just a WIP: https://gist.github.com/jahredhope/c0d490ec2c58aa45efd11d138b72d9ff
Though the big win for me is the TypeScript type inference. State param's type is inferred in your actions:
And actions automatically have their parameters Types inferred:
Update: Working on an implementation here: https://github.com/developit/unistore/compare/master...jahredhope:add-hooks?expand=1 Which comes from a standalone implementation here: https://github.com/jahredhope/react-unistore I've just been testing the standalone implementation in some apps and seems to be working well.
Would be lovely to get this in. I love this little library, but it's definitely missing support for hooks
I created some hooks for Unistore based heavily on react-redux and redux-zero.
Repo: https://github.com/mihar-22/preact-hooks-unistore
I'd love some feedback and maybe integrate it into Unistore?
@mihar-22 looks really good. The only change I'd like to see in your implementation is to allow passing selector values to useSelector()
rather than just functions. You can import { select } from 'unistore/src/utils.js'
or just copy it over.
All done @developit. I can keep it as a separate package or I can make a PR to pull it in and we can add it to src/hooks
? Either way no problemo.
Hey @mihar-22, I was following this thread and tried to use your proposal in my typical dashboard app that I'm porting from another framework.
Apparently, I don't see how useActions
hook must be used with async functions. The bound value gets resolved to a listener only when the modified state is returned from hook function. Do you have any recommended usage pattern in mind?
Hey @dkourilov, if you can raise the issue over at https://github.com/mihar-22/preact-hooks-unistore then I can try and help when I have a moment. If you can provide a little bit more information that'd help, thanks :)
Are there any plans to go forward with official unistore react/preact hooks?
Hope to see unistore and preact-hooks-unistore in a united package soon.
@developit ’s WIP gist: https://gist.github.com/developit/ecd64416d955874c3426d4582045533a
@developit ’s WIP gist: https://gist.github.com/developit/ecd64416d955874c3426d4582045533a
Wouldn't install on Win10 for me :/
Used
@developit what do you think about this?