All Projects → iusehooks → Redhooks

iusehooks / Redhooks

Licence: mit
Predictable state container for React apps written using Hooks

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Redhooks

Use Substate
🍙 Lightweight (<600B minified + gzipped) React Hook to subscribe to a subset of your single app state.
Stars: ✭ 97 (-34.9%)
Mutual labels:  hooks, state-management, reducer
Pure Store
A tiny immutable store with type safety.
Stars: ✭ 133 (-10.74%)
Mutual labels:  immutability, hooks, state-management
Redux Machine
A tiny library (12 lines) for creating state machines in Redux apps
Stars: ✭ 338 (+126.85%)
Mutual labels:  state-management, reducer
React Recollect
State management for React
Stars: ✭ 411 (+175.84%)
Mutual labels:  immutability, state-management
Pullstate
Simple state stores using immer and React hooks - re-use parts of your state by pulling it anywhere you like!
Stars: ✭ 683 (+358.39%)
Mutual labels:  hooks, state-management
Redux Orm
A small, simple and immutable ORM to manage relational data in your Redux store.
Stars: ✭ 2,922 (+1861.07%)
Mutual labels:  state-management, reducer
Mobx Keystone
A MobX powered state management solution based on data trees with first class support for Typescript, support for snapshots, patches and much more
Stars: ✭ 284 (+90.6%)
Mutual labels:  immutability, state-management
React Hooks
Essential set of React Hooks for convenient Web API consumption and state management.
Stars: ✭ 515 (+245.64%)
Mutual labels:  hooks, state-management
atomic-state
A decentralized state management library for React
Stars: ✭ 54 (-63.76%)
Mutual labels:  hooks, state-management
React Context Hook
A React.js global state manager with Hooks
Stars: ✭ 50 (-66.44%)
Mutual labels:  hooks, state-management
Juicr.js
A simple (and tiny <1kb) redux inspired reducer for handling state changes.
Stars: ✭ 102 (-31.54%)
Mutual labels:  state-management, reducer
Radioactive State
☢ Make Your React App Truly Reactive!
Stars: ✭ 273 (+83.22%)
Mutual labels:  hooks, state-management
React Recomponent
🥫 Reason-style reducer components for React using ES6 classes.
Stars: ✭ 272 (+82.55%)
Mutual labels:  state-management, reducer
Constate
React Context + State
Stars: ✭ 3,519 (+2261.74%)
Mutual labels:  hooks, state-management
react-stateful-component
Functional stateful React components with sideEffect support
Stars: ✭ 19 (-87.25%)
Mutual labels:  state-management, reducer
Easy Peasy
Vegetarian friendly state for React
Stars: ✭ 4,525 (+2936.91%)
Mutual labels:  hooks, state-management
micro-observables
A simple Observable library that can be used for easy state management in React applications.
Stars: ✭ 78 (-47.65%)
Mutual labels:  hooks, state-management
zoov
Use 🐻 Zustand with Module-like api
Stars: ✭ 24 (-83.89%)
Mutual labels:  hooks, state-management
Use Global Context
A new way to use “useContext” better
Stars: ✭ 34 (-77.18%)
Mutual labels:  hooks, state-management
Clean State
🐻 A pure and compact state manager, using React-hooks native implementation, automatically connect the module organization architecture. 🍋
Stars: ✭ 107 (-28.19%)
Mutual labels:  hooks, reducer

Redhooks Logo

Redhooks is a tiny React utility library for holding a predictable state container in your React apps. Inspired by Redux, it reimplements the redux paradigm of state-management by using React's new Hooks and Context API, which have been officially released by the React team.

Build Status Package size Coverage Status License Tweet

Installation

npm install --save redhooks

Motivation

In the Reactjs docs a nice paragraph titled useYourImagination() suggests to think of different possible usages of functionality Hooks provide, which is essentially what Redhooks tries to do. This package does not use any third party library, being only dependent upon the Hooks and the Context API. You do not need to install react-redux to connect your components to the store because you can have access to it directly from any of your function components by utilizing the useStore Redhooks API. Hooks are not allowed within class Components, for using the store within them Redhooks exposes a Higher Order Component (HOC) named connect. It also supports the use of middleware like redux-thunk or redux-saga or your own custom middleware conforming to the middleware's API.

Basic Example

Redhooks follows the exact same principles of redux, which was the package's inspiration.

  • the total state of your app is stored in an object tree inside of a single store object.
  • state is read only, so the only way to change the state is to dispatch an action, an object describing the changes to be made to the state.
  • to specify how the actions transform the state tree, you write pure reducers.

Store

store.js

import { createStore, combineReducers } from "redhooks";

// function reducer
const hello = (
  state = { phrase: "good morning" },
  { type, payload }
) => {
  switch (type) {
    case "SAY_HELLO":
      return { ...state, phrase: payload };
    default:
      return state;
  }
};

