How to append to Redux state - reactjs

I'm trying to append to my redux state, but having an issue understanding where I'm wrong. At the moment, I'm getting an error state is not iterable.
reducer
const CURRENT_USER = "CURRENT_USER";
const reducer = (state = [], action) => {
switch(action.type) {
case CURRENT_USER:
const currentUser = [
...state,
action.payload
];
return currentUser;
default:
return state;
}
}
export default reducer;
dispatch
posts.forEach((post: BlogPost) => {
if (post.userId === currentUserPosts) {
dispatch({
type: "CURRENT_USER",
payload: { ...post },
});
}
});

So from the reducer after updating the state you have to return the new state
so as I see
case CURRENT_USER:
return {
currentUser: [...state, action.payload]
}
.....
You are returning an object {currentUser: [..} this will be the new state after the action happens
so again if you call this, state is an object ({currentUser: [..}) if you try to treat that as iterable (array) you will get this error
[...{currentUser: []}]

Related

redux dispatch action on every element of an array

I have an array of x element
Arr (2) ['16', '149']
I need to dispatch an action on every Id in this array in order to find Object by Id.
My reducer for single Id is working and it is returning me value. But im lost.
This is my reducer
const initialState = {
product: {},
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case GET_PRODUCT_BY_ID:
return {
...state,
product: action.payload,
};
default:
return state;
}
};
my action:
export const getProductById = (Id)=>async(dispatch)=>{
try {
const {data}= await api.getProductById(Id)
dispatch({type:GET_PRODUCT_BY_ID, payload: data})
} catch (error) {
console.log(error)
}
}
I would like to return something like array of objects with values for that id. Im stuck with this.

Setting the initial state of redux in reducers

Hey guys i am stuck in a situation in which i have to set the inital state of reducer to some value let me show you the code
First of all i have an action creater like this
export const fetchuser = () => {
return async dispatch => {
const res = await axios.get("/api/currentuser");
dispatch({
type: "fetchuser",
payload: res.data
});
};
};
which just fetches the data from api and dispatches an action to reducer
export default function(state = {}, action) {
switch (action.type) {
case "fetchuser":
return action.payload||false;
default:
return state;
}
}
now in second action creater i have to make a post request and increase the "credits" value in user database
export const handletoken = token => {
return async dispatch => {
const res = await axios.post("/api/stripe", token);
dispatch({ type: "credits", payload: res.data });
};
};
so i get the updated value here then i pass this on to the reducer
export default function(state = {}, action) {
switch (action.type) {
case "credits":
return action.payload
default:
return state;
}
}
and then combine them in reducer/index.js
export default combineReducers({
auth: authreducer,
credits:creditsreducer
});
console log of auth reducer in app.js in mapstatetoprops function gives
auth:
credits: 40
googleid: "109463598810933991924"
__v: 0
_id: "5d7fff2c4cb0604139055ce4"
so in credits reducer as u can see i have defined initial value of state as an empty object but i want to set it as the value of credits key of auth reducer, I could easily set it to array or an object hardcoding it but here i need to set its value as a value which is already in my another reducer so how can i achieve this ?
Assuming you need to wait for "fetchuser" to succeed to set credits in your creditsreducer you can handle the "fetchuser" action in your creditsreducer as well:
export default function(state = {}, action) {
switch (action.type) {
case "fetchuser":
return action.payload ? action.payload.credits : state;
case "credits":
return action.payload
default:
return state;
}
}
Always keep previous reducer state value. Otherwise no use of redux state value. like this
1.export default function(state = {}, action) {
switch (action.type) {
case "fetchuser":
let data = action.payload||false;
return {
...state,
fetchuser: data //any where you can access fetchuser data as well as previous state will not change.
}
default:
return state;
}
}
Change all the reducers like above.

Dispatching function does not "always" update the props

In my sample todo list app, I am trying to update all todos after creating or removing one so I can re-render the list component after changes. It sometimes updates the todos, but sometimes does not. I could not figure out the reason.
I am using Redux and Thunk. When a button is clicked, I dispatch "Create requested", then make async request, then dispatch "Create received", then dispatch fetchAllTodos(), and then catch "Create failed".
In the HomePage container, I pass this.props.todos to TodoList component as props. The problem I have is that it sometimes re-renders the list component with the changes, but sometimes does not. I also figured out in componentWillReceiveProps method that I do not get the updated props. Could this be related to a server issue or is it something I am doing wrong?
Here is how I set up the store:
const middlewares = [thunk];
const store = createStore(
combineReducers({
...reducers,
}),
compose(applyMiddleware(...middlewares))
);
Here is the fetchAllTodos() method:
export function fetchAllTodos() {
return dispatch => {
dispatch({
type: ALL_TODOS_REQUESTED
});
console.log('todos requested');
return SuperFetch.get('/todos')
.then(todos => {
console.log('todos received ' + todos.length);
dispatch({
type: ALL_TODOS_RECEIVED,
payload: todos
})
})
.catch(error => dispatch({
type: ALL_TODOS_FAILED,
payload: error
}));
}
Here is createTodo() method:
export function createTodo(description) {
const body = {description: description};
return dispatch => {
dispatch({
type: CREATE_TODO_REQUESTED
});
return SuperFetch.post(`/todos`, body)
.then(todo => dispatch({
type: CREATE_TODO_RECEIVED,
payload: todo
}))
.then(dispatch(fetchAllTodos()))
.catch(error => dispatch({
type: CREATE_TODO_FAILED,
payload: error
}));
}
}
Here is the reducer:
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ALL_TODOS_REQUESTED:
state = Object.assign({}, state, {todosLoading: true, todosError: null});
break;
case ALL_TODOS_RECEIVED:
state = Object.assign({}, state, {todos: action.payload, todosLoading: false, todosError: null});
break;
case ALL_TODOS_FAILED:
state = Object.assign({}, state, {todos: null, todosLoading: false, todosError: action.payload});
break;
// rest of it
return state
In HomePage, I just map state to props, and then pass the todos if they exist:
render() {
const {todos, error, loading} = this.props;
// if error part
if (todos && !loading) {
return (
<React.Fragment>
<Container text>
<TodoInput/>
</Container>
<Container style={{marginTop: '1em'}} text>
<TodoList todos={todos}/>
</Container>
</React.Fragment>
);
}
Might the problem be related to the fact that I am dispatching the action in TodoInput component and trying to update the TodoList? If so, how can I fix the problem because I do not want to dispatch all actions on the same HomePage container.
The problem is the way you manage state in your reducers. What you are doing is directly mutating an existing state, by doing state = blah which is against redux-priniciples. In order for redux to effectively recognize that a change has been made to the reducer, you must return a brand-new state object. Only then will your connected components re-render with the updated reducer data.
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ALL_TODOS_REQUESTED:
return {
...state,
todosLoading: true,
todosError: null
}
case ALL_TODOS_RECEIVED:
return {
...state,
todos: action.payload,
todosLoading: false,
todosError: null
}
case ALL_TODOS_FAILED:
return {
...state,
todos: null,
todosLoading: false,
todosError: action.payload
}
default:
return state
}
}
Problems is how you are updating the state in your reducer :
if todos is Object
state = {...state, {todos: {...action.payload, ...state.todos }, todosLoading: false, todosError: null}}
if todos is list
state = {...state, {todos: state.todos.concat(action.payload) }, todosLoading: false, todosError: null}}

What is the different when we using state and reducer in mapStateToProps. What is the correct method?

I have seen that lot of tutorials were using this method in their mapStateToProps method.
(state.ReducerName)
const mapStateToProps = (state) => {
return {
invoices: state.IcmWebReducer,
}
};
But this didn't work for me. What was working for me was this
(state.objectParameterName)
const mapStateToProps = (state) => {
return {
params: state.params,
invoices: state.invoices
}
};
My reducer as below
const initialState = {
invoices : [],
params: {
status: 'Pending',
_sort: 'documentInfo.dueDate',
_order: 'desc',
q: ''
}
};
const IcmWebReducer = (state = initialState, action) =>{
switch (action.type){
case 'UPDATE_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices
});
case 'UPDATE_PARAMS':
return Object.assign({}, state, {
params: action.params
});
default:
return state;
}
};
export default IcmWebReducer;
What is the correct method ? What is the different between this ?
(state.objectParameterName) is the correct method.
mapStateToProps is for using the redux state as props in the connected component. The reducer is a pure function, it just takes some value and returns it. The reducer updates and returns the states based upon the action type fired.
Even if you somehow define the reducer in the redux state and try to use it, it's a wrong practice.

How do I return an array from a reducer?

I have an array of items. When the items update I dispatch an UPDATED_LIST action and pass along the item with its updated data.
For example:
const initialState = {
items: []
}
const reducer = items(state = initialState, action) {
switch(action.type) {
case 'UPDATED_ITEMS':
return { ...state, ...action.payload }
default:
return state
}
}
I dispatch like so:
store.dispatch({
type: 'UPDATED_ITEMS',
payload: [ { name: "bob"}, { name: "harry" } ]
})
And mapStateToProps:
const mapStateToProps = state => ({ items: state.items })
My problem is when I try to access items from within a component it's an object instead of an array. I have to do the following to get access to the array:
const mapStateToProps = state => ({
items: Object.keys(state.offers).map((k) => state.items[k])
})
Is it possible to get the items as an array without having to convert them?
In your reducer update it to where you set items with the action payload. You were previously using the spread operator on your action payload which converts all your array indexes into the state object as keys.
const reducer = items(state = initialState, action) {
switch(action.type) {
case 'UPDATED_ITEMS':
return { ...state, items: [...action.payload] }
default:
return state
}
}
If you don't want a nested state in your mapStateToProps you can do this where you make your initial state an array. Similar to the todo reducer shown here. https://redux.js.org/basics/example-todo-list#reducerstodos.js
const initialState = [];
const reducer = items(state = initialState, action) {
switch(action.type) {
case 'UPDATED_ITEMS':
return [ ...action.payload ];
default:
return state
}
}
const mapStateToProps = state => ({
items: state.items
})

Resources