Introduction

Installation

Using npm:

npm install redux-sands

Using yarn:

yarn add redux-sands

Basics

redux-sands gives you a single class as default export, from now on called ReduxWrapper. Here's a simple example that demonstrates how you could use it:

import ReduxWrapper from "redux-sands";
import component from "./component";

// Instantiate the wrapper.
const wrapper = new ReduxWrapper({ called: "example" });

// Simply add the initial state, the component for render + a reducer.
wrapper
  .add({ initState: { count: 0 } })
  .add({ component })
  .add({ update: (state, action) => ({ ...state, ...action }) });

// Expose the redux-wrapper as any other redux-component.
export default wrapper.connection;
export const reducer = wrapper.reducer;

And now let's call it:

class Comp extends PureComponent {
  render() {
    // When using 'ReduxWrapper', only an object as param is allowed.
    // Provide your values then via that object.
    return (
      <div onClick={() => this.props.update({ count: this.props.count + 1 })}>
        Increment
      </div>
    );
  }
}

As far as basic use cases go, that's it! No more hassle with manually creating actions, mappings and endless switches. Action-types get inferred automatically, as well as the linking to the reducer. You can focus on the actual app logic without having to deal with refactoring etc.

redux-sands only works when calling the reducer with an object as param. You have to provide your state-changing values there.

Importing

Furthermore, ReduxWrapper provides additional skills to simplify redux-usage:

...

wrapper
  .add({ initState: { count: 0 } })
  .add({ component })
  .add({ update: (state, action) => ({ ...state, ...action }) })
  .import({ reducer: { otherReduxComp: ["reset", { origin: "delete", as: "otherDelete" }] } })
  .import({ state: { otherReduxComp: ["schemas"] } });

// Expose the redux-wrapper as any other redux-component.
// Important: 'saga' has to be exported when using 'import(...)' and integrated into the store-middleware.
export default wrapper.connection;
export const reducer = wrapper.reducer;
export const saga = wrapper.saga;

You're able to import reducers as well as state-props from other ReduxWrappers in a dead-simple fashion. As demonstrated above, you can either import them 'as-is' or apply renaming.

When importing reducers, you have to hook up the saga-export to the saga-middleware in your app. Else the action won't get fired.

If you don't have it installed yet, here's the project.

Behind the scenes

redux-sands internally uses proxies to import reducers from other comps. This is necessary b/c we can't guarantee that the component we're importing from has already been instantiated. Therefore, when an import is declared, the wrapper instance creates a unique proxy for that action. Upon calling, this proxy gets fired, which itself fires saga to dispatch the actual action.

This race-condition doesn't occur when statically importing the reducers to your store. But since we're working here at runtime, redux may not find the other's reducer during its init, since it hasn't been created yet.

Saga is necessary, b/c dispatching actions in other reducers is a non-pattern. Hence we're dispatching the requested action as a controlled side effect via saga.

Saga-integration

Last but not least, as hinted above, redux-saga is also supported. Here's how:

...

wrapper
  .add({ initState: { data: {} } })
  .add({ component })
  .add({
    refetch: {
      fn: (state, { data = {} }) => {
        return { ...state, data };
      },
      withSaga: {
        takeEvery: function*(action) {
          const { url, result, put } = action;

          try {
            const data = yield fetch(url);
            yield put({ ...result, data });
          } catch (e) {
            yield put({ ...result, error: e });
          }
        }
      }
    }
  })

// Expose the redux-wrapper as any other redux-component.
export default wrapper.connection;
export const reducer = wrapper.reducer;
export const saga = wrapper.saga;

Here you can see a dummy-implementation that leverages the saga-integration. You provide both the standard reducer-function and a saga-function. The specific saga-fn gets derived by its key with the value representing the actual generator-method used by saga. After the async calls are done, you place your params in the 'put' method, which is provided in the action (including call from saga). The params then get passed to the reducer, where stuff gets done as usual.

All saga helpers, effect creators and effect combinators are supported, as listed here.

For a guide on how to use them, go here.

That's it for an overview. For detailed info, take a look at the API-specs following.

Last updated