I want to write context with UseReducer hook but an error
error this:dispatch is not a function
what is problem?
please help me guys
almost is correct but not working it.
I want to see the appropriate action by clicking on the buttons,
one is increment, one is decrement and the other is reset.
CounterOne
import { UseCount, UseCountActions } from "./CounterProvider";
const CounterOne = () => {
const count = UseCount();
const dispatch = UseCountActions();
return (
<div>
<h2>count is:{count}</h2>
<button onClick={() => dispatch({ type: "add", value: 1 })}>
Addone{" "}
</button>
<button onClick={() => dispatch({ type: "decrement", value: 1 })}>
decrement
</button>
<button onClick={() => dispatch({ type: "reset" })}>reset</button>
</div>
);
};
export default CounterOne;
CounterProvider
import React, { useReducer, useState } from "react";
import { useContext } from "react";
const CounterContext = React.createContext();
const CounterContextDispather = React.createContext();
const initialState = 0;
const reducer = (state, action) => {
switch (action.type) {
case "add":
return state + action.value;
case "decrement":
return state - action.value;
case "reset":
return initialState;
default:
return state;
}
};
const CounterProvider = ({ children }) => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<CounterContext.Provider value={count}>
<CounterContextDispather.Provider value={dispatch}>
{children}
</CounterContextDispather.Provider>
</CounterContext.Provider>
);
};
export default CounterProvider;
export const UseCount = () => useContext(CounterContext);
export const UseCountActions = () => {
return CounterContextDispather;
};
export const UseCountActions = () => {
return useContext(CounterContextDispather);
};
There is a official example
I am trying to update an element from an array by adding an object as a property like shown in this picture
When a user clicks on a single node button, a modal appears the user fills the form and then it is addes as a property for this node.
But for some reason I get this type error that says that the updateElement is not a function.
BTW, I am using Redux & react-flow-renderer libraries.
Reducer
import * as types from '../actions/types';
const initialState = {
elements: []
};
const flow = (state = initialState, action) => {
switch (action.type) {
case types.UPDATE_ELEMENT:
return {
...state,
elements: state.elements.map((e) => {
if (e.id === action.payload.id) {
e = {
...e,
options: action.payload.options,
};
}
return e;
}),
};
default:
return state;
}
};
export default flow;
Action
import { UPDATE_ELEMENT } from './types';
export const updateElement = (data) => (dispatch) => {
dispatch({
type: UPDATE_ELEMENT,
payload: data,
});
};
Node modal
import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { updateElement } from '../../../../redux/actions/flow';
const VPCNodeModal = (props, { updateElement }) => {
const [formData, setFormData] = useState({
instance: '',
});
// options
const { instance } = formData;
const onFormChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmitForm = () => {
const update = {
id: selectedElement.id,
options: formData,
};
updateElement(update);
};
return (
<>
<Modal {...props}>
<form
onSubmit={(e) => {
e.preventDefault();
onSubmitForm();
}}
>
<label>
<span> Instance name:</span>
<input
type='text'
name='instance'
value={instance}
onChange={onFormChange}
/>
</label>
<button type='submit'>Submit</button>
</form>
</Modal>
</>
);
};
VPCNodeModal.propTypes = {
updateElement: PropTypes.func.isRequired
};
export default connect(null, { updateElement })(VPCNodeModal);
Issue is while receiving the props.
change
const VPCNodeModal = (props, { updateElement }) => {
to
const VPCNodeModal = (props) => {
const { updateElement } = props;
updateElement is a props was passes in VPCNodeModal. So you should update like this with spread operator
const VPCNodeModal = ({ updateElement, ...props }) => {
I'm trying to make todo app but,
Every time I input something, my redux developer tools shows an '' (empty string)
my reduce look like this
const TodoReducer = (state = initialState, action) => {
switch(action.type){
case 'ADD_TODO':
return {
...state,
todoList: [...state.todoList, action.item]
}
}
}
export default TodoReducer;
my action
export const addTodo = () => {
return {
type: 'ADD_TODO',
item: ''
}
}
and the App.js
function App() {
const [input, setInput] = useState('');
const todoList = useSelector(state => state.todoList)
const dispatch = useDispatch();
const addHandler = () => {
console.log(`adding ${input}`)
dispatch(addTodo({
item: input
}))
setInput('');
}
return (
<div>
<p>TODO</p>
<p>{todoList}</p>
<input type="text"
value={input}
onChange={e=> setInput(e.target.value)}
/>
<button type="button" onClick={addHandler}>Add</button>
</div>
);
}
thank you in advance. any help is appreciated
You forgot to pass the item as an input to the action and return
Change:
export const addTodo = () => {
return {
type: 'ADD_TODO',
item: ''
}
}
To:
export const addTodo = (item) => {
return {
type: 'ADD_TODO',
item: item
}
}
And
Change
<p>{todoList}</p>
To
<p>
{Array.isArray(todoList) &&
todoList.map((item, itemIndex) => (
<div key={itemIndex}>{item}</div>
))}
</p>
I have two separate components. I want to have a button that when clicked on will add an element to an array in my reducer and redirect to another component, this component that gets redirected to needs to render the data that was just added to the array. The page redirects to the component I want but the data does not load and the console.logs don't show anything.
This is the component that has the redirect button. On this component the console.log(socialNetworkContract.members[0]) shows the string I expect.
const Posts = () => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
window.location.href='/member'
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default Posts;
This is my reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
and this is the component that is redirected to. this just says undefined in console.log(socialNetworkContract.members[0])
const Member = () => {
const [user, setUser] = useState({});
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
useEffect(async()=>{
try {
const pro = socialNetworkContract.members[0]
console.log(socialNetworkContract.members[0])
await setUser(pro)
console.log(socialNetworkContract.members[0])
} catch (e) {
console.error(e)
}
}, [])
I have the route set in Routes.js as
<Route path="/member" exact component={Member} />
Use history.push('/') instead of window.location.href which will reload your whole page and you will lost your local state data.
const {withRouter} from "react-router-dom";
const Posts = (props) => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member');
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter( Posts );
I'm not a Javascript expert so I wondered if anyone has an "elegant" way to combine multiple reducers to create a global state(Like Redux). A function that does not affect performance when a state updating multiple components etc..
Let's say I have a store.js
import React, { createContext, useReducer } from "react";
import Rootreducer from "./Rootreducer"
export const StoreContext = createContext();
const initialState = {
....
};
export const StoreProvider = props => {
const [state, dispatch] = useReducer(Rootreducer, initialState);
return (
<StoreContext.Provider value={[state, dispatch]}>
{props.children}
<StoreContext.Provider>
);
};
Rootreducer.js
import Reducer1 from "./Reducer1"
import Reducer2 from "./Reducer2"
import Reducer3 from "./Reducer3"
import Reducer4 from "./Reducer4"
const rootReducer = combineReducers({
Reducer1,
Reducer2,
Reducer3,
Reducer4
})
export default rootReducer;
Combine slice reducers (combineReducers)
The most common approach is to let each reducer manage its own property ("slice") of the state:
const combineReducers = (slices) => (state, action) =>
Object.keys(slices).reduce( // use for..in loop, if you prefer it
(acc, prop) => ({
...acc,
[prop]: slices[prop](acc[prop], action),
}),
state
);
Example:
import a from "./Reducer1";
import b from "./Reducer2";
const initialState = { a: {}, b: {} }; // some state for props a, b
const rootReducer = combineReducers({ a, b });
const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(rootReducer, initialState);
// Important(!): memoize array value. Else all context consumers update on *every* render
const store = React.useMemo(() => [state, dispatch], [state]);
return (
<StoreContext.Provider value={store}> {children} </StoreContext.Provider>
);
};
Combine reducers in sequence
Apply multiple reducers in sequence on state with arbitrary shape, akin to reduce-reducers:
const reduceReducers = (...reducers) => (state, action) =>
reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state);
Example:
const rootReducer2 = reduceReducers(a, b);
// rest like in first variant
Combine multiple useReducer Hooks
You could also combine dispatch and/or state from multiple useReducers, like:
const combineDispatch = (...dispatches) => (action) =>
dispatches.forEach((dispatch) => dispatch(action));
Example:
const [s1, d1] = useReducer(a, {}); // some init state {}
const [s2, d2] = useReducer(b, {}); // some init state {}
// don't forget to memoize again
const combinedDispatch = React.useCallback(combineDispatch(d1, d2), [d1, d2]);
const combinedState = React.useMemo(() => ({ s1, s2, }), [s1, s2]);
// This example uses separate dispatch and state contexts for better render performance
<DispatchContext.Provider value={combinedDispatch}>
<StateContext.Provider value={combinedState}> {children} </StateContext.Provider>
</DispatchContext.Provider>;
In summary
Above are the most common variants. There are also libraries like use-combined-reducers for these cases. Last, take a look at following sample combining both combineReducers and reduceReducers:
const StoreContext = React.createContext();
const initialState = { a: 1, b: 1 };
// omit distinct action types for brevity
const plusOneReducer = (state, _action) => state + 1;
const timesTwoReducer = (state, _action) => state * 2;
const rootReducer = combineReducers({
a: reduceReducers(plusOneReducer, plusOneReducer), // aNew = aOld + 1 + 1
b: reduceReducers(timesTwoReducer, plusOneReducer) // bNew = bOld * 2 + 1
});
const StoreProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(rootReducer, initialState);
const store = React.useMemo(() => [state, dispatch], [state]);
return (
<StoreContext.Provider value={store}> {children} </StoreContext.Provider>
);
};
const Comp = () => {
const [globalState, globalDispatch] = React.useContext(StoreContext);
return (
<div>
<p>
a: {globalState.a}, b: {globalState.b}
</p>
<button onClick={globalDispatch}>Click me</button>
</div>
);
};
const App = () => <StoreProvider> <Comp /> </StoreProvider>
ReactDOM.render(<App />, document.getElementById("root"));
//
// helpers
//
function combineReducers(slices) {
return (state, action) =>
Object.keys(slices).reduce(
(acc, prop) => ({
...acc,
[prop]: slices[prop](acc[prop], action)
}),
state
)
}
function reduceReducers(...reducers){
return (state, action) =>
reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
If you simply want to achieve a combine reducer feature without any third-party library, do it as below. (REF: Redux source/code)
The working code is here https://codepen.io/rajeshpillai/pen/jOPWYzL?editors=0010
I have two reducers created, one dateReducer and another counterReducer. I am using it as
const [state, dispatch] = useReducer(combineReducers({
counter: counterReducer,
date: dateReducer
}), initialState);
The combineReducers code
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
Usage: Extract the respective state
const { counter, date } = state;
NOTE: You can add more redux like features if you wish.
The complete working code (in case codepen is down :))
const {useReducer, useEffect} = React;
function dateReducer(state, action) {
switch(action.type) {
case "set_date":
return action.payload;
break;
default:
return state;
}
}
function counterReducer(state, action) {
console.log('cr:', state);
switch (action.type) {
case 'increment': {
return state + 1;
}
case 'decrement': {
return state - 1;
}
default:
return state;
}
}
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
const initialState = {
counter: 0,
date: new Date
};
function App() {
const [state, dispatch] = useReducer(combineReducers({
counter: counterReducer,
date: dateReducer
}), initialState);
console.log("state", state);
const { counter, date } = state;
return (
<div className="app">
<h3>Counter Reducer</h3>
<div className="counter">
<button onClick={() =>
dispatch({ type: 'increment'})}>+
</button>
<h2>{counter.toString()}</h2>
<button onClick={() =>
dispatch({ type: 'decrement'})}>-
</button>
</div>
<hr/>
<h3>Date Reducer</h3>
{date.toString()}
<button className="submit"
type="submit"
onClick={() =>
dispatch({ type: 'set_date', payload:new Date })}>
Set Date
</button>
</div>
);
}
const rootElement = document.querySelector("#root");
ReactDOM.render(<App />, rootElement);
NOTE: This is a quick hack (for learning and demonstration purpose only)
There is a library called react combine reducer that is specifically use for combining reducer with the context api. Below is the code sample
import { useReducer } from 'react';
import combineReducers from 'react-combine-reducers';
const initialIdentity = {
name: 'Harry'
}
const initialLocation = {
country: 'UK',
city: 'London'
}
const identityReducer = (state, action) => {
switch (action.type) {
case 'ACTION_A':
return { ...state, name: 'Puli' };
default: return state;
}
}
const locationReducer = (state, action) => {
switch (action.type) {
case 'ACTION_B':
return { ...state, city: 'Manchester' };
default: return state;
}
}
const [profileReducer, initialProfile] = combineReducers({
identity: [identityReducer, initialIdentity],
location: [locationReducer, initialLocation]
});
const [state, dispatch] = useReducer(profileReducer, initialProfile);
console.log(state);
// Outputs the following state:
// {
// identity: {
// name: "Harry"
// },
// location: {
// country: "UK",
// city: "London"
// }
// }
In your rootReducer.js file you can use combineReducers from redux to combine multiple reducers. The traditional way is:
import { combineReducers } from 'redux';
const rootReducer = combineReducers({ name: nameReducer});
export default rootReducer;
You can import the rootReducer while creating the store as:
import { combineReducers } from 'redux';
let store = createStore(rootReducer);
While using useReducer hook you can pass the rootReducer to it:
const [state, dispatch] = useReducer(rootReducer, initialState);
Hope this works for you.
Instead of using useReducer use useCombineReducers() . may change this function to accept multiple parameters based on your requirement
const inti ={ count:0, alpha:''}
export function reducer1(state, action) {
switch (action.type)
{
case 'increment':
return {...state , count: state.count + 1};
case 'decrement':
return {...state , count: state.count - 1};
default:
return {count:0};
} }
export function reducer2(state, action) {
switch (action.type) {
case 'add':
return {...state , alpha: state.alpha + action.payload };
case 'rem':
return {...state , alpha: state.alpha + action.payload};
default:
return {alpha:''};
}}
function useCombineReducers(reducer1,reducer2, init) {
const [state,setState] = useState(init);
function dispatch(action)
{
let ns = null;
if(action.type == 'add' || action.type=="rem")
{
ns = reducer2(state,action)
}
else
{
ns = reducer1(state,action)
}
setState(ns);
}
return [state, dispatch];}
function App() {
const [state,dispatch] = useCombineReducers(reducer1,reducer2,inti);
return (
<>
<Provider >
<Counter state ={state} dispatch={dispatch}></Counter>
<Alpha state ={state} dispatch={dispatch}></Alpha>
</Provider>
</>
); }
const Counter = (props) => {
return (
<div style ={{Border:'10px', width:'20px'}}>
Count : {props.state.count}
<button onClick={()=> props.dispatch({type: 'increment'})}> + </button>
<button onClick={()=> props.dispatch({type: 'decrement'})}> - </button>
</div>
)} export default Counter
const Alpha = (props) => {
return (
<div style ={{Border:'10px', width:'20px'}}>
Alpha : {props.state.alpha}
<button onClick={()=> props.dispatch({type: 'add',payload:'+'})}> + </button>
<button onClick={()=> props.dispatch({type: 'rem',payload:'-'})}> - </button>
</div>
)} export default Alpha
I played around a bit and were thinking about the problem as I had to handle it as well.
This might not be the best approach but I just defined my reducers as objects with key: reducer function combinations:
const counterRed = {
increment: (oldState, action) => ({
...oldState,
counter: oldState.counter + 1
}),
decrement: (oldState, action) => ({
...oldState,
counter: oldState.counter - 1
})
};
and
const dateRed = {
set_date: (oldState, action) => ({ ...oldState, date: action.payload })
};
and I combined them like this:
const reducer = (oldState, action) => {
const combinedReducers = { ...dateRed, ...counterRed };
let newState = null;
if (combinedReducers[action.type]) {
newState = combinedReducers[action.type](oldState, action);
}
if (newState) {
return { ...newState };
}
return oldState;
};
a working example can be seen here: https://codesandbox.io/s/jovial-kowalevski-25pzf?file=/src/App.js