npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-store-hooks

v1.0.7

Published

Create a redux-like store using hooks. Supports middleware

Downloads

12

Readme

use-store-hooks

Create a redux-like store using hooks. Supports middleware.

Demo available here.

Test Coverage

Work in progress. No changes have been made to the API up to this point.

Update March 29, 2019:

Files left to test:

  • createDevTools.js
  • withDevTools.js
65.71% Statements 46/70
45.83% Branches 11/24
67.74% Functions 21/31
67.19% Lines 43/64

Update March 28, 2019:

 50% Statements 35/70
 29.17% Branches 7/24
 32.26% Functions 10/31
 51.56% Lines 33/64

Motivation

Redux is a very powerful concept. This document aims to share how one could still use the concept without having to ever install redux and react-redux.

In addition, this package provides a bunch of methods to setup a redux-like global store, which connects to Redux Dev Tools and also consumes middleware!

How to use?

  1. Invoke a store
const store = invokeStore(reducer);

Unlike createStore, this method simply does a dry run of your reducer to get the initial state. Optionally, invokeStore can take an initialState as second parameters.

If you wish to apply middleware, invokeStore takes them as third argument. Middleware must be an array of redux valid middlewares.

Enhancers are not supported!

  1. Wrap your React tree with <Provider>
function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
  1. Consume the store

This is where this library is fundamentally different than redux and react-redux.

You have two options:

Connect

This library exposes connect which behaves almost like react-redux's. The main difference is that the second parameter to connect should always be a function!

export function WithConnect({ count, dispatch }) {
  return (
    <Counter
      count={count}
      inc={() => dispatch({ type: INC })}
      dec={() => dispatch({ type: DEC })}
    />
  );
}

export default connect(store => ({ count: store }))(GlobalStoreExample);

This is a slightly annoying method, which involves Higher Order Components, sometimes testing these is cumbersome.

connect can take zero, one or two arguments!

useContext

React 16.8.1, exposes the useContext API, which can be used to replace connect.

import React, { useContext } from "react";
import Counter from "../components/Counter";
import { State } from "use-global-store";
import { INC, DEC } from "../ducks/counter";

export function WithoutConnect() {
  const { state: count, dispatch } = useContext(State);
  return (
    <Counter
      count={count}
      inc={() => dispatch({ type: INC })}
      dec={() => dispatch({ type: DEC })}
    />
  );
}

export default WithoutConnect;

This is a much better approach, as it isolates completely the component from external props. The benefits of using context are already well known.

Notice how WithoutConnect does not need pre-defined props!

Why?

You could've set this up yourself, what is the big gain?

This library has no additional dependencies other than React 16.1+ being present in your project!

The biggest gain is the possiblity to use middleware. Furthermore, you can to enable this anywhere in your application.

For example, Redux Dev Tools is a good extension to debug your React-Redux applications, but it relies on enhancing Redux. How could you still use it in your application?

Vanilla Approach

Let's say you have a React component. Notice that this class component has been structured in such a way that it dispatches actions to a reducer, which updates the state. Redux is a way of coding, not just a library!

import React, { Component } from "react";
import Counter from "../components/Counter";
import reducer, { INC, DEC } from "../ducks/counter";

export class Managed extends Component {
  state = {
    count: 0
  };

  dispatch = action => reducer(this.state.count, action);

  increase = () => this.setState({ count: this.dispatch({ type: INC }) });
  decrease = () => this.setState({ count: this.dispatch({ type: DEC }) });

  render() {
    const { count } = this.state;
    return <Counter count={count} inc={this.increase} dec={this.decrease} />;
  }
}

In order to use it you'd have set your component as shown here:

import React, { Component } from "react";
import Counter from "../components/Counter";
import reducer, { INC, DEC } from "../ducks/counter";

const useDevTools =
  process.env.NODE_ENV === "development" &&
  typeof window !== "undefined" &&
  window.__REDUX_DEVTOOLS_EXTENSION__;

export class ReactComponentDevTools extends Component {
  state = {
    count: 0
  };

  devTools = null;
  extension = null;

  componentDidMount() {
    if (useDevTools) {
      this.extension = window.__REDUX_DEVTOOLS_EXTENSION__;
      this.devTools = this.extension.connect({
        name: "Managed Dev Tools"
      });
      this.devTools.send("@INIT", this.state.count);
    }
  }

  componentWillUnmount() {
    if (useDevTools) {
      this.extension.disconnect();
    }
  }

