Giter Site home page Giter Site logo

redux-box's Introduction

redux_box

Redux Box 2

Build Status Coveralls github GitHub GitHub last commit

Note: If migrating from version 1.x.x to 2, you would find breaking changes.

Redux-Box aims at abstracting away the complexity in using redux with redux-saga, while letting you manage application state in modular fashion, without losing the flexibility or without introducing new bizarre terms. It organizes your store as collection of independent modules which can be used across different stores/applications/platforms.

redux_box

Illustration by Vikas

Table of contents:

What's it for:

  1. Clean, expressive and minimal reducers: If you prefer keeping your code expressive, you will feel right at home with redux-box. Have a look at a simple reducer written with and without redux-box:

redux_box

If you are concerned about the state getting mutated directly in the snippet above, then you need not be. Because the state being passed to a mutation is NOT the actual state object of application, instead it's a Proxy of the state. Redux-box relies on wonderful immer library to achieve the expressiveness you see above.

  1. Organise your giant state into modules
  2. Setup redux+redux-saga for our react/react-native app in a trice
  3. Simplified Sagas
  4. Just import and use store: You wouldn't need to write a dedicated HOC to interact with your store. If you need to interact with a particular store-module, you can simply import it and use it. As simple as that! Redux box offers two ways of using a module in your component : using @connectStore decorator or using render props. (refer to the usage section for better reference)

Installation

npm install --save redux-box 

OR

yarn add redux-box 

Note for React Native:

To support the latest decorator and generator syntax, you would want to use the .babelrc file as below:

