use-saga-reducer
Version:
Use redux-saga without redux
194 lines (191 loc) • 5.55 kB
JavaScript
import React, { createContext, useReducer, useRef, useEffect, useMemo, useContext } from 'react';
import { stdChannel, runSaga } from 'redux-saga';
const SagaContext = createContext({});
/**
* Passes values into `runSaga` of each decendent `useSagaReducer` call.
*
* Methods are merged with local values, context methods are run first.
* Context value is merged with local value, local values will override
* existing properties.
* @param props Optional saga options
*/
const SagaProvider = ({ children, ...props }) => {
return React.createElement(SagaContext.Provider, { value: props, children: children });
};
function mergeVoidMethods(contextMethod, localMethod) {
if (!contextMethod && !localMethod) {
return;
}
if (contextMethod && !localMethod) {
return contextMethod;
}
if (!contextMethod && localMethod) {
return localMethod;
}
return (...args) => {
contextMethod(...args);
localMethod(...args);
};
}
function mergeSagaMonitors(contextMonitor, localMonitor) {
if (!contextMonitor && !localMonitor) {
return;
}
if (contextMonitor && !localMonitor) {
return contextMonitor;
}
if (!contextMonitor && localMonitor) {
return localMonitor;
}
const sagaMonitorKeys = [
'actionDispatched',
'effectCancelled',
'effectRejected',
'effectResolved',
'effectTriggered',
'rootSagaStarted'
];
const combinedSagaMonitor = {};
for (const key of sagaMonitorKeys) {
const method = mergeVoidMethods(contextMonitor[key], localMonitor[key]);
if (method) {
combinedSagaMonitor[key] = method;
}
}
return combinedSagaMonitor;
}
function mergeEffectMiddlewares(contextMiddlewares, localMiddlewares) {
if (!contextMiddlewares && !localMiddlewares) {
return;
}
if (contextMiddlewares && !localMiddlewares) {
return contextMiddlewares;
}
if (!contextMiddlewares && localMiddlewares) {
return localMiddlewares;
}
return [...contextMiddlewares, ...localMiddlewares];
}
/**
* Create an saga, disconnected from redux with its own state and dispatch.
*
* @see https://github.com/azmenak/use-saga-reducer
* @example
* ```
* function* dataFetcher() {
* try {
* const data = yield call(API.fetchData)
* yield put({type: 'FETCH_SUCCESS', payload: data})
* } catch (error) {
* yield put({type: 'FETCH_ERROR'})
* }
* }
*
* function* dataFetchingSaga() {
* yield takeLatest('FETCH', dataFetcher)
* }
*
* function reducer(state = {}, action) {
* if (action.type === 'FETCH_SUCCESS') {
* return action.payload
* }
*
* return state
* }
*
* //...
*
* const [state, dispatch] = useSagaReducer(saga, reducer)
* ```
*/
function useSagaReducer(
/**
* Saga method, called when the component mounts, must be a generator function.
* Same as would be passed to reduxSaga.runSaga
*/
saga,
/**
* Reducer method, passed into React's `useReducer` method
*/
reducer,
/**
* Optional initalized argument, passed into React's `useReducer` method
*/
initializerArg,
/**
* Store initialized function, passed into React's `useReducer` method
*/
initializer,
/**
* Additional options passed into the `runSaga` method
*
* Supports:
* ```
* sagaMonitor // each monitor will run context methods then local methods
* onError // runs context method then local method
* context // merges context values into local values
* effectMiddlewares // combines with context middleswares, running context first
* ```
* @see https://redux-saga.js.org/docs/api/#runsagaoptions-saga-args
*/
runSagaOptions) {
const [state, reactDispatch] = useReducer(reducer, initializerArg, initializer);
const stateRef = useRef(state);
useEffect(() => {
stateRef.current = state;
}, [state]);
const sagaIO = useMemo(() => {
const channel = stdChannel();
const dispatch = (action) => {
reactDispatch(action);
Promise.resolve().then(() => {
channel.put(action);
});
};
const getState = () => stateRef.current;
return {
channel,
dispatch,
getState
};
}, []);
const sagaContext = useContext(SagaContext);
useEffect(() => {
const options = runSagaOptions || {};
const context = {
...sagaContext.context,
...options.context
};
const sagaMonitor = mergeSagaMonitors(sagaContext.sagaMonitor, options.sagaMonitor);
const onError = mergeVoidMethods(sagaContext.onError, options.onError);
const effectMiddlewares = mergeEffectMiddlewares(sagaContext.effectMiddlewares, options.effectMiddlewares);
const sagaOptions = {
...sagaIO,
context,
sagaMonitor,
onError,
effectMiddlewares
};
const task = runSaga(sagaOptions, saga);
return () => {
task.cancel();
};
}, []);
return [state, sagaIO.dispatch];
}
/**
* Helper function to create custom redux-saga effects
* @param type unique type string
* @param payload any object
*/
function makeCustomEffect(type, payload) {
return {
'@@redux-saga/custom': true,
combinator: false,
type,
payload
};
}
export default useSagaReducer;
export { SagaProvider, makeCustomEffect, useSagaReducer };
//# sourceMappingURL=index.js.map