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

react-easy-flux

v1.6.0

Published

React binding for flux

Downloads

18

Readme

ci codecov downloads node npm MIT npm bundle size

react-easy-flux

Easy and fast react binding for flux.

Description

react-easy-flux implements flux architecture to help managing global application state with unidirectional data flow. It uses well known attributes of flux pattern, such as reducers and actions (action creators).

Installation

As any other npm package react-easy-flux can be added to your project by following command:

npm i -S react-easy-flux

It requires any version of react with new context API support as peer dependency, so it should be installed as well.

npm i -S react

API

createStorage(reducer, middlewares)

createStorage function creates new storage attributes such as Provider, useStorage and useActionCreators hooks. You can crete several storages for different kinds of data or use single storage (redux-like-way).

const {
    Provider: ThemeProvider,
    useStorage: useTheme,
    useActionCreators: useThemeActions,
    Consumer: ThemeConsumer,
    connect: withTheme
} = createStorage(themeReducer);

const {
    Provider: GlobalStateProvider,
    useStorage: useGlobalState,
    useActionCreators: useGlobalActions,
    Consumer: GlobalStorageConsumer,
    connect: withGlobalStorage
} = createStorage(globalStateRedurec);

Provider

All storage data consumers should be wrapped with Provider component, created by createStorage function. You can pass state prop to set initial storage state.

render(
    <ThemeProvider state={ initialTheme }>
        <GlobalStorageProvider state={ initialGlobalState }>
            <App />
        </GlobalStorageProvider>
    </ThemeProvider>,
    document.getElementById('app')
);

useStorage(selector, equalityFunction)

To consume/interact with storage state component should use useStorage() hook. It returns array of two elements: current state and dispatch() function to dispatch actions.

const Button = ({ ... }) => {
    const [ theme ] = useTheme();

    // use theme to set component style
};
import { setTheme } from './themeActions.js';

const ThemeSelector = ({ ... }) => {
    const [ , dipatch ] = useTheme();
    const onSelect = theme => dispatch(
        setTheme(theme)
    );

    ...
}

Usually we can hold different data in separate stores, but for some reason you may want to have single storage for everything (like redux way). For instance, it's much easier to use single useStorage() hook for everything than maintain separate useThemeStorage(), useLocaleStorage() and so on. Here selectors comes to help. We just need to provide selector function to select only needed data.

const MyComponent = () => {
    const [ title, dispatch ] = useStorage(
        ({ data }) => data.title
    );
}

To optimize rendering useStorage() hook consumes equality function as second optional argument. This function will be called to check whether state changes are significant enough to re-render component.

...
  if (equalityFunction(oldState, newState)) {
      return;
  }

  // re-render component

By default strict equality used as equality function (strictEqual function imported by package), but user is always able to pass something custom:

const MyComponent = () => {
    const [ state, dispatch ] = useStorage(
        (oldState, newState) => oldState.title === newState.title
    );

    // such component will be re-rendered only on title change
}

In most cases shallowEqual function is most convenient and simple way to optimize rendering rendering. It works similar to react memo/PureComponent.

import { shallowEqual } from 'react-easy-flux';

const MyComponent = () => {
    const [ state, dispatch ] = useStorage(shallowEqual);

    // will be re-rendering only when something actually changed
}

useActionCreators(actionCreatorsMap)

There might be a situation, when some component does not consumes state, but updates it. To prevent unnecessary updates for this components we can specify empty selector:

const ChatInput = () => {
    const [ , dispatch ] = useStorage(() => 0);

    const sendMessage = message => dispatch(send(message));

    ...
}

But it's easier to use another hook called useActionCreators(). This hook is used to bind action creators and not consumes state:

import { setTheme } from './themeActions.js';

const ChatInput = ({ ... }) => {
    // const [ , dispatch ] = useStorage(() => 0);

    // const sendMessage = message => dispatch(send(message));
    const { send: sendMessage } = useActionCreators({ send })

    ...
}

Such component won't be updated on state change.

useActionCreators() hook also supports attribute of array type. In some cases it can make things easier:

const [ onClick ] = useActionCreators([ onClick ]);

// instead of

const { invoke: onClick } = useActionCreators({ invoke });

What about class components?

Class components are not able to use hooks so we have two additional options:

connect()

connect() HoC consumes 3 arguments (all optional):

  • selector - function to select data from current storage state (similar to useStorage() hook 1st argument). Selector should return object, each field of this object becomes separate prop passed to wrapped component.
  • actionCreatorsMap - plain object where each value is an action creator function to bind. Each field of this object becomes separate prop passed to wrapped component. If not provided, dispatch() function will be added as dispatch prop.
  • equalityFunction - by default HoC uses shallowEqual() function to check selected state. It means component get updated when any field of selected object updated. Use this comparator to adjust update behaviour.

Here is an example:

import { connect } from './themeStorage';
import { change } from './themeActions';

class ThemeSelector extends React.Component {
    render() {
        const { items, current, change } = this.props;

        return (
            <Select
                items={ items }
                selected={ current }
                onChange={ change }
                />
        );
    }
}

export default connect(
    ({ items, current }) => ({ items, current }),
    { change },
    (oldState, newState) => oldState.current === newState.current
);

Consumer

Consumer component provides functionality similar to connect() HoC but implements different pattern - renderProp. It consumes 3 props:

  • selector - similar to useStorage() 1st argument.
  • actionCreators - action creators map/array similar to useActionCreators() 1st argument.
  • equalityFunction - similar to useStorage() 2nd argument.

As a children renderProp function should be provided. This function will receive 2 arguments:

  • state - state (or selected part)
  • actions - map/array of bound action crearors (when actionCreators prop provided) or instance of dispatch() function (when no actionCreators prop propvided).

Here is an example equivalent to example from connect() HoC description:

import { Consumer } from './themeStorage';
import { change } from './themeActions';

const selector = ({ items, current }) => ({ items, current });
const actionCreators = { change };
const equalityFunction = (oldState, newState) => oldState.current === newState.current;

class ThemeSelector extends React.Component {
    renderThemeSelector = ({ items, current, change }) => (
        <Select
            items={ items }
            selected={ current }
            onChange={ change }
            />
    );

    render() {
        return (
            <Consumer
                selector={ selector }
                actionCreators={ actionCreators }
                equalityFunction={ equalityFunction }
            >
                { this.renderThemeSelector }
            </Consumer>
        )
    }
}

Middlewares

react-easy-flux supports middlewares similar to redux middlewares, so you can use any redux-compatible ones.

import thunkMiddleware from 'redux-thunk';

const logMiddleware = store => next => action => {
    console.log(action);

    next(action);
}

const {
    Provider,
    useStorage
} = createStorage(reducer, [ thunkMiddleware, logMiddleware ])

combineReducer(sreducerMap)

Starting from [email protected] we're able to use combineReducers() helper function. It compiles map of reducers into a single reducer function.

const reducer = combineReducers({
    theme: themeReducer,
    locale: localeReducer
});

const newState = reducer(oldState, action);
/*
const newState = {
    theme: themeReducer(oldState.theme, action),
    locale: localeReducer(oldState.locale, action)
}
*/

combineReducers() have 2 important features:

  • it supports reducer maps with any deep
const reducer = combineReducers({
    locale: localeReducer,
    theme: {
        colorSheme: colorShemeReducer,
        contrast: contrastReducer
    }
});
/*
const reducer = combineReducers({
    locale: localeReducer,
    theme: combineReducers({
        colorSheme: colorShemeReducer,
        contrast: contrastReducer
    })
});
*/
  • it keeps refs when possible
const identity = value => value;
const reducer = combineReducers({
    value1: identity,
    value2: identity
});
const oldState = { value1: 1, value2: 2 };
const newValue = reducer(oldState, {});

oldState === newState; // true