{
  "presets": [
    "babel-preset-react-native-stage-0/decorator-support"
  ],
  "env": {
    "development": {
      "plugins": [
        "transform-react-jsx-source",
        "transform-es2015-typeof-symbol"
      ]
    },
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}

The Basics

Redux box emphasizes on dividing the whole application into multiple modules. Each of these modules manages it's state seperately, with the help of 5 segments (You can skip the segments you don't need in your project):

  1. state (It specifies the initial state of the module)

  2. mutations (It specifies the function to be run when a specific action is dispatched, it's same as reducer but clutter-free)

  3. dispatchers (it contains the actionCreators for your store. Each method of this object must return an action object )

  4. sagas (this is where you write all your sagas / async operations)

  5. selectors ( selectors can be thought of as getters or computed properties from your state)

Usage

step 1: create a module

Make sure you specify a unique name for each module ('user' in this example)

// store/user.js
import { createSagas, createContainer, createModule } from "redux-box";
import { call } from "redux-saga/effects";

// define initial state
const state = {
  name: "John",
  email: "[email protected]",
  todos: [{ name: "First", type: 1 }, { name: "Second", type: 0 }]
};

// define dispatchers
export const dispatchers = {
  setName: name => ({ type: "SET_NAME", name }),
  setEmail: email => ({ type: "SET_EMAIL", email })
};

// define mutations
const mutations = {
  SET_NAME: (state, action) => (state.name = action.name),
  SET_EMAIL: (state, action) => (state.email = action.email)
};

// define sagas
const sagas = createSagas({
  SET_EMAIL: function*(action) {
    const response = yield call(api.updateEmail, action.email);
  }
});

// selectors
export const getTodos = (state) => state.todos
export const getCompletedTodos = createSelector( getTodos, (todos) => {
    return  todos.filter(todo => todo.type==1)
})
const selectors = {
    getTodos,
    getCompletedTodos
};

export default createModule({
  state,
  mutations,
  sagas,
});

step 2 : register the module in redux store

import { createStore } from "redux-box";
import userModule  from "./user";

export default createStore({
  user: userModule
});

OPTIONAL: if you need to create store with some reducers and middlewares, the signature of createStore method from redux-box goes like this:(if you have already included a module in modules array, you need NOT to register it's sagas or reducers manually by including in config object)

createStore((modules: Object), (config: Object));

//example config object
config = {

  //define redux middlewares
  middlewares: [],
  
   //define the default state for your store
  preloadedState: {},

  // sagas to be manually registered
  sagas: [userModule.sagas, testModule.sagas],

  // reducers to be manually registered
  reducers: {
    user: yourCustomReducer
  },
  decorateReducer: reducer => {
    //do something
    return newReducer;
  },
  
  //overrite the compose function
  composeRedux: (composer) => {
    // do something
    // return modified compose function
  },
  
  // Dynamically decide when to enable or disable dev-tools 
  enableDevTools: () => true,
  devToolOptions: {}
};

After this you would need to wrap your root component around the Provider tag like so :

import React from "react";
import { Provider } from "react-redux";
import store from "./store";
import RootComponent from "./components/RootComponent";

class App extends React.component {
  render() {
    return (
      <Provider store={store}>
        <RootComponent />
      </Provider>
    );
  }
}

export default App;

step 3: Use the module in component

through decorator

import React, { Component } from "react";
import { connectStore } from "redux-box";
import { getTodos, getCompletedTodos, dispatchers } from "./user";

@connectStore({
  mapState: state => ({
    user: state.user
  }), 

  mapSelectors: {
    todos: getTodos,
    completedTodos: getCompletedTodos
  }, 

  mapDispatchers: {
    setName: dispatchers.setName,
    setEmail: dispatchers.setEmail
  }
})
export default class AppComponent extends Component {
  componentDidMount() {
    console.log(this.props.user);
    /*
	{
	    name : 'John',
	    email : '[email protected]',
	    getTodos: [{ name: "First", type: 1 }, { name: "Second", type: 0 }],
	    getCompletedTodos: [{ name: "First", type: 1 }],
	    setName : fn(arg),
	    setEmail : fn(arg)
	}
    */
  }

  render() {
    const { user } = this.props;
    return (
      <div>
        <h1>{user.name}</h1>
        <h2>{user.email}</h2>
	
	<button onClick={()=>{ 
	  this.props.setName('jane doe')
	}}> Change Name </button>
	
      </div>
    );
  }
}

Live Examples (For [email protected] only)

Docs for V2 are in progress, would be updated in a few days

Here are some examples to let you play around with redux-box

  1. Basic example - https://stackblitz.com/edit/react-3c8vsn?file=Hello.js
  2. Example showing redux-saga usage: - https://stackblitz.com/edit/react-qmedt4?file=Hello.js
  3. Example usage with redux-form: https://stackblitz.com/edit/react-w4dqth?file=store%2Findex.js
  4. Example usage with redux-persist : https://stackblitz.com/edit/react-pezrbb?file=store%2Findex.js
  5. Example showing usage of preloaded state for SSR: https://stackblitz.com/edit/react-qcasn4?file=store/index.js
  6. Using redux-observable: https://stackblitz.com/edit/react-zu8qjn?file=store%2Fuser%2Fepics.js

Upcoming Features

No pending feature requests

Suggest a new feature here

FAQs

  1. Error:
Either wrap the root component in a <Provider>, or explicitly pass “store” as a prop to "Connect(MyComponent)

This error occurs because of mismatch among versions of dependencies of redux-box, most likely react-redux. You can run this command to fix this issue for now:

  1. Decorators aren't working

Decorators aren't still a part of es6. To use the decorator syntax you should be using a transpiler like babel. Also, in create-react-app projects the .babelrc file doesn't really work so you would need to run npm run eject to be able to use custom babel-plugins. Following .babelrc should suffice:

{
  "plugins": ["transform-decorators-legacy", "styled-components"],
  "presets": [ "react","es2015", "stage-2" ]
}

In case you wouldn't like to eject, you can still use redux-box without decorators. Like so:

@connectStore({
 ui: uiModule
})
class TestComponent extends React.Component{
  ...
}
export default TestComponent

Above snippet is equivalent to:

class TestComponent extends React.Component{
  ...
}

export default connectStore({
 ui: uiModule
})(TestComponent)
  1. Can I use all the features of redux-box, with createStore from redux instead?

Yes, you can! Here's the script showing how you can use createStore from redux, to setup your modules (with reducers, sagas and middlewares): (v1.3.9 onwards)

import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import { all } from "redux-saga/effects";
import { moduleToReducer } from "redux-box";
import { module as homeModule } from "./home";
import { module as userModule } from "./user";

//hook up your module reducers
const combinedReducer = combineReducers({
  home: moduleToReducer(homeModule),
  user: moduleToReducer(userModule)
});

// hook up your module sagas
const sagas = [...homeModule.sagas, ...userModule.sagas];

// hook up your middlewares here
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];

//what follows below is the usual approach of setting up store
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let enhancer = composeEnhancers(applyMiddleware(...middlewares));

function* rootSaga() {
  yield all(sagas);
}

const store = createStore(combinedReducer, enhancer);
sagaMiddleware.run(rootSaga);
export default store;

redux-box's People

Contributors

anish000kumar avatar binocarlos avatar dependabot[bot] avatar dragonfire1119 avatar nickbreaton avatar will-stone 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-box's Issues

working with rn 0.56-0.57 / babel 7

Hello,
I'm using box on my < 0.56 and it's configured and working well but cannot setup it with new rn/babel.
As I read rn uses new metro-react-native-babel-preset

Any hint for setting it up ?
I think I cannot setup babel dependencies, I read that stages are deprecated in new babel!!!

Include getters

like in vuex getters enable you write computed functions that return values that can be used across you app. this values are updated when state changes across your apps.

Adding Redux Observable?

I would love to see either an example of this with redux-observable (if possible) or have this added as a feature.

I love this syntax for modules, and think it paired with redux-observable would be really amazing.

Typescript?

This looks awesome! I really like it.

I would love to see a typescript example of this this in action because it seems like you should be able to strongly type this very nicely 😄

Improve Action creator

Pretty much how https://github.com/infinitered/reduxsauce does it.

Instead of creating a lot of functions that just return the object that represent the action that you are talking about,

You could use a function that does that for you and specially, it gives you back the action list.

take a look to reduxsaurce, I think it is worth to add something similiar or the same.

Side note,

that way you could even scope your actions based on some configuration when you create the actions, like prefix, which it is really handy when you have large applications (you do not need to hunt the entire code looking for every place that you need to rename (add the prefix) your action.

Store isn't passed to children with newer redux & react-redux versions

When using the newest versions of react-redux (^6.0.0) and redux (^4.0.1) instead of the versions this project uses (5.0.6 and ^3.7.2) the following exception occurs:
Either wrap the root component in a <Provider>, or explicitly pass “store” as a prop to "Connect(MyComponent)
when using this setup (simplified):

import React from 'react';
import store from './store'
import { Provider } from 'react-redux';
import MyComponent from './MyComponent';

export default const App = () => (<Provider store={store}><MyComponent></Provider>);
import React, { Component } from "react";
import { module as testModule } from "./store/testModule";
import { connectStore } from "redux-box";
class MyComponent extends React.Component {
  render() {
    return (<div>Hey</div>)
   }
}
export default connectStore({
    test: testModule
})(MyComponent) 

I guess there has been a breaking change between the versions - it does work when using the lower versions in the project. Would be nice if someone more skilled than I am could take a look (and maybe update the dependencies?)

Example with Testing

Thanks for this useful package and all the examples.

I couldn't find any example with a test. Specifically, I am interested in testing sagas. It seems the approach proposed in redux-saga (https://redux-saga.js.org/docs/advanced/Testing.html) is not applicable here or at least is not very straight forward. Appreciate if you provide a test case for sagas.

Thanks

More complex examples?

I like the example that you give in the Readme but when you open the example in the example folder its much simpler and doesnt seem to take advantage of that beautiful syntax you show in the readme.

It would be great if you could show a more complex example, perhaps one that does an api request too, to show how you handle async actions?

How to invoke actions from sagas

I'm trying to figure out how to invoke actions from sagas

Should this work ?

const state = {
	version: '1.0.0',
}

const actions = {
	setVersion: version => ({ type: 'SET_VERSION', payload: version }),
}

const mutations = {
	SET_VERSION: (state, action) => (state.name = action.payload),
}

const sagas = createSagas({
	GET_VERSION: function*(action) {
		const response = yield call(api.getVersion)
		yield put(actions.setVersion(response.version))
	},
})

But then what if I need to invoke an action from another module ?

Should I import the other module (or at least the other module's actions) ?

Any thoughts ?

P.D.: I've seen the saga example where you invoke put without the action creator ({type: ....}), but the idea of using the action creator is to have strong typing, whenever typescript becomes available.

idea: replace screenshots with Markdown

I wonder why there are screenshots in Readme instead of text? It prevents e.g. copy-pasting (not to mention other aspects like overall accesibility, i.e. screen readers, poor connection, SEO etc.)

Markdown (at least in GH and NPM flavors) has syntax coloring out-of-the-box.
You write:
```javascript
import {createSagas, createCntainer } from 'redux-box'
```

And it looks:

import {createSagas, createCntainer } from 'redux-box'

https://guides.github.com/features/mastering-markdown/

Cannot read property 'sagas' of undefined

Hi,

After using this lines I got the error :

import store from "../store/index" // points to store/index.js file

const accessToken = store.getState().auth.accessToken;

console.log('accessToken', accessToken)

Possible to be a bug or my code mistaken ?

Thanks

Reselect?

I'm new to all this stuff but found your (a) video on redux-box and really like the way it looks compared to all the boilerplate. I also found this project: react-boilerplate which seems great. I see lots of mention of using reselect for performance reasons, but can't say I actually understand what it is doing yet. I'm wondering if you could address the ability to use reselect for performance or if maybe redux-box addresses this in another way.

Support for dynamic selectors

I've noticed that the Selectors are all executed immediately and the results of them are provided to the React components for rendering. This means that it's difficult to have selectors that take parameters.

What I've taken to doing, which works but feels a bit strange, is having Selectors that return functions. For example:

    selectUsers: (state) => () => selectUsers(state),
    selectUserById: (state) => (id) => selectUserById(state, id)

So the React component sees two functions, one called selectUsers with zero parameter, and one called selectUserById with a single parameter. These can then be called as needed and will return the correct values from the state, but the actual execution of them is only on-demand and they can be parameterised.

I'm not sure if such a feature would be beneficial being included as a core part of redux-box, or how, but it would be useful for me :)

Redux Saga effect 'select' doesn't work as expected

Redux Box packages things nicely into modules, where each module is nicely self-contained.

This goes as far as having Selectors that exist as part of the module, and when called are only give this modules portion of the state. This is fantastic for building up self-contained modules easily.

Unfortunately, Redux Box relies on the Redux Sagas "effects" for some feature. One such feature is the obtaining values from elsewhere in the state during a Saga - using the select effect.

This effect works exactly the same as in Redux Saga. Which means that it calls the provided selector with the entire state tree and not just the local modules portion of it. Ultimately this means that we need to have two different types of selector - some that work on the local module and some that work on the entire state tree.

It could be useful to have a variation of select that applies the selector only to the local module state, so that the selectors used can remain consistent.

Redux-box + GraphQL

I would like to get an example of how to use redux-box with an api GraphQl

Many thanks and beautiful work.

extra sagas passed in via the config don't register

I'm using the sagas property of the config to pass in some additional sagas that are not part of a module and I'm noticing they are not registering - using this example from the docs

import { createStore } from 'redux-box'
import { call } from 'redux-saga/effects'
function* myCustomSaga() {
  console.log('my saga is registered')
}
const config = {
  sagas: [call(myCustomSaga)]
}
export default createStore(modules, config)

I'm not seeing myCustomSaga register.

I think this is because of this line:

config.sagas && config.sagas.forEach(saga => sagas.concat(saga));

which needs to be:

sagas = config.sagas ? sagas.concat(config.sagas) : sagas;

i.e. it looks as though the concatenated saga array is getting lost in a black hole within the forEach loop

I've tested this and it works - I will make a PR presently

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.