I would like to update Redux State on the basis of one action, as follows:
export const editAction = (itemType, itemContent, id) => (dispatch) => {
return axios.put(`${url}/${itemType}/${id}`, {
itemType,
...itemContent,
})
.then(({ data }) => {
console.log(data);
dispatch({
type: EDIT_SUCCESS,
itemType,
data,
id,
});
})
};
I omitted catch block to shorten source code.
What return should I use to update Redux State in reducer after EDIT_SUCCESS action type?
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_SUCCESS:
return {
...state,
[action.itemType]: [...action.data],
};
case ADD_SUCCESS:
return {
...state,
[action.itemType]: [...state[action.itemType], action.data],
};
case EDIT_SUCCESS:
return {
...state,
// ???,
};
case DELETE_SUCCESS:
return {
...state,
[action.itemType]: [...state[action.itemType].filter(item => item._id !== action.id)],
};
}
};
It is quite clear to me how to implement FETCH, ADD and DELETE because I done it (as you can see) but I have no idea how to implement return for EDIT_SUCCESS.
But importantly, after run editAction() (submit button in Formik), editing object is updated (by axios) in the database correctly but State in Redux DevTools as well as view in a browser remains the same. Only when I refresh page, I see the difference.
If you have edited an element, you should look for it and update it.
Something like:
const newState = {...state};
const indexOfElementToUpdate = newState[action.itemType].findIndex(item => item._id === action.id);
newState[action.itemType][indexOfElementToUpdate] = action.data;
return newState;
Related
I wonder how I can arrange likes for items in a redux or (in my case context) store. I dont know how I can do it without changing the item (that I receive from backend) in the store (to add a possible like to each item)
So far Im doing the following check inside my component:
const SearchResult () => {
const {state: {savedList}} = useContext(SearchContext);
const [isLiked, setIsLiked] = useState(false);
useEffect(() => {
return savedList.find(item => {
if (item._id === searchResult._id) setIsLiked(true);
else setIsiked(false);
})
};
}, []);
...
}
reducer
const likeReducer = (state, action) => {
switch (action.type) {
case 'FETCH_SAVED_ITEMS_SUCCESS':
return {
...state,
isFetching: false,
savedlist: action.payload
}
case 'LIKE_SUCCESS':
return {
...state,
savedlist: action.payload //updated list
}
default:
return state;
}
};
action
const like = dispatch => async (product) => {
dispatch({type: 'LIKE'})
try {
await API.like(product)
dispatch({type: 'LIKE_SUCCESS', payload: product})
} catch(error) {
dispatch({type: 'LIKE_ERROR', payload: error.message})
}
};
I have 2 questions on managing the state for my app.
Part 1: I can't update and set the entire state with my reducer, when I fetch the data from the server, but can change a part of that state:
state.main_meals = action.payload - works
state = action.payload - doesn't work
Part 2: I have heard that you shouldn't be using pure "setter" functions in redux, but how else am I supposed to set my state?
here is my reducer:
setDiaryState: (state, action) => {
state = action.payload;
},
and my component:
const diary = useAppSelector((state) => state.diary);
const dispatch = useAppDispatch();
const [user, loading, error] = useAuthState(auth);
useEffect(() => {
{
user &&
db
.collection("users")
.doc(`${auth.currentUser?.uid}`)
.collection("diaryState")
.doc("currentState")
.onSnapshot((doc) => {
const data = doc.data();
dispatch(setDiaryState(data));
});
}
}, [user]);
Your reducer must always return a immutable state here,
state = action.payload;
You are clearly mutating it which won't trigger a re-render,this can be fixed by simply add a return statement.
This would replace your entire state by action.payload.
setDiaryState: (state, action) => {
return {...action.payload};
},
if you wish to modify a subset of your state you can use:
setDiaryState: (state, action) => {
return {...state, main_meals:action.payload};
},
This way you ensure that your state is always immutable.
I'm learning to use useReducer. I wrote in react input which, after clicking the button, adds an element to the table and displays on the page, I also want to add a logic that will not allow adding two the same elements with the same content to the table, but unfortunately it does not work, please help. console.log works, but adds an item to the array anyway
const tab = []
const App = () => {
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'ADD':
for (const n of state) {
if (n.name === action.course.name) {
console.log('repeated text')
return
}
}
return [...state, action.course]
}
}, tab)
Hard to test this without your specific data, but this should work:
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'ADD':
if(state.find(i => i.name === action.course.name))
return state
else
return [...state, action.course]
}
}, tab)
I'll spare you the broader context as it's pretty simple. Using React hooks, the first dispatch here fires automatically and works just fine, but the second one doesn't. Context is imported from another file, so I assume it's a (lowercase) context issue, but I don't know how to fix it?
const Component = props => {
const [, dispatch] = useContext(Context);
useEffect(() => {
document.title = state.title;
// eslint-disable-next-line react-hooks/exhaustive-deps
});
// this works
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
function onAddClick() {
// this doesn't work
dispatch({ type: "UPDATE_TITLE", payload: "CLICKED!" });
}
return (
<div>
<AddButton onClick={onAddClick} />
</div>
);
};
Here's the parent.
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
state["title"] = action.payload;
return state;
default:
return state;
}
};
const initialState = {
title: "My Title"
};
export const Context = createContext(initialState);
const App = () => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
<Component />
</Context.Provider>
);
};
export default App;
Console logs fire in the correct reducer case in both cases, but only the one marked 'this works' will actually update the state properly, the other one fails silently.
Fixed: https://codesandbox.io/s/cranky-wescoff-9epf9?file=/src/App.js
I don't know what you trying to achieve by placing the dispatch outside controlled env (like an event or useEffect):-
// this works
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
// but it will run in infinite loop tho (no changes can be made then)
So the fixes should be:-
in Reducer, make sure not to completely mutate your state:-
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
// Don't do this
// let newState = state;
// newState["page"]["title"] = action.payload;
// console.log("updating", newState, newState.page.title);
// return newState;
// Do this instead
return {
...state,
page: {
...state.page,
title: action.payload
}
};
default:
return state;
}
};
Place your dispatch for not Click! inside an event or function or better yet in this case, useEffect since you wanna apply it once the component rendered.
const Demo = props => {
const [state, dispatch] = useContext(Context);
useEffect(() => {
document.title = state.title;
// eslint-disable-next-line react-hooks/exhaustive-deps
// this works (should be here)
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
}, []); // run it once when render
// this works but, (should not be here tho - cause it will run non-stop)
// dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
function onAddClick() {
// this will work now
dispatch({ type: "UPDATE_TITLE", payload: "CLICKED!" });
}
return (
<div>
<button onClick={onAddClick}>Click Me!</button>
<p>State now: {state.title}</p>
</div>
);
};
You can try and refer to this sandbox and see how it works.
EDITED & UPDATED sandbox
It looks like you are attempting to mutate state directly. Instead try to return a new object that is the result of the changes from the action applied to the old state.
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
return {
...state,
page: {
...state.page,
title: action.payload
}
};
default:
return state;
}
};
Alternatively, use produce from immerjs to give you the ability to write your reducer in this mutable style.
import produce from "immer";
const Reducer = produce((state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
state["title"] = action.payload;
break;
default:
return;
}
});
I am updating my redux state, and the state doesn't seem to be getting mutated, however the DOM is still not refreshing.
//update filters for events
setFilters = (name) => async () => {
const {onSetActiveEventTypes, authUser} = this.props;
let array = this.props.activeEventTypes
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
await Promise.resolve(onSetActiveEventTypes(array));
}
render() {
return <Accordion title="Filters" collapsed>
{
(this.props.eventTypes && this.props.activeEventTypes ?
<EventFilter eventTypes={this.props.eventTypes} activeEventTypes={this.props.activeEventTypes} action={this.setFilters}/>
: '')
}
</Accordion>
}
const mapStateToProps = (state) => ({
eventTypes: state.eventsState.eventTypes,
activeEventTypes: state.eventsState.activeEventTypes
});
const mapDispatchToProps = (dispatch) => ({
onSetEventTypes: (eventTypes) => dispatch({ type: 'EVENT_TYPES_SET',
eventTypes }),
onSetActiveEventTypes: (activeEventTypes) => dispatch({ type:
'ACTIVE_EVENT_TYPES_SET', activeEventTypes })
});
const authCondition = (authUser) => !!authUser;
export default compose(
withAuthorization(authCondition),
connect(mapStateToProps, mapDispatchToProps)
)(DashboardPage);
I have placed my code in my component above, it should be all that is needed to debug. I will put the reducer below
const applySetEventTypes = (state, action) => ({
...state,
eventTypes: action.eventTypes
});
const applySetActiveEventTypes = (state, action) => ({
...state,
activeEventTypes: action.activeEventTypes
});
function eventsReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case 'EVENT_TYPES_SET' : {
return applySetEventTypes(state, action);
}
case 'ACTIVE_EVENT_TYPES_SET' : {
return applySetActiveEventTypes(state, action);
}
default : return state;
}
}
export default eventsReducer;
Above is my reducer, I think I am following the correct patterns for managing redux state and maintaining immutability. What am I missing?
setFilters is a method that the checkboxes use to update active filters compared to all the filters available.
You are definitely mutating state:
const {onSetActiveEventTypes, authUser} = this.props;
let array = this.props.activeEventTypes
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
That mutates the existing array you got from the state, and then you are dispatching an action that puts the same array back into the state. So, you are both A) reusing the same array all the time, and B) mutating that array every time.
The approaches described in the Immutable Update Patterns page in the Redux docs apply wherever you are creating new state values, whether you're generating the new state in a reducer based on a couple small values, or before you dispatch the action.
//update filters for events
setFilters = (name) => async () => {
const {onSetActiveEventTypes, authUser} = this.props;
let array = []
this.props.activeEventTypes.map((type) =>{
array.push(type)
})
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
//use this once server sending active filters
// await eventTable.oncePostActiveEventTypes(authUser.email, array).then( data
=> {
// Promise.resolve(onSetActiveEventTypes(data));
// })
await Promise.resolve(onSetActiveEventTypes(array));
}