That's essentially what I want to do. The problem is this doesn't update state. Any idea what I'm missing?
type Tree = Array<Element>;
type SetLayerTreeItem = { payload: Element };
const initialState: Tree = [];
const LayersTree = createSlice({
name: 'LayersTree',
initialState,
reducers: {
setLayerTreeItem(state, { payload }: SetLayerTreeItem) {
state[payload.level] = payload; // Here's the problem
},
},
});
I ended up doing this:
setLayerTreeItem(state, { payload }: SetLayerTreeItem) {
const newState = state.slice();
newState[payload.level] = payload.element;
return newState;
},
I need to increase property in the state, but I have no idea how I can do it
help please, I have tried two days but got many troubles
export const initialState = {
ids: [],
basket: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_BASKET':
if (state.ids.indexOf(action.item.id) === -1) {
// if new item add to basket
return {
ids: [...state.ids, action.item.id],
basket: [...state.basket, action.item]
}
}
else {
if (state.basket.length > 0) {
// state.basket[state.basket.length - 1].quantity += 1
}
return {
...state,
// IT'S WRONG
basket: [...state.basket, [state.basket.length - 1].quantity += 1]
}
}
default:
return state;
}
}
You mutate state (basket.quantity += 1) when it should treated as immutable.
I believe you need to rethink how you structure your state if it gets so hard to immutably update a state.
export const initialState = {
ids: [],
basket: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_TO_BASKET":
if (state.ids.indexOf(action.item.id) === -1) {
...
} else {
...
const basketsShallowCopy = [...state.basket];
const basketToUpdate = basketsShallowCopy.pop();
const updatedBasked = {
...baskedToUpdate,
quantity: basketToUpdate.quantity + 1,
};
basketsShallowCopy.push(updatedBasked);
return {
...state,
basket: basketsShallowCopy,
};
}
...
}
};
I have a list of products called work items stored on my Redux store and I want to add an action that adds new work item or remove existing one when user picks up a a work item from the ui.
What I have so far is this workItemReducer:
import {
FETCH_WORKITEMS_BEGIN,
FETCH_WORKITEMS_SUCCESS,
FETCH_WORKITEMS_FAILURE,
SELECTED_WORKITEM
} from '../actions/workItemAction';
const initialState = {
workItems: [{"name":'work 1'}, {"name":'work 2'}, {"name":'work 3'}],
workItemsSelected: {},
loading: false,
error: null
};
export default function workItemReducer(state = initialState, action) {
switch(action.type) {
case FETCH_WORKITEMS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_WORKITEMS_SUCCESS:
return {
...state,
loading: false,
workItems: action.payload.workItems
};
case FETCH_WORKITEMS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
workItems: []
};
case SELECTED_WORKITEM:
return {
...state,
workItemsSelected: action.payload.workItem
};
default:
return state;
}
}
and the actions looks as below:
export const FETCH_WORKITEMS_BEGIN = 'FETCH_WORKITEMS_BEGIN';
export const FETCH_WORKITEMS_SUCCESS = 'FETCH_WORKITEMS_SUCCESS';
export const FETCH_WORKITEMS_FAILURE = 'FETCH_WORKITEMS_FAILURE';
export const SELECTED_WORKITEM = 'SELECTED_WORKITEM';
export const fetchWorkItemsBegin = () => ({
type: FETCH_WORKITEMS_BEGIN
});
export const fetchWorkItemsSuccess = workItems => ({
type: FETCH_WORKITEMS_SUCCESS,
payload: { workItems }
});
export const fetchWorkItemsFailure = error => ({
type: FETCH_WORKITEMS_FAILURE,
payload: { error }
});
export const selectedWorkItem = workItem => ({
type: SELECTED_WORKITEM,
payload: { workItem }
});
I have a container component that disptach or call these actions which I am a bit confused where the logic of adding a new one or removing existing one happens, either on the container/smart component or directly in the reducer.
Container component has this method:
onWorkItemSelect = (workItem) => {
this.props.dispatch(selectedWorkItem(workItem));
};
Anyone can help on writing the logic of adding new or remove existing one and where that code should live?
adding this to reducer works thou im not sure if all this code should remain into the reducer:
case SELECTED_WORKITEM:
let arr = [];
if (containsObject(action.payload.workItem, state.workItemsSelected)) {
arr = remove(state.workItemsSelected, action.payload.workItem);
} else {
arr = [...state.workItemsSelected, action.payload.workItem];
}
return {
...state,
workItemsSelected: arr
};
It should be done in the reducer
when adding one you could just spread the current array which you can get from the reducer state
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: [...workItems, workItem],
}
to delete one
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: workItems.filter(x => x.name === workItem.name),
}
I am learning Redux at school, as such we are using tests to insure we have benchmarks passing to help us in our understanding of the building blocks.
I am up to the portion where I am creating the Reducerfunction and I am almost done \o/ however I can't get one test to pass.
1) returns the initial state by default
And below the console spits back...
Reducer returns the initial state by default:
AssertionError: expected undefined to be an object
at Context. (tests/redux.spec.js:103:49)
My thinking it's because the test handles some of the concerns one would be responsible for e.g importing, creating action types etc. But not all. So maybe I am missing something the test is not providing?
Anyway here is my reducer file:
import pet from "../components/PetPreview";
import { createStore } from "redux";
import { adoptPet, previewPet, addNewDog, addNewCat } from "./action-creators";
// ACTION TYPES
const PREVIEW_PET = "PREVIEW_PET";
const ADOPT_PET = "ADOPT_PET";
const ADD_NEW_DOG = "ADD_NEW_DOG";
const ADD_NEW_CAT = "ADD_NEW_CAT";
// INTITIAL STATE
const initialState = {
dogs: [
{
name: "Taylor",
imgUrl: "src/img/taylor.png"
},
{
name: "Reggie",
imgUrl: "src/img/reggie.png"
},
{
name: "Pandora",
imgUrl: "src/img/pandora.png"
}
],
cats: [
{
name: "Earl",
imgUrl: "src/img/earl.png"
},
{
name: "Winnie",
imgUrl: "src/img/winnie.png"
},
{
name: "Fellini",
imgUrl: "src/img/fellini.png"
}
]
// These dogs and cats are on our intial state,
// but there are a few more things we need!
};
export default function reducer(prevState = initialState, action) {
var newState = Object.assign({}, prevState)
console.log('initialState', typeof initialState)
switch (action.type) {
case PREVIEW_PET:
// console.log('newState', newState)
return Object.assign({}, prevState, {
petToPreview: action.pet
});
break
case ADOPT_PET:
return Object.assign({}, prevState, {
petToAdopt: action.pet
});
break
case ADD_NEW_DOG:
// console.log('action', action.dog)
// console.log('prevState.dogs', prevState.dogs)
newState.dogs = prevState.dogs.concat([action.dog])
return newState;
break
case ADD_NEW_CAT:
// console.log('action', action.dog)
// console.log('prevState.dogs', prevState.dogs)
newState.cats = prevState.cats.concat([action.cat])
return newState;
break;
default:
return prevState;
}
return initialState
}
As you can see after the switch block I am returning the initialState
Shouldn't that be it?
Below is the redux.spec.js file:
import { expect } from "chai";
import { createStore } from "redux";
// You will write these functions
import {
previewPet,
adoptPet,
addNewDog,
addNewCat
} from "../src/store/action-creators";
import reducer from "../src/store/reducer";
const DOGS = [
{
name: "Taylor",
imgUrl: "src/img/taylor.png"
},
{
name: "Reggie",
imgUrl: "src/img/reggie.png"
},
{
name: "Pandora",
imgUrl: "src/img/pandora.png"
}
];
const CATS = [
{
name: "Earl",
imgUrl: "src/img/earl.png"
},
{
name: "Winnie",
imgUrl: "src/img/winnie.png"
},
{
name: "Fellini",
imgUrl: "src/img/fellini.png"
}
];
function getRandomPet(pets) {
return pets[Math.floor(Math.random() * pets.length)];
}
describe("Action creators", () => {
describe("previewPet", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(DOGS);
expect(previewPet(pet)).to.be.deep.equal({
type: "PREVIEW_PET",
pet: pet
});
});
});
describe("adoptPet", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(DOGS);
expect(adoptPet(pet)).to.be.deep.equal({
type: "ADOPT_PET",
pet: pet
});
});
});
describe("addNewDog", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(DOGS);
expect(addNewDog(pet)).to.be.deep.equal({
type: "ADD_NEW_DOG",
dog: pet
});
});
});
describe("addNewCat", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(CATS);
expect(addNewCat(pet)).to.be.deep.equal({
type: "ADD_NEW_CAT",
cat: pet
});
});
});
}); // end Action creators
describe("Reducer", () => {
let store;
beforeEach("Create the store", () => {
// creates a store (for testing) using your (real) reducer
store = createStore(reducer);
});
it("returns the initial state by default", () => {
// In addition to dogs and cats, we need two more fields
expect(store.getState().petToPreview).to.be.an("object");
expect(store.getState().petToAdopt).to.be.an("object");
});
describe("reduces on PREVIEW_PET action", () => {
it("sets the action's pet as the petToPreview on state (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(DOGS);
const action = {
type: "PREVIEW_PET",
pet: pet
};
store.dispatch(action);
const newState = store.getState();
// ensures the state is updated properly - deep equality compares the values of two objects' key-value pairs
expect(store.getState().petToPreview).to.be.deep.equal(pet);
// ensures we didn't mutate anything - regular equality compares the location of the object in memory
expect(newState.petToPreview).to.not.be.equal(prevState.petToPreview);
});
});
describe("reduces on ADOPT_PET action", () => {
it("sets the action's pet as the petToAdopt on state (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(DOGS);
const action = {
type: "ADOPT_PET",
pet: pet
};
store.dispatch(action);
const newState = store.getState();
expect(newState.petToAdopt).to.be.deep.equal(pet);
expect(newState.petToAdopt).to.not.be.equal(prevState.petToAdopt);
});
});
describe("reduces on ADD_NEW_DOG action", () => {
it("adds the new dog to the dogs array (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(DOGS);
const action = {
type: "ADD_NEW_DOG",
dog: pet
};
store.dispatch(action);
const newState = store.getState();
expect(newState.dogs.length).to.be.equal(prevState.dogs.length + 1);
expect(newState.dogs[newState.dogs.length - 1]).to.be.deep.equal(pet);
expect(newState.dogs).to.not.be.equal(prevState.dogs);
});
});
describe("reduces on ADD_NEW_CAT action", () => {
it("adds the new cat to the cats array (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(CATS);
const action = {
type: "ADD_NEW_CAT",
cat: pet
};
store.dispatch(action);
const newState = store.getState();
expect(newState.cats.length).to.be.equal(prevState.cats.length + 1);
expect(newState.cats[newState.cats.length - 1]).to.be.deep.equal(pet);
expect(newState.cats).to.not.be.equal(prevState.cats);
});
});
describe("handles unrecognized actions", () => {
it("returns the previous state", () => {
const prevState = store.getState();
const action = {
type: "NOT_A_THING"
};
store.dispatch(action);
const newState = store.getState();
// these should be the same object in memory AND have equivalent key-value pairs
expect(prevState).to.be.an("object");
expect(newState).to.be.an("object");
expect(newState).to.be.equal(prevState);
expect(newState).to.be.deep.equal(prevState);
});
});
}); // end Reducer
Thanks in advance!
in the test cases, one of the test case default says
it("returns the initial state by default", () => {
// In addition to dogs and cats, we need two more fields
expect(store.getState().petToPreview).to.be.an("object");
expect(store.getState().petToAdopt).to.be.an("object");
});
meaning there must be petTpPreview and petToAdapt protery attached to the store at the inital itself. this can be done by adding these two as boject to the state as follows.
// INTITIAL STATE
const initialState = {
petToPreview:{},
petToAdopt: {},
dogs: [
{
name: "Taylor",
imgUrl: "src/img/taylor.png"
},
{
name: "Reggie",
imgUrl: "src/img/reggie.png"
},
{
name: "Pandora",
imgUrl: "src/img/pandora.png"
}
],
cats: [
{
name: "Earl",
imgUrl: "src/img/earl.png"
},
{
name: "Winnie",
imgUrl: "src/img/winnie.png"
},
{
name: "Fellini",
imgUrl: "src/img/fellini.png"
}
]
// These dogs and cats are on our intial state,
// but there are a few more things we need!
};
hope it helps!
All paths in the switch statement lead to a return, which means your return initialState on the penultimate line is unreachable.
Also, your newState is nothing but a clone of prevState, and is unnecessary.
Removing that, and adding a helper function for switchcase combined with some es6 spread love, your code becomes
const switchcase = cases => defaultValue => key =>
(key in cases ? cases[key] : defaultValue);
const reducer = (state = initialState, action) =>
switchcase({
[PREVIEW_PET]: { ...state, petToPreview: action.pet },
[ADOPT_PET]: { ...state, petToAdopt: action.pet },
[ADD_NEW_DOG]: { ...state, dogs: [...state.dogs, action.dog] },
[ADD_NEW_CAT]: { ...state, cats: [...state.cats, action.cat] },
})(state)(action.type);
With all the clutter gone, it's glaringly obvious that the problem is in the fact that your code returns the initialState object if action.type === undefined. And your initialState object contains only dogs and cats properties, whereas your test expects there to be petToPreview and petToAdopt properties.
You can add those properties in the initialState or you can change the test depending on what functionality you want.
Shouldn't you return the previous state by default? The by default is the case that the reducer doesn't care about the action, and simply return its current state, which is prevState in your case.
I am rendering a tabList, onclick of any tab it will be highlighted. I am achieving this by checking a selected attribute.
Everytime a tab is clicked I am changing selected to true/false in my reducer.
myTabsReducer should not mutate but return a new array.
Am I doing it right way? As per documentation We should not mutate state (should not alter state). In my case I'm altering my reducer. Is it Ok to do it or is there any other way to achieve it?
export const myTabsReducer = (id) => {
return [
{
id: 1,
name:"My Tab 1",
selected: id==1?true:false
},
{
id: 2,
name:"My Tab 2",
selected: id==2?true:false
},
{
id: 3,
name:"My Tab 3",
selected: id==3?true:false
}
]
}
const myActiveTabReducer = (state = {}, action) => {
switch (action.type) {
case SELECT_PLAN_TAB:
return action.payload
default:
return 1;
}
}
const allReducers = (state = {}, action) => {
return {
allTabs: myTabsReducer(myActiveTabReducer(state, action)),
}
}
It would be much better to have your state in this way:
const initialState = {
tabs: [
{ id: 1, name: 'Tab 1' },
{ id: 2, name: 'Tab 2' }
],
selectedTab: 1
}
so that your reducer changes only the selectedTab property on "SELECT_PLAN_TAB":
const activeTabReducer = (state = initialState, action) => {
switch (action.type) {
case SELECT_PLAN_TAB:
return {
...state,
selectedTab: action.payload
};
default:
return state;
}
}
Then, if you need the tabs array with the selected property for each tab, I would use a selector: see http://redux.js.org/docs/recipes/ComputingDerivedData.html
You are not mutating the array. Every time reducer is called you generate a new array.
const foo1 = allReducers({}, {type:' SELECT_PLAN_TAB', payload: 1})
const foo2 = allReducers({}, {type:' SELECT_PLAN_TAB', payload: 1})
console.log(foo1.allTabs === foo2.allTabs) // false
You should be careful, because this function will always return new instance even if input stays the same. This means that shallow equality check will fail and depending on how you consume this store, your component will always re-render.