I'm a newbie in redux and es6 syntax. Here the problem:
There is an app with multiple posts.
const initialState = {
items: {
3: {title: '1984'},
6: {title: 'Mouse'},
19:{title: 'War and peace'}
}
}
App receive an array of liked posts ids:
dispatch(receiveLikedPosts(3, {id:3, ids: [3,6]}));
function receiveLikedPosts(ids) {
return {
type: LIKED_POSTS_RECEIVED,
ids
};
}
There is a posts reducer:
function posts(state = initialState, action) {
switch (action.type) {
case LIKED_POSTS_RECEIVED:
// here I need to update my posts state: post.liked => true (only 3 and 6 post)
default:
return state;
}
}
1) I have to update my reducers LIKED_POSTS_RECEIVED code. Dunno how to make it in right way.
2) Is it correct to dispatch events multiple times? ( one dispatch for each liked post)
Here the code:
// action
let ids = [3,6]
for (let id of ids) {
dispatch({type: LIKE, id});
}
// reducers
function post(state, action) {
switch (action.type) {
case LIKE:
return Object.assign({}, state, {
liked: true
});
default:
return state;
}
}
function posts(state = initialState, action) {
switch (action.type) {
case LIKE:
return Object.assign({}, state, {
[action.id]: post(state[action.id], action)
});
default:
return state;
}
}
This is confusing to me:
dispatch(receiveLikedPosts(3, {id:3, ids: [3,6]}));
function receiveLikedPosts(ids) {
return {
type: LIKED_POSTS_RECEIVED,
ids
};
}
Your function receiveLikedPosts only accepts one argument, yet you're passing it two. And I'm not sure what { id: 3, ids: [3, 6] } is supposed to be doing. But, here's what I would do:
Initial state and reducer:
const initialState = {
items: {
3: { title: '1984', liked: false },
6: { title: 'Mouse', liked: false },
19: { title: 'War and peace', liked: false }
}
};
function posts(state = initialState, action) {
switch (action.type) {
let newItems = {};
case LIKED_POSTS_RECEIVED:
// copy the current items into newItems
newItems = {...state.items};
// Loop through the liked IDs, set them to liked:true
action.ids.forEach((likedId) => {
newItems[likedId].liked = true;
});
// Return the new state
return {
...state,
items: newItems,
}
default:
return state;
}
}
Action creator:
function receiveLikedPosts(ids) {
return {
type: LIKED_POSTS_RECEIVED,
ids,
};
}
And finally, the dispatch:
dispatch(receiveLikedPosts([3, 6]));
Related
So i just learned redux and tried to make a simple list with redux and react.
but when i click on the button to add item to the list i got an error "state is not iterable"
here is my code
reducer
function manageList(state = { items: [] }, action) {
switch (action.type) {
case ADD_ITEM:
return { list: [...state, action.payload] };
case RESET_LIST:
return {
item: [...state, []],
};
default:
return state;
}
}
action
export const ADD_ITEM = "ADD_ITEM";
export const RESET_LIST = "RESET_LIST";
export function addItem(text) {
return { type: ADD_ITEM, payload: text };
}
export function resetList() {
return { type: RESET_LIST };
}
You're spreading an object inside an array, to fix that you should spread the items property inside an array:
function manageList(state = { items: [] }, action) {
switch (action.type) {
case ADD_ITEM:
return { list: [...state.items, action.payload] };
case RESET_LIST:
return {
items: [...state.items, []],
};
default:
return state;
}
}
I think also that you should replace list and item by items :
function manageList(state = { items: [] }, action) {
switch (action.type) {
case ADD_ITEM:
return { items: [...state.items, action.payload] };
case RESET_LIST:
return {
items: [...state.items, []],
};
default:
return state;
}
}
I think you should spread them as "state.items" not as just "state".
Like this:
item: [...state.items,[]]
Not like this:
item: [...state,[]]
export const commentReducer = (state = initialState, action) => {
switch (action.type) {
case COMMENT_REQUEST:
return {
...state,
loading: true,
};
case COMMENT_SUCCESS:
return {
...state,
comment: [...state.comment, action.payload],
};
case COMMENT_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
It works for me
I have the following state object in redux:
console.log({
jobOffers: {
filters: {
employments: [],
careerLevels: [],
jobTypeProfiles: [],
cities: [],
countries: [],
searchTerm: '',
currentPage: 1,
pageSize: 5
}
}
});
I want to set the array employments new.
That's my redux reducer:
export const reducer = (state = initialStateData, action) => {
switch (action.type) {
case Action.SET_ARR_FILTER:
{
const newNestedState = {
...state[action.key],
[action.key]: action.value,
};
return { ...state,
[action.key]: newNestedState
};
}
default:
return state;
}
};
The action:
export const SET_ARR_FILTER = 'SET_ARR_FILTER';
export const setEmployment = employment => ({
type: SET_ARR_FILTER,
key: 'employments',
value: employment,
});
But my object looks like this after the reducer has been called:
console.log({
employments: {
employments: ['HelloWorld']
},
})
What is wrong here ?
You're a level too deep (or not deep enough, depending on how you see it).
You need something like:
case Action.SET_ARR_FILTER:
{
const { filters } = state
return { ...state,
filters: {
...filters,
[action.key]: action.value
}
};
}
Similar to Mark's answer, all one line if you like.
export const reducer = (state = initialStateData, action) => {
switch (action.type) {
case Action.SET_ARR_FILTER:
return {
...state,
filter: {
...state.filter,
[action.key]: action.value
}
}
default:
return state;
}
};
Finally got it myself. Answer is:
case Action.SET_ARR_FILTER:
{
return {
...state,
jobOffers: {
...state.jobOffers,
filters: { ...state.jobOffers.filters,
[action.key]: action.value
},
},
};
}
Let's say I have normalized state like this (using normalizr)
entities: {
todos: {
'aaa': {
id: 'aaa',
text: 'Todo A',
completed: false
},
'bbb': {
id: 'bbb',
text: 'Todo B',
completed: false
},
'ccc': {
id: 'ccc',
text: 'Todo C',
completed: false
}
}
}
Then I have an action that fetches array of IDs from the server, that are completed. For that action type, I have reducer like this:
const todos = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMPLETED_SUCCESS:
return {
...state,
todos: action.payload.completedIds.map((id) => {
return {
[id]: {
...state.todos[id],
completed: true
}
}
})
};
default:
return state;
}
};
If I recieved an array with ['aaa', 'ccc'] (could be thousands of items in real world app), I want to set "completed" to TRUE on those respective todos in a single action, is it possible?
My current implementation of reducer doesn't work since it returns an array of objects, while original normalized state is object with IDs as key.
Thanks.
You could store the modified todos in an object and then update it using the spread syntax like
const todos = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMPLETED_SUCCESS:
let newTodos = {};
action.payload.completedIds.forEach((id) => {
newTodos[id]: {
...state.todos[id],
completed: true
}
})
})
return {
...state,
todos: {
...state.todos,
...newTodos
}
};
default:
return state;
}
};
I think this is the solution for your problem:
const todos = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMPLETED_SUCCESS:
return {
...state,
todos: Object.keys(action.payload.completedIds).reduce((previous, current) => {
previous[current] = {
...state.todos[current],
completed: true
}
return previous;
}, {})
})
};
default:
return state;
}
};
I used this solution because returns a new object and leaves the original object as it is
You can try this:
const todos = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMPLETED_SUCCESS:
const newState = {};//New Object
const filterKeys = Object.keys(state).filter( key=>{
newState[key]=state[key]; //copy
return completedIds.indexOf(key)!=-1;
});
filterKeys.forEach(key=> newState[key].completed = true);
return newState;
default:
return state;
}
};
I'm fairly new to react/redux and I've encountered a problem: my connect() component doesn't trigger render(). After some debugging I was pretty surprised to see that the root of this issue lies inside of a reducer. Apparently, after my action is dispatched, the reducer somehow gets the updated state inside of it's first argument (although it should be the old state, according to the docs), and that is why connect() component can't receive new props. Here is my reducer:
import * as types from '../actions/action-types';
const chart = (state = {}, action) => {
switch (action.type) {
case types.ADD_CHART:
let series = [
{
id: 0,
visible: true,
title: action.series[0]
}
];
for (let i = 1; i < action.series.length; i++) {
series.push({
id: i,
visible: false,
title: action.series[i]
});
}
return {
id: action.id,
series
}
case types.UPDATE_CHART:
if (state.id !== action.id) {
return state;
}
let result = {
id: action.id,
series: action.series
};
return {
id: action.id,
series: action.series
};
default:
return state;
}
}
const charts = (state = [], action) => {
console.log('charts reducer:');
console.log('old state:'); // it outputs the updated state
console.log(state);
switch (action.type) {
case types.ADD_CHART:
return [
...state,
chart(undefined, action)
];
case types.UPDATE_CHART:
let newState = state.map(c => chart(c, action));
console.log('new state:');
console.log(newState);
return state.map(c => chart(c, action));
default:
return state;
}
}
export default charts;
Any thoughts on this?
In lesson 16 of the egghead.io series on Redux, I tried to implement my own combineReducers function before looking at how Dan did it. I got as far as the following. I tried to use for ... in on the sub-reducers (todos, visibilityFilter) passed in like so
const combineReducers = (reducers) => {
return (state,action) => {
let temp = {};
for (let i in reducers) {
temp[i] = reducers[i](state,action)
}
return temp;
}
}
This does not work. When I test it with the expect library, I receive the following error in the console. Strangely, if I'm not mistaken, it looks like the state from the call to the todos reducer has been nested in to the call of the visibilityFilter reducer. This is very odd as my code shows them being distinctly separate fields in the object returned.
Uncaught Error: Expected { todos: [ { completed: false, id: 1, text:
'Go shopping' } ], visibilityFilter: { todos: [ { completed: false,
id: 0, text: 'Learn Redux' } ], visibilityFilter: 'SHOW_ALL' } } to
equal { todos: [ { completed: false, id: 0, text: 'Learn Redux' }, {
completed: false, id: 1, text: 'Go shopping' } ], visibilityFilter:
'SHOW_ALL' }
My test code is
const testTodoApp = () => {
const stateBefore = {
todos: [{id: 0, text:'Learn Redux', completed: false}],
visibilityFilter: 'SHOW_ALL',
};
// action is an object. with a defined type property.
const action = {
type: 'ADD_TODO',
id: 1,
text: 'Go shopping',
};
const stateAfter = {
todos: [{id: 0, text:'Learn Redux', completed: false},
{id: 1, text:'Go shopping', completed: false},
],
visibilityFilter: 'SHOW_ALL',
};
deepFreeze(stateBefore);
deepFreeze(action);
expect(
todoApp(stateBefore, action)
).toEqual(stateAfter);
console.log("Test passed: todoApp")
}
testTodoApp();
This test will pass if I use the built-in combineReducers.
The sub-reducers and call to combineReducers are as follows:
const todo = (state = {} ,action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id, text: action.text, completed: false,
};
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
...state, completed: !state.completed,
};
default:
return state;
}
}
const todos = (state=[], action) =>{
switch (action.type) {
case 'ADD_TODO':
console.log('ADD_TODO switch selected')
return [
...state,
todo(undefined,action),
];
case 'TOGGLE_TODO':
console.log('TOGGLE_TODO switch selected')
return state.map( t => todo(t, action))
default:
console.log('default switch selected')
return state;
}
}
const visibilityFilter = (
state = 'SHOW_ALL',
action
) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
}
const todoApp = combineReducers({
todos,
visibilityFilter,
})
My questions are:
What was it in my code that caused this nesting of one reducer within the other?
I realize Dan used reduce instead, but for pedagogical reasons, how can I go about using the for ... in pattern to implement combineReducers?
After that, can you please comment on the appropriateness of using for ... in for such applications, and if it is a bad pattern, what is it that makes it so?
I just realized that the todos reducer and visibilityFilter reducers have to be passed the part of the combined state that corresponds to their key, not the entire combined state. So the working code should look like this, where I have added an object accessor to the corresponding part of the state in the 5th line.
const combineReducers = (reducers) => {
return (state,action) => {
let temp = {};
for (let i in reducers) {
temp[i] = reducers[i](state[i],action)
}
return temp;
}
}