I have an icon that when clicked it triggers a function that calls an API and then dispatch an action to remove a blog post from the state. But the problem is that my UI does not re-render. However, if I refresh my browser the post that I deleted is no longer there and my store matches my state.
Here is my function that calls an API and then dispatch an action:
export function deletePost(postID) {
return (dispatch) => {
fetch(`${url}/posts/${postID}`, { method: 'DELETE', headers})
.then((response) => response.json())
.then((postID) => dispatch(removePost(postID)))
.catch((error)=>{console.log('dispatch error',error)});
};
Here is my action:
export function removePost ( postID ) {
return {
type: REMOVE_POST,
postID,
}
}
And here is my reducer:
function posts (state = {}, action) {
switch (action.type) {
case REMOVE_POST:
return [
...state.filter((post)=>post.id!==action.postID)
];
default:
return state
}
}
Now when I simply dispatch an action without calling an API
export function deletePost(postID) {
return (dispatch) => {
dispatch(removePost(postID));
}
My state is correctly updated but of course my redux store is not. When I do the calling of API before dispatching an action as shown earlier, there is also no error coming from the console log.
What could be the problem here? I am very new to ReactJS and can't find a solution yet to this problem after many tries.
Also, as a note, I am using redux-thunk in this project.
I have a few questions, but first: I think the problem is here:
[
...state.filter((post)=>post.id!==action.postID)
]
What is the state's shape? Is it state = [post1, post2, ...]? I can see the initial state is {}, so I find it weird to be calling state.filter and not state.posts.filter or whatever here.
The other might be problem, is with post.id !== action.postID, maybe the received ID is an number type, and maybe the local id is a string? Maybe the other way around?
Related
I have a array of objects kept in my state, I want to be able to edit one of the objects in the array and update the state.
However, I cannot seem to update anything with the state except push more items into it.
I am using #reduxjs/toolkit and the createSlice() method for my reducers.
Here is my slice, it has some logic to pull the initial state array from an API.
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import { BACKEND_API } from "../../utilities/environment";
import fetchViaApi from "../../utilities/fetchViaApi";
export const getInitialDashboards = createAsyncThunk(
'dashboard/getDashboards',
async () => {
const response = await fetchViaApi('/dashboards', {
baseUrl: BACKEND_API,
method: "GET"
});
const data = await response.json();
return data;
}
)
const initialState = [];
const dashboardsSlice = createSlice({
name: 'dashboards',
initialState,
reducers: {
setDashboards: (state,action) => {
state = action.payload;
},
updateDashboard: (state,action) => {
// state.push(action.payload);
state = [...state.slice(0, 5)];
},
deleteDashboard: (state, action) => {
},
},
extraReducers: builder => {
builder.addCase(getInitialDashboards.fulfilled, (state, action) => {
action.payload.forEach(element => {
state.push(element);
});
})
}
});
export const { setDashboards, updateDashboard, editDashboard, deleteDashboard } = dashboardsSlice.actions;
export default dashboardsSlice.reducer;
The commented out state.push(action.payload) works fine, but sometimes I don't want to add new object to the array, but edit existing ones.
My thought was to slice the existing element out and add the new version back to the array. But I cannot slice the state.
I am using Redux DevTools in Chrome and watching the state not change after calling updateDashboard, there were 10 elements after getDashboards is completed.
You had the right idea, but your reducers need to be returning the new state, not assigning it.. e.g.
reducers: {
setDashboards: (state,action) => {
return action.payload;
},
updateDashboard: (state,action) => {
return [...state.slice(0, 5)];
},
deleteDashboard: (state, action) => {
return [];
},
},
The issue is that state = anything is not a valid way to update data with Immer. It's not mutating the existing state, and it's not returning a new value - it just points the local state variable to something else, so Immer has no way to know that anything changed.
If you want to replace the existing state entirely, do return newStateValue. If you want to update part of the state, then mutate a nested field or value.
See the Writing Reducers with Immer page in the RTK docs for more details.
I faced a similar problem today. Updating or assigning values to the state directly is not working. But updating the properties inside the state variable works
I would add a property named dashboards to the state and update it instead of updating the state directly in reducer
Redux toolkit is using immer under the hood. It might be helpful to take a look at immer and get an idea to mutate the state
In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);
First, I want to call the action 'CLICK_SEARCH' to get the response from the server
then pass the response to 'CLICK_SEARCH2' to update the store.
But its failure, the console result showing 'CLICK_SEARCH' instead of 'CLICK_SEARCH2' and action.payload is undefined. Anyone help? thx
function reducreForSeach(state = initialState3, action) {
if (typeof state === 'undefined') {
return 0
}
switch(action.type) {
case 'CLICK_SEARCH': {
axios.post('abc/url', {
responseType: 'document',
headers: { 'X-Requested-With': 'XMLHttpRequest'},})
.then(function(response) {
dispatch({
type: 'CLICK_SEARCH2',
payload: response.data});
}
case 'CLICK_SEARCH2': {
console.log(action.type);
console.log(action.payload);
}
}
Have you tried declaring constants for action types? Simple as this one:
const CLICK_SEARCH = 'CLICK_SEARCH';
const CLICK_SEARCH2 = 'CLICK_SEARCH2';
function reducreForSeach(state = initialState3, action) {
//...other parts of the code
switch(action.type) {
case CLICK_SEARCH: {
//...
dispatch({ type: CLICK_SEARCH2,
//...
case CLICK_SEARCH2: {
//...
dispatch is never defined or fed to the function reducreForSeach, so it will always fail to call CLICK_SEARCH2.
Chaining actions are not directly supported by Redux. You can implement by yourself, but I suggest you to look into popular libraries like redux-thunk or redux-saga to learn how to call actions based on other action's result. They are all well-documented and recommended in Redux's official website
You don't want to do such things in reducers as those are meant to be pure functions.
You can dispatch other actions from within action generators by using redux-thunk which enables you to return a function from your action generators.
The function you return will be called with dispatch as its props and allows you to further dispatch actions, from within the callback.
e.g.
export const mySearchActionGenerator = keyword => dispatch => {
dispatch ({
type: actionTypes.MY_SEARCH_ACTION
keyword
});
dispatch ({
type: actionTypes.MY_SEARCH_ACTION
keyword
});
}
Another option would be using redux-saga which allows far more complex setups.
I have an action dispatcher that picks up comments from a blog post, straight from an API server first then dispatch an action.
export function fetchPostComments(postID) {
return (dispatch) => {
fetch(`${url}/posts/${postID}/comments`, { method: 'GET', headers})
.then((response) => response.json())
.then((comments) => {console.log("action ", comments); dispatch(getPostComments(comments))})
.catch((error)=>{console.log('fetch comments error',error)});
};}
I console.logit as you see above, and it gives the result I am looking for, as shown below.
And here is how I dispatch an action.
export function getPostComments (comments) {
return {
type: GET_POST_COMMENTS,
comments
}
}
And now here is my reducer:
function comments (state = {}, action) {
switch (action.type) {
case GET_POST_COMMENTS:
console.log("Reducer comments ", action.comments);
return action.comments;
default :
return state
}
}
If you observe again, I use a console.log to confirm the result and again I have the correct result that I desired as shown below:
Now, in one of my component, I display the blog post information and a link to view comments. Once the user click the view comments, it triggers the calling of the action dispatcher.
<div className="row">
<a onClick={this.handleFetchComments} href=""><span className="float-date"><strong style={{fontWeight:'bold'}}>View Comments</strong></span></a>
</div>
<div>
<div>
<span>Count : { this.state.comments.length }</span>
</div>
<textarea placeholder="Add your comments here"></textarea>
<input type="submit" value="+Add comment" />
</div>
This is the handler to fetch comments:
handleFetchComments = (e) => {
e.preventDefault();
const comments = this.props.getAllComments(this.props.postID);
console.log("Comments fetch ", comments);
setInterval(this.loadComments(comments),1000);
}
loadComments(comm) {
console.log("Before ", this.state.comments);
this.setState(()=>({comments:comm}));
console.log("After ", this.state.comments);
}
I put some console.log just to check the result and this is the result which I get, which is wrong and not compatible with the result produced by the reducer a moment ago. It gives me an undefined and empty result from a dispatch action, as shown in the result below:
Meanwhile, my Redux Dev tools every time I clicked the View Comments link, it shows up the correct data that I desired and triggers the dispatch action as shown below.
This is my state by the way on my Component, that has the link to View Comments:
state = {
post: [],
hasClickComments : false,
comments: []
}
This is my mapStateToProps in my App.js:
function mapStateToProps ({posts, comments, categories}) {
return {
posts: posts,
comments: comments,
categories: categories,
}
}
And this is my mapDispatchToProps:
function mapDispatchToProps (dispatch) {
return {
getAllComments: (postID) => dispatch(fetchPostComments(postID)),
}
}
I have other actions that I dispatch also mapDispatchtoProps and it's all working so far, except for this one.
I am new to React and Redux, and I am looking for the solution for this one almost two days but could not figure it out. What could have gone wrong?
You mix React Component's State and props.State And Props
after mapStateToProps all the data become the component's props.
and you should use this.props.comments to get your data!
function comments (state = {}, action) {
switch (action.type) {
case GET_POST_COMMENTS:
console.log("Reducer comments ", action.comments);
return {
...state,
comments: action.comments;
}
default :
return state
}
}
If your comment data is in redux then might be the problem is the way you are using mapStateToProps.
I suggest you to use debugger in function and find what you are getting in state.
function mapStateToProps (state) {
debugger
//return {
// posts: posts,
// comments: comments,
// categories: categories,
//}
}
now look at the data in state on the console.
then assign the data to required variables.
I am wondering how folks using Redux are approaching their backend persistence. Particularly, are you storing the "actions" in a database or are you only storing the last known state of the application?
If you are storing the actions, are you simply requesting them from the server, then replaying all of them when a given page loads? Couldn't this lead to some performance issues with a large scale app where there are lots of actions?
If you are storing just the "current state", how are you actually persisting this state at any given time as actions happen on a client?
Does anyone have some code examples of how they are connecting the redux reducers to backend storage apis?
I know this is a very "it depends on your app" type question, but I'm just pondering some ideas here and trying to get a feel for how this sort of "stateless" architecture could work in a full-stack sense.
Thanks everyone.
Definitely persist the state of your reducers!
If you persisted a sequence of actions instead, you wouldn't ever be able to modify your actions in your frontend without fiddling around inside your prod database.
Example: persist one reducer's state to a server
We'll start with three extra action types:
// actions: 'SAVE', 'SAVE_SUCCESS', 'SAVE_ERROR'
I use redux-thunk to do async server calls: it means that one action creator function can dispatch extra actions and inspect the current state.
The save action creator dispatches one action immediately (so that you can show a spinner, or disable a 'save' button in your UI). It then dispatches SAVE_SUCCESS or a SAVE_ERROR actions once the POST request has finished.
var actionCreators = {
save: () => {
return (dispatch, getState) => {
var currentState = getState();
var interestingBits = extractInterestingBitsFromState(currentState);
dispatch({type: 'SAVE'});
window.fetch(someUrl, {
method: 'POST',
body: JSON.stringify(interestingBits)
})
.then(checkStatus) // from https://github.com/github/fetch#handling-http-error-statuses
.then((response) => response.json())
.then((json) => dispatch actionCreators.saveSuccess(json.someResponseValue))
.catch((error) =>
console.error(error)
dispatch actionCreators.saveError(error)
);
}
},
saveSuccess: (someResponseValue) => return {type: 'SAVE_SUCCESS', someResponseValue},
saveError: (error) => return {type: 'SAVE_ERROR', error},
// other real actions here
};
(N.B. $.ajax would totally work in place of the window.fetch stuff, I just prefer not to load the whole of jQuery for one function!)
The reducer just keeps track of any outstanding server request.
function reducer(state, action) {
switch (action.type) {
case 'SAVE':
return Object.assign {}, state, {savePending: true, saveSucceeded: null, saveError: null}
break;
case 'SAVE_SUCCESS':
return Object.assign {}, state, {savePending: false, saveSucceeded: true, saveError: false}
break;
case 'SAVE_ERROR':
return Object.assign {}, state, {savePending: false, saveSucceeded: false, saveError: true}
break;
// real actions handled here
}
}
You'll probably want to do something with the someResponseValue that came back from the server - maybe it's an id of a newly created entity etc etc.
I hope this helps, it's worked nicely so far for me!
Definitely persist the actions!
This is only a counterexample, adding to Dan Fitch's comment in the previous answer.
If you persisted your state, you wouldn't ever be able to modify your state without altering columns and tables in your database. The state shows you only how things are now, you can't rebuild a previous state, and you won't know which facts had happened.
Example: persist an action to a server
Your action already is a "type" and a "payload", and that's probably all you need in an Event-Driven/Event-Sourcing architecture.
You can call your back-end and send the actions inside your actionCreator (see Dan Fox's answer).
Another alternative is to use a middleware to filter what actions you need to persist, and send them to your backend, and, optionally, dispatch new events to your store.
const persistenceActionTypes = ['CREATE_ORDER', 'UPDATE_PROFILE'];
// notPersistenceActionTypes = ['ADD_ITEM_TO_CART', 'REMOVE_ITEM_FROM_CART', 'NAVIGATE']
const persistenceMiddleware = store => dispatch => action => {
const result = dispatch(action);
if (persistenceActionTypes.indexOf(action.type) > -1) {
// or maybe you could filter by the payload. Ex:
// if (action.timestamp) {
sendToBackend(store, action);
}
return result;
}
const sendToBackend = (store, action) => {
const interestingBits = extractInterestingBitsFromAction(action);
// déjà vu
window.fetch(someUrl, {
method: 'POST',
body: JSON.stringify(interestingBits)
})
.then(checkStatus)
.then(response => response.json())
.then(json => {
store.dispatch(actionCreators.saveSuccess(json.someResponseValue));
})
.catch(error => {
console.error(error)
store.dispatch(actionCreators.saveError(error))
});
}
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
createStore(
yourReducer,
aPreloadedState,
applyMiddleware(thunk, persistenceMiddleware)
)
(You can also use a middleware to send current state to the backed. Call store.getState().)
Your app already knows how to transform actions into state with reducers, so you can also fetch actions from your backend too.