Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useStore Hooks? #136

Open
thadeu opened this issue Jan 31, 2019 · 19 comments
Open

useStore Hooks? #136

thadeu opened this issue Jan 31, 2019 · 19 comments

Comments

@thadeu
Copy link

thadeu commented Jan 31, 2019

/**
 * Available methods
 *
 * value
 * isValue
 * setValue({ item: value })
 * withValue({ item: value })
 *
 * @param {*} key
 */
export function useStore(key) {
  let camelKey = _.camelCase(key)
  let setKey = _.camelCase(`set_${key}`)
  let isKey = _.camelCase(`is_${key}`)
  let withKey = _.camelCase(`with_${key}`)

  let selected = store.getState()[camelKey]

  function setter(value) {
    if (typeof value === 'function') {
      const newValue = value(selected)

      return store.setState({
        [camelKey]: { ...selected, ...newValue }
      })
    }

    return store.setState({
      [camelKey]: { ...selected, ...value }
    })
  }

  let getter = selected

  return {
    [camelKey]: getter,
    [isKey]: getter,
    [setKey]: setter,
    [withKey]: setter
  }
}

Used

import { useStore } from 'unistore'

export function disconnected() {
  log('Ably Events | event: disconnected')

  const { setAblySocket } = useStore('ablySocket')
  setAblySocket({ status: 'disconnected' })

  const { emitter } = useStore('emitter')
  emitter.emit('ably', { payload: 'disconnected' })
}

@developit what do you think about this?

@thadeu thadeu changed the title Hooks? useStore Hooks? Jan 31, 2019
@thadeu
Copy link
Author

thadeu commented Feb 1, 2019

/**
 * 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)

@developit
Copy link
Owner

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

@developit
Copy link
Owner

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>
}

@dy
Copy link

dy commented Apr 2, 2019

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.

@dy dy mentioned this issue Apr 3, 2019
@thadeu
Copy link
Author

thadeu commented Apr 9, 2019

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?

@DonnieWest
Copy link

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

@jahredhope
Copy link

jahredhope commented Jun 22, 2019

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.

@yuqianma
Copy link

yuqianma commented Jul 6, 2019

@jahredhope You would be interested in https://github.com/facebookincubator/redux-react-hook

@jahredhope
Copy link

jahredhope commented Jul 6, 2019

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

  • Issues with the connect statement causing re-renders on irrelevant state changes, I think I need to work on the equality comparison.
  • useSelector doesn't accept an equality function. Wanted to get an MVP working first.

Though the big win for me is the TypeScript type inference.
State param's type is inferred in your actions:
Screen Shot 2019-07-07 at 9 16 58 am
And actions automatically have their parameters Types inferred:
Screen Shot 2019-07-07 at 9 13 35 am

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.

@dan-lee
Copy link
Contributor

dan-lee commented Aug 8, 2019

Would be lovely to get this in. I love this little library, but it's definitely missing support for hooks

@mihar-22
Copy link

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?

@developit
Copy link
Owner

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

@mihar-22
Copy link

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.

@dkourilov
Copy link

dkourilov commented Nov 12, 2019

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?

@mihar-22
Copy link

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 :)

@dan-lee
Copy link
Contributor

dan-lee commented Apr 30, 2020

Are there any plans to go forward with official unistore react/preact hooks?

@khuyennguyen7007
Copy link

Hope to see unistore and preact-hooks-unistore in a united package soon.

@danielweck
Copy link

@tomByrer
Copy link

@developit ’s WIP gist: https://gist.github.com/developit/ecd64416d955874c3426d4582045533a

Wouldn't install on Win10 for me :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests