react-easy-flux
v1.6.0
Published
React binding for flux
Downloads
18
Readme
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()
HoC (similar to react-redux connect() HoC)Consumer
render prop
connect()
connect()
HoC consumes 3 arguments (all optional):
selector
- function to select data from current storage state (similar touseStorage()
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 asdispatch
prop.equalityFunction
- by default HoC usesshallowEqual()
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 touseStorage()
1st argument.actionCreators
- action creators map/array similar touseActionCreators()
1st argument.equalityFunction
- similar touseStorage()
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 (whenactionCreators
prop provided) or instance ofdispatch()
function (when noactionCreators
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