Git Product home page Git Product logo

reusable's Introduction

Build Status npm version

Reusable - state management with hooks

  • Use hooks to manage the store
    • One paradigm for both local and shared state, and an easier transition between the two
  • Use a single context provider and avoid nesting dozens of providers
  • Allow direct subscriptions with selectors for better re-render control

How to use

Pass a custom hook to createStore:

const useCounter = createStore(() => {
  const [counter, setCounter] = useState(0);
  useEffect(...)
  const isOdd = useMemo(...);

  return {
    counter,
    isOdd,
    increment: () => setCounter(prev => prev + 1)
    decrement: () => setCounter(prev => prev - 1)
  }
});

and get a singleton store, with a hook that subscribes directly to that store:

const MyComponent = () => {
  const {counter, increment, decrement} = useCounter();
}

const AnotherComponent = () => {
  const {counter, increment, decrement} = useCounter(); // same counter
}

then wrap your app with a provider:

const App = () => (
  <ReusableProvider>
    ...
  </ReusableProvider>
)

Note there is no need to provide the store. Stores automatically plug into the top provider

Selectors

For better control over re-renders, use selectors:

const Comp1 = () => {
  const isOdd = useCounter(state => state.isOdd);
}

Comp1 will only re-render if counter switches between odd and even

useCounter can take a second parameter that will override the comparison function (defaults to shallow compare):

const Comp1 = () => {
  const counter = useCounter(state => state, (prevValue, newValue) => prevValue === newValue);
}

Using stores from other stores

Each store can use any other store similar to how components use them:

const useCurrentUser = createStore(() => ...);
const usePosts = createStore(() => ...);

const useCurrentUserPosts = createStore(() => {
  const currentUser = useCurrentUser();
  const posts = usePosts();
  
  return ...
});

Demos

basic
Edit basic

TodoMVC

Edit basic

How does this compare to other state management solutions?

Current state management solutions don't let you manage state using hooks, which causes you to manage local and global state differently, and have a costly transition between the two.

Reusable solves this by seemingly transforming your custom hooks into global stores.

What about hooks+Context?

Using plain context has some drawbacks and limitations, that led us to write this library:

  • Context doesn't support selectors, render bailout, or debouncing
  • When managing global state using Context in a large app, you will probably have many small, single-purpose providers. Soon enough you'll find a Provider wrapper hell.
  • When you order the providers vertically, you can’t dynamically choose to depend on each other without changing the order, which might break things.

How does it work

React hooks must run inside a component, and our store is based on a custom hook.
So in order to have a store that uses a custom hook, we need to create a "host component" for each of our stores.
The ReusableProvider component renders a Stores component, under which it will render one "host component" per store, which only runs the store's hook, and renders nothing to the DOM. Then, it uses an effect to update all subscribers with the new value. We use plain pubsub stores under the hood, and do shallowCompare on selector values to decide if we re-render the subscribing component or not.

Notice that the ReusableProvider uses a Context provider at the top-level, but it provides a stable ref that never changes. This means that changing store values, and even dynamically adding stores won't re-render your app.

Feedback / Contributing:

We would love your feedback / suggestions Please open an issue for discussion before submitting a PR Thanks

reusable's People

Contributors

adamkleingit avatar avihai-yosef avatar dependabot[bot] avatar morsdyce avatar yarindeoh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

reusable's Issues

Add example - Redux integration

Add an example with an existing Redux store, where Reusable is introduced and needs data from Redux (with the new Redux hooks API)

Code example typo in readme

in readme in one code snippet it is written:

const useCurrentUser = createStore(() => ...);
const usePosts = createStore(() => ...);

const useCurrentUserPosts = createStore(() => {
  const currentUser = useCurrentUser();
  const posts = useCurrentUser();
  
  return ...
});

inside useCurrentUserPosts function you are using useCurrentUser() twice instead of using usePosts() for posts

overriding areEqual without using selectors

I trying to update a component(CompA) from another component(CompB) and CompB has

const [data, setData] = useData((state) => {
    return state;
}, () => true);

I am using the areEqual here to stop the re-render of CompB. Now setting the data using setData here not updating the store.

and in CompA

const [data, setData] = useData();

It always gives the initialData.

Bug: Async state updates in React 17

When asynchronously updating the state of a reusable store multiple times, sometimes the final update is not reflected in the state that is returned to the subscriber.