  dispatch = action => {
    const nextState = reducer(this.state.count, action);
    if (useDevTools) {
      this.devTools.send(action.type, nextState);
    }
    return nextState;
  };

  increase = () => this.setState({ count: this.dispatch({ type: INC }) });
  decrease = () => this.setState({ count: this.dispatch({ type: DEC }) });

  render() {
    const { count } = this.state;
    return <Counter count={count} inc={this.increase} dec={this.decrease} />;
  }
}

export default ReactComponentDevTools;

Now your component reports to Redux Dev Tools. However it is now much more verbose!

withDevTools

Instead, you could just use withDevTools, which enhancers your reducer.

import React, { useReducer } from "react";
import Counter from "../components/Counter";
import reducer, { INC, DEC } from "../ducks/counter";
import { withDevTools } from "../../../src/";

const enhanced = withDevTools(reducer, { name: "Enhanced" });

export function ReactHookDevToolsEnhancer() {
  const [count, dispatch] = useReducer(enhanced, 0);
  const inc = () => dispatch({ type: INC });
  const dec = () => dispatch({ type: DEC });

  return <Counter count={count} inc={inc} dec={dec} />;
}

export default ReactHookDevToolsEnhancer;

And now your the Counter state is up in the Redux Dev Tools.

useMiddleware

You can also make local redux store which connects wraps a section of your application.

import React, { useReducer } from "react";
import Counter from "../components/Counter";
import reducer, { INC, DEC } from "../ducks/counter";
import {
  useMiddleware,
  useProvider,
  createDevTools,
  invokeStore
} from "../../../src/";

// You define your own Context
import CustomContext from "./YourCustomContext";

const middlewares = [createDevTools({ name: "Local Redux" })];
const store = invokeStore(reducer, undefined, middlewares);

export function LocalRedux({ children }) {
  const [state, dispatch, ready] = useProvider(store);

  return (
    <CustomContext.Provider value={{ dispatch, state }}>
      {ready ? children : null}
    </CustomContext.Provider>
  );
}

export default LocalRedux;

Further down the three just invoke useContext and pass your CustomContext as argument!

API

These are the API's exposed by the package.

invokeStore

This function takes three arguments.

  • reducer
  • initialState - optional
  • middlewares - array of middlewares - optional
const store = invokeStore(reducer, undefined, undefined);

Provider

React-like node, which takes a store as single prop!

const App = () => (
  <Provider store={store}>
    <AwesomeApp />
  </Provider>
);

connect

React-Redux like function. Takes two arguments, mapStateToProps and mapDispatchToProps to props. Both must be functions! Both could also be undefined.

It returns a Wrapper, which can consume a React Component. The Wrapper passes props and the results of mapStateToProps(state, props) and mapDispatchToProps(dispatch, props) as props to the React Component.

This connect function also passes dispatch down to the React Component.

export default connect(
  store => ({ store }),
  dispatch => ({ inc: () => dispatch({ type: INC }) })
)(Counter);

Eventually you should move away from connect!

State

The actual global state. To move away from connect import this instead, and pass it to useContext from React's main API.

useContext returns an object!

In this case:

const { state, dispatch } = useContext(State);

createDevTools

Easily setup dev tools as middleware by invoking this function. It optionally takes an object, with an environment flag, and a name to be used in the dev tools extension.

The environment flag, could simply be whether or not you are in development environment.

const devTools = createDevTools({
  env: process.env.NODE_ENV === "development",
  name: "Wow"
});

useProvider

Given a store:

const store = { reducer, initialState, middlewares };

Returns the state of the store, a dispatcher and whether or not the store is ready to be used!

function Main() {
  const [state, dispatch, ready] = useProvider(store);
  return [state, dispatch, ready];
}

useMiddleware

Takes a store of shape:

const store = { reducer, initialState, middlewares };

Returns a state and enhanceDispatch, which runs throught the middleware!

function Main() {
  const [state, enhancedDispatch] = useMiddleware(store);
  return [state, enhancedDispatch];
}

combineReducers

If you have more than one reducer, you can make a plain object out of them and pass it to combineReducers. The result is your rootReducer and what you should pass to invokeStore.

const rootReducer = combineReducers({
  auth,
  counter,
  uiState
});

const store = invokeStore(rootReducer);

compose

Naive implementation.

const double = x => x * 2;

console.log(
  compose(
    double,
    double
  )(2) === double(double(2))
); // true