Reputation: 6197
I created a sample for a button toggle.
This is done by useContext
(store the data) and useReducer
(process the data). and it is working fine.
Here's the CodeSandBox Link to how it works.
version 1
is just dispatch when clicking the button.
Then I created a version 2
of toggling. basically just put the dispatch inside a custom hook. but somehow, it doesn't work.
// context
export const initialState = { status: false }
export const AppContext = createContext({
state: initialState,
dispatch: React.dispatch
})
// reducer
const reducer = (state, action) => {
switch (action.type) {
case 'TOGGLE':
return {
...state,
status: action.payload
}
default:
return state
}
}
//custom hook
const useDispatch = () => {
const {state, dispatch} = useContext(AppContext)
return {
toggle: dispatch({type: 'UPDATE', payload: !state.status})
// I tried to do toggle: () => dispatch(...) as well
}
}
// component to display and interact
const Panel = () => {
const {state, dispatch} = useContext(AppContext)
// use custom hook
const { toggle } = useDispatch()
const handleChange1 = () => dispatch({type: 'TOGGLE', payload: !state.status})
const handleChange2 = toggle // ERROR!!!
// and I tried handleChange2 = () => toggle, or, handleChange2 = () => toggle(), or handleChange2 = toggle()
return (
<div>
<p>{ state.status ? 'On' : 'Off' }</p>
<button onClick={handleChange1}>change version 1</button>
<button onClick={handleChange2}>change version 2</button>
</div>
)
}
// root
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<AppContext.Provider value={{state, dispatch}}>
<div className="App">
<Panel />
</div>
</AppContext.Provider>
);
}
Not sure what's going there. but I think there's something wrong with the dispatched state.
(I tried it works if the payload is not processing state, like some hard code stuff, so the dispatch should be fired at this moment)
Could someone give me a hand? Appreciate!!!
Upvotes: 4
Views: 7551
Reputation: 53874
Well, there no such thing React.dispatch
. Its value is undefined
export const AppContext = createContext({
state: initialState,
// useless
// dispatch: undefined
dispatch: React.dispatch
});
// dispatch function won't trigger anything.
const {state, dispatch} = useContext(AppContext);
version 1 is actually how context should be used, although usually, you will want to add an extra memoization step (depending on the use case), because on every render you assign a new object {state,dispatch}
which always will cause a render even though state
may be the same.
See such memoization use case example.
If my point wasn't clear, see HMR comment:
Strategic useMemo should be used, if many components access the context then memoizing is a good idea when the component with the provider re-renders for reasons other than changing the context.
Upvotes: 2
Reputation: 39260
You are correct that toggle needs to be a function but you are dispatching action type UPDATE and the reducer doesn't do anything with that action.
Dennis is correct that there is no point in the initial value you are giving the context and may as well leave it empty as the provider will provide the value.
The useMemo suggestion from Dennis will not optimize your example since App re renders when state changes so the memoized value will never be used.
Here is a working example of your code with comments what I changed:
const { createContext, useReducer, useContext } = React;
const initialState = { status: false };
//no point in setting initial context value
const AppContext = createContext();
const reducer = (state, action) => {
switch (action.type) {
case 'TOGGLE':
return {
...state,
status: action.payload,
};
default:
return state;
}
};
const useDispatch = () => {
const { state, dispatch } = useContext(AppContext);
return {
//you were correct here, toggle
// has to be a function
toggle: () =>
dispatch({
//you dispatch UPDATE but reducer
// is not doing anything with that
type: 'TOGGLE',
payload: !state.status,
}),
};
};
const Panel = () => {
const { state, dispatch } = useContext(AppContext);
const { toggle } = useDispatch();
const handleChange1 = () =>
dispatch({ type: 'TOGGLE', payload: !state.status });
const handleChange2 = toggle; // ERROR!!!
return (
<div>
<p>{state.status ? 'On' : 'Off'}</p>
<button onClick={handleChange1}>
change version 1
</button>
<button onClick={handleChange2}>
change version 2
</button>
</div>
);
};
function App() {
const [state, dispatch] = useReducer(
reducer,
initialState
);
return (
<AppContext.Provider value={{ state, dispatch }}>
<div className="App">
<Panel />
</div>
</AppContext.Provider>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 3