This appears to only happen when updating the state an even number of times, from within an async function. The value remains stale until the state is updated again.

The reproduction below uses a contrived example with Promise.resolve, but the issue seems to occur for any async function (i.e. after fetching data, setTimeout callback, etc).

Reproduce

Sandbox reproduction:
https://codesandbox.io/s/reusablejs-react-17-bug-4hg8n?file=/src/App.js

This same code seems to be working on React 16:
https://codesandbox.io/s/reusablejs-react-16-no-bug-g8jz5?file=/src/App.js

Additional Info

  • package.json (from codesandbox above):
{
  "name": "reusablejs-react-17-bug",
  "version": "1.0.0",
  "description": "",
  "keywords": [],
  "main": "src/index.js",
  "dependencies": {
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-scripts": "4.0.0",
    "reusable": "1.0.1"
  },
  "devDependencies": {
    "@babel/runtime": "7.13.8",
    "typescript": "4.1.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}
  • which browser
    Chrome, Safari

Is it possible to have multiple root providers and still share the same store

I love your idea, and I'd like to use it, but there is quite a tricky case that seems to make it unusable.

In React Native, when using wix react native navigation, every screen (eg in tabs based app) is technically new react root, so it requires to be wrapped in proper providers independently.

As every view is technically different react root, every provider would have 'fresh' state and therefore state will not be shared across screens.

Would such limitation be possible to be solved with reusable?

(wix navigation discussion - wix/react-native-navigation#3549 )

Add example - testing stores

Can do on TodoMVC example. Just need to see how a developer might test a store. Sounds like it's just like testing a regular hook, but with a ReusableProvider wrapper

Forking to Dart/Flutter

Can I port this/Clone this into dart/flutter?

Could I get assistance by asking for clarification on certain things?

Thanks
Sean

Add SSR example

Make sure we can use Reusable with SSR when hydrating with state. Not sure that's possible under the current implementation because the state is saved in a component's state.

Add example - Lazy-loaded modules

Add an example with lazy loaded modules that have their own Reusable stores. Make sure the stores are added dynamically to the container

Recoil

Hi!

What do you think about Recoil?

Show github repo under npm package

Its appears the npm package published does to link back to this repo. The repo is usally show to users can visit this page right fro npm.

Multiple Stores Question

Quick clarification on using multiple stores.

Can I reference multiple stores inside a single component?
//
// create 2 separate stores, neither knows about each other
//

// store 1
const useCurrentUser = createStore(() => {
return useState("John");
});

// store 2
const useCounter = createStore(() => {
const [counter, setCounter] = useState(0);
..

// try to reference both stores in a component (second reference does not work)
// Component
const Footer = () => {
// reference store 2
const counter = useCounter();
// reference store 1
const [currentUser, setCurrentUser] = useCurrentUser(); <==== currentUser is 'undefined'

Therefore, we have to compose multiple stores into 1 master store and then use that 1 master store in our components?

Question: Did you use this package with React 17

We've updated our app to React 17, but encountered weird bugs where state updates did not get back in other components where the store was used. It's a complex app, but downgrading to React 16 fixed these bugs for us. I will try to make a real bug report, but have any of you encountered this problem?

Support case of nested ReusableProviders

The bottom ones should not provide the container again if it's already provided above it.
So probably just bring it with context and if it exists just render children

Add logger

Configurable in Provider, for example:
<ReusableProvider logger={NODE_ENV === 'development'}>

By default it's false

Passing setup data to Reusable stores

Hi all,

Is there a way to pass setup data to the different stores that I create to manage state? My workaround is currently to create a SettingsProvider and read the context to pass some global data to the different stores.

render(
  <SettingsProvider settings={JSON.parse(app.dataset.page)} >
    <ReusableProvider>
      <InertiaApp
        initialPage={JSON.parse(app.dataset.page)}
        resolveComponent={name => require(`./Pages/${name}`).default}
      />
    </ReusableProvider>
  </SettingsProvider>,
  app
)

And inside a Store:

import { useContext } from "react";
import { createStore } from "reusable";

import {SettingsContext} from '../Providers/SettingsProvider';

const useResellerProducts = createStore(() => {
    const {resellerProducts} = useContext(SettingsContext);
    return {resellerProducts}
});
export default useResellerProducts;

The object resellerProducts is passed from the backend, I need it to be available in some of the stores. Is there a better way to pass data into the stores (from the backend)?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.