Skip to content

Instantly share code, notes, and snippets.

@fiuzagr
Last active August 16, 2020 14:27
Show Gist options
  • Save fiuzagr/91be100759c6d5d1f4e735f7802eb17f to your computer and use it in GitHub Desktop.
Save fiuzagr/91be100759c6d5d1f4e735f7802eb17f to your computer and use it in GitHub Desktop.
Simple React Flux with Hooks

Simple React Flux with Hooks

Usage

import { StateProvider, useGetState } from './state';

const MyComp = () => {
  const [state, actions] = useGetState();
  
  [...]
};
   
const App = () => {
  return (
    <StateProvider>
      <MyComp />
    </StateProvider>
  );
};
export const RESET_STATE = 'RESET_STATE';
export const SET_ONBOARDING = 'SET_ONBOARDING';
export const SET_OPEN_MENU = 'SET_OPEN_MENU';
export const SET_FONT_SIZE = 'SET_FONT_SIZE';
import * as types from './action-types';
const creator = (type) => (dispatch) => (payload, meta = {}) => {
console.debug('ACTION', type, { payload, meta });
return dispatch({ type, payload, meta });
};
export const resetState = creator(types.RESET_STATE);
export const setOnboarding = creator(types.SET_ONBOARDING);
export const setOpenMenu = creator(types.SET_OPEN_MENU);
export const setFontSize = creator(types.SET_FONT_SIZE);
export default {
onboarding: false,
openMenu: null,
fontSize: 'medium',
};
import * as types from './action-types';
const resetState = (_, { payload }) => payload;
const createSimpleReducer = (field) => (state, { payload }) => ({
...state,
[field]: payload,
});
const setOnboarding = createSimpleReducer('onboarding');
const setOpenMenu = createSimpleReducer('openMenu');
const setFontSize = createSimpleReducer('fontSize');
export default {
[types.RESET_STATE]: resetState,
[types.SET_ONBOARDING]: setOnboarding,
[types.SET_OPEN_MENU]: setOpenMenu,
[types.SET_FONT_SIZE]: setFontSize,
};
import React, {
useState,
useEffect,
createContext,
useContext,
useReducer,
} from 'react';
import { node } from 'prop-types';
import useActions from './use-actions';
import reducers from './reducers';
import initialState from './initial-state';
const dataStorage = localForage.createInstance({
name: 'myStorage',
storeName: 'data',
});
const reducer = (state, action) => {
if (reducers[action.type]) {
const newState = reducers[action.type](state, action);
console.debug('REDUCER', action.type, { state, newState });
return newState;
}
return state;
};
export const StateContext = createContext();
// exposes [state, actions] from context
export const useGetState = () => useContext(StateContext);
export const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const [loadingState, setLoadingState] = useState(true);
const actions = useActions(dispatch);
// reset state with state in storage
useEffect(() => {
if (loadingState) {
dataStorage
.getItem('state')
.then((state) => state && actions.resetState(state))
.catch(console.error)
.finally(() => setLoadingState(false));
}
// useful for debug
if (process.env.NODE_ENV === 'development') {
window.__ACTIONS = actions;
}
}, [loadingState, actions]);
// save new state on storage and preserve previous state
useEffect(() => {
if (!loadingState) {
dataStorage
.getItem('state')
.then((previousState) =>
dataStorage.setItem('previousState', previousState)
)
.then(() => dataStorage.setItem('state', state))
.catch(console.error);
}
}, [state, loadingState]);
return loadingState ? (
<div>Loading...</div>
) : (
<StateContext.Provider value={[state, actions]}>
{children}
</StateContext.Provider>
);
};
StateProvider.propTypes = {
children: node.isRequired,
};
import { useMemo } from 'react';
import * as actions from './actions';
const useActions = (dispatch) =>
useMemo(() => Object.values(actions).map((action) => action(dispatch)), [dispatch]);
export default useActions;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment