Skip to content

useStore Hooks? #136

Open
Open

Description

@thadeu
/**
 * 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?

Activity

  1. changed the title [-]Hooks?[/-] [+]useStore Hooks?[/+] on Jan 31, 2019
  2. thadeu commented on Feb 1, 2019

    @thadeu
    Author
    /**
     * 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)
  3. developit commented on Mar 7, 2019

    @developit
    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);
  4. developit commented on Mar 7, 2019

    @developit
    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>
    }
  5. dy commented on Apr 2, 2019

    @dy

    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.

  6. thadeu commented on Apr 9, 2019

    @thadeu
    Author

    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?

  7. DonnieWest commented on Apr 16, 2019

    @DonnieWest

    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

  8. jahredhope commented on Jun 22, 2019

    @jahredhope

    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.

  9. yuqianma commented on Jul 6, 2019

    @yuqianma
  10. jahredhope commented on Jul 6, 2019

    @jahredhope

    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.

  11. dan-lee commented on Aug 8, 2019

    @dan-lee
    Contributor

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

  12. mihar-22 commented on Sep 26, 2019

    @mihar-22

    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?

  13. developit commented on Oct 17, 2019

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

  14. mihar-22 commented on Oct 18, 2019

    @mihar-22

    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.

  15. dkourilov commented on Nov 12, 2019

    @dkourilov

    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?

  16. mihar-22 commented on Nov 12, 2019

    @mihar-22

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

  17. dan-lee commented on Apr 30, 2020

    @dan-lee
    Contributor

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

  18. khuyennguyen7007 commented on May 21, 2020

    @khuyennguyen7007

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

  19. tomByrer commented on Jul 19, 2020

    @tomByrer

    @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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions