Reputation: 909
I am currently trying to migrate Redux files to React hooks (useReducer) with minimal changes. I am reusing Redux reducers and the initial states. e.g.
export const INITIAL_STATE = {
locale: "en"
};
export const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "CHANGE_LOCALE":
return {
...state,
locale: action.payload
};
default:
return state;
}
};
I'd like to reuse Redux actions as well and need some help, currently, I have.
export const changeLocale = (payload) => {
return (dispatch) => {
Cookies.set("lc", payload);
dispatch({
type: "CHANGE_LOCALE",
payload
});
};
};
But dispatch here and dispatch in useReducer are different functions, what are the ways to migrate such action to be used in useReducer hook?
Upvotes: 0
Views: 403
Reputation: 11
Here is what we use. Add a utility function:
const wrapAsync = (dispatch) => (action) => {
if (typeof action === "function") {
return action(dispatch);
}
return dispatch(action);
};
Then in your code:
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
const asyncDispatch = React.useMemo(() => wrapAsync(dispatch), [dispatch]);
Pass the new asyncDispatch down to child components:
<LocaleContext.Provider
value={{...state, asyncDispatch}}>
{children}
</LocaleContext.Provider>
Then in your child component, you can do as follows:
import {changeLocale} from "~/actions/locales";
let {locale, asyncDispatch} = useContext(LocaleContext);
<Button onclick={({e}) => asyncDispatch(changeLocale(e.target.value))}/>
Upvotes: 1
Reputation: 40653
You can't achieve this with plain a useReducer
though it's straight-forward to create a custom hook that can deal with the "thunk" (or whatever you want to call the actions which are functions) as well. Your base case reducer is:
const [ state, dispatch ] = React.useReducer(reducer, INITIAL_STATE);
However you need to modify your dispatch
to allow it to make it have "middleware" like behaviour:
const useReducerWithThunk = (reducer, initialState) => {
const [ state, innerDispatch ] = React.useReducer(reducer, initialState);
const myDispatch = async (action) => {
if (typeof action === 'function') {
// Wrap the result in a promise if it's not one already
return await Promise.resolve(action(myDispatch, () => state));
} else {
return innerDispatch(action);
}
};
return [ state, myDispatch ];
}
Then your code will do:
const [ state, dispatch ] = useReducerWithThunk(reducer, initialState);
This should be enough for a simple drop-in replacement of redux with thunks, however be mindful that dispatch
will become async which is not the default behaviour.
Upvotes: 0