// function reducer
const counter = (state = 0, { type, payload }) => {
  switch (type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};

// You can use the combineReducers function
const rootReducer = combineReducers({ hello, counter });
const store = createStore(rootReducer);

// or if you want to be less verbose you can pass a plain object whose values are reducer functions
const store = createStore({ hello, counter });

// eventually we can pass to createStore as second arg an opts object like:
// const opts = { preloadedState: { counter: 10 }, initialAction: { type: "INCREMENT" } }
// const store = createStore(rootReducer, opts);

export default store;

App.js

import Provider from "redhooks";
import store from "./store";

function App() {
  return (
      <Provider store={store}>
        <DispatchAction />
        <DispatchActionExpensive />
        <ReadFromStore />
      </Provider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Dispatching Sync and Async Actions - Non-expensive rendering operation

If your component does not require expensive rendering, you can use the useStore Redhooks API within your functional component in order to access the Redhooks store. Class or function components that perform expensive rendering operations can be connected to the store by using the connect Redhooks HOC which takes care to avoid unnecessary re-rendering in order to improve performance. We'll be able to see this in action below:

./components/DispatchAction.js

import React from "react";
import { useStore } from "redhooks";

const DispatchAction = () => {
  const { dispatch } = useStore();
  return (
     <div>
        <button
          onClick={() =>
            dispatch({
              type: "SAY_HELLO",
              payload: "hello"
            })
          }
        >
          Say Hello
        </button>
        <button onClick={() => dispatch({ type: "INCREMENT" })}>
          Sync Increment Counter
        </button>
        <button
          onClick={() =>
            setTimeout(() => dispatch({ type: "DECREMENT" }), 3000)
          }
        >
          Async Decrement Counter
        </button>
      </div>
  );
};

export default DispatchAction;

Dispatching Sync and Async Actions - Expensive rendering operation

For components requiring expensive rendering, the use of connect helps to avoid any unnecessary re-rendering.

./components/DispatchActionExpensive.js

import React from "react";
import { connect } from "redhooks";

const DispatchActionExpensive = props => (
     <div>
        <button onClick={() => props.dispatch({ type: "INCREMENT" })}>
          Sync Increment Counter
        </button>
        <button
          onClick={() =>
            setTimeout(() => props.dispatch({ type: "DECREMENT" }), 3000)
          }
        >
          Async Decrement Counter
        </button>
      </div>
);

export default connect()(DispatchActionExpensive);

Use Store from a Function Component

./components/ReadFromStore.js

import React from "react";
import { useStore } from "redhooks";

const ReadFromStore = () => {
  const { state } = useStore();
  const { hello, counter } = state;
  return (
    <section>
      <h1>{hello.phrase}</h1>
      <span>{counter}</span>
    </section>
  );
};

export default ReadFromStore;

Tip: If your function component requires an expensive render, you should use the connect HOC Redhooks API.

Use Store from a Class Component

import React, { Component } from "react";
import { connect } from "redhooks";

class ReadFromStore extends Component {
  render() {
    const { hello, counter } = this.props;
    return (
        <section>
            <h1>{hello.phrase}</h1>
            <span>{counter}</span>
        </section>
    );
  }
};

function mapStateToProp(state, ownProps) {
  return {
    hello: state.hello,
    counter: state.counter
  };
}

export default connect(mapStateToProp)(ReadFromStore);

Apply Middleware

As for Redux, middleware is a way to extend Redhooks with custom functionality. Middleware are functions which receive the store's dispatch and getState as named arguments, and subsequently return a function. Redhooks supports the use of redux middleware like redux-thunk, redux-saga or you could write custom middleware to conform to the middleware API.

Custom middleware - Logger Example

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

Use redux-thunk and redux-saga

import React from "react";
import { render } from "react-dom";
import Provider, { createStore } from "redhooks";
import thunk from "redux-thunk";
import createSagaMiddleware from "redux-saga";
import reducer from "./reducers";

const sagaMiddleware = createSagaMiddleware();

const middlewares = [thunk, sagaMiddleware];

const store = createStore(reducer, { middlewares });

function* helloSaga() {
  console.log("Hello Sagas!");
}
// redux-saga needs to run as soon the store is ready
store.onload = () => sagaMiddleware.run(helloSaga);

render(
    <Provider store={store}>
        <DispatchAction />
        <ReadFromStore />
    </Provider>,
  document.getElementById("root")
);

Usage with React Router

App routing can be handled using React Router.

import React from 'react'
import Provider from 'redhooks'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Home from './Home'
import About from './About'

const App = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route exact path="/about" component={About} />
      </Switch>
    </Router>
  </Provider>
)

export default App
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redhooks'
import thunk from "redux-thunk";
import rootReducer from "./reducers"
import App from './App'

const opts = {
  preloadedState: { counter: 9 },
  initialAction: { type: "INCREMENT" },
  middlewares: [thunk]
};

const store = createStore(rootReducer, opts)

render(<App store={store} />, document.getElementById('app'))

Isolating Redhooks Sub-Apps

import React from "react";
import Provider, { createStore } from "redhooks";
import ReadFromStore from "./components/ReadFromStore";
import Footer from "./components/Footer";

const counter = (state = 0, { type, payload }) => {
  switch (type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};

const store = createStore(counter);

export default function SubApp() {
  return (
    <Provider store={store}>
      <DispatchAction />
      <ReadFromStore />
    </Provider>
  );
}

Each instance will be independent and it will have its own store.

import React from "react";
import SubApp from "./SubApp";

function App() {
  return (
    <React.Fragment>
      <SubApp />
      <SubApp />
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Redhooks API Reference

createStore

createStore(reducer, [opts])

createStore returns the store object to be passed to the <Provider store={store} />.

  • The reducer argument might be a single reducer function, a function returned by combineReducers or a plain object whose values are reducer functions (if your store requires multiple reducers).
  • The opts optional argument is an object which allows you to pass a preloadedState, initialAction and middlewares.

The store is ready when the Provider is mounted, after which an onload event will be triggered.

Example

const opts = {
    preloadedState: 1,
    initialAction: { type: "DECREMENT" },
    middlewares: [thunk, sagaMiddleware, logger]
};
const store = createStore(reducer, opts);
store.onload = ({ dispatch }) => dispatch({ type: "INCREMENT" });

combineReducers

combineReducers(reducers)

combineReducers combines an object whose props are different reducer functions, into a single reducer function

  • The reducers argument is an object whose values correspond to different reducing functions that need to be combined into one.

Example

const rootReducer = combineReducers({ counter, otherReducer })
const store = createStore(rootReducer)

connect

connect([mapStateToProps], [mapDispatchToProps])

connect function connects a React component to a Redhooks store. It returns a connected component class that wraps the component you passed in taking care to avoid unnecessary re-rendering. It should be used if your class or function components require expensive rendering.

  • If a mapStateToProps function is passed, your component will be subscribed to Redhooks store. Any time the store is updated, mapStateToProps will be called. The results of mapStateToProps must be a plain object, which will be merged into your component’s props. If you don't want to connect to Redhooks store, pass null or undefined in place of mapStateToProps.
  • mapDispatchToProps, if passed, may be either a function that returns a plain object whose values, themselves, are functions or a plain object whose values are action creator functions. In both cases the props of the returned object will be merged in your component’s props. If is not passed your component will receive dispatch prop by default.

Example

const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
const mapDispatchToProps = dispatch => ({ increment: action => dispatch({ type: action })})
// or
const mapDispatchToProps = { increment: type => ({ type })}

export default connect(mapStateToProps, mapDispatchToProps)(ReactComponent)

bindActionCreators

bindActionCreators(actionCreators, dispatch)

bindActionCreators turns an object whose values are action creators into an object with the same keys, but with every function wrapped in a dispatch call so they may be invoked directly.

  • actionCreators An object whose values are action creator functions or plain objects whose values are action creator functions
  • dispatch The dispatch function available on your Redhooks store.

Action creator

An action creator is a function that creates an action.

type ActionCreator = (...args: any) => Action | AsyncAction

Example

actions.js

export const action_1 = action_1 => action_1
export const action_2 = action_2 => action_2
export const action_3 = action_3 => action_3

YourComponentConnected.js

import React from "react";
import { connect, bindActionCreators } from "redhooks";
import * as actions from "./actions";

const YourComponent = ({ actions, counter }) => (
  <div>
    <h1>counter</h1>
    <button onClick={actions.action_1}>action_1</button>
    <button onClick={actions.action_2}>action_2</button>
    <button onClick={actions.action_2}>action_3</button>
  </div>
);

const mapStateToProps = state => ({
  counter: state.counter
});

// a verbose way
const mapDispatchToProps = dispatch => ({
  actions: bindActionCreators(actions, dispatch)
});
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(YourComponent);

// or simply
export default connect(
  mapStateToProps,
  { actions }
)(YourComponent);

Provider

The <Provider /> makes the Redhooks store available to any nested components.

Example

import React from "react";
import Provider from "redhooks";
import store from "./store";
import ReadFromStore from "./components/ReadFromStore";
import Footer from "./components/Footer";

export default function App() {
  return (
    <Provider store={store}>
      <DispatchAction />
      <ReadFromStore />
    </Provider>
  );
}

useStore

useStore()

useStore can be only used within a function component and it returns the store.

  • The store is an object whose props are the state and the dispatch.

Example

import React from "react";
import { useStore } from "redhooks";

const Example = () => {
  const { state, dispatch } = useStore(); // do not use it within a Class Component
  const { counter } = state;
  return (
    <section>
      <span>{counter}</span>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>
        Increment Counter
      </button>
    </section>
  );
};

export default Example;

CodeSandbox Examples

Following few open source projects implemented with redux have been migrated to redhooks:

License

This software is free to use under the MIT license. See the LICENSE file for license text and copyright information.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].