I'm using Redux with Next JS, and i'm trying to add array of objects to it. in my app i have a form with multiple inputs. to create this multiple input i've created an input form of 10.
{homeAmeneties.map((home) => {
<div className="h-8 flex justify-end rounded-md pr-10">
<input
type="number"
onInput={(event) =>
(event.target.value = event.target.value.slice(
0,
event.target.maxLength
))
}
maxLength="3"
onChange={(e) => updateVal(e, home.name)}
className="border-[1px] w-1/2 border-black rounded-md h-full focus:outline-none px-2"
/>
</div>
})}
each of the values from the input is added to an array. but i wanted to add it into redux array. in my redux reducer i've created
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
arr: []
}
export const arrSlice = createSlice({
name: "arrS",
initialState,
reducers: {
setArr: (state, action) => {
state.arr = [...state.arr, action.payload];
}
}
});
//...rest here
when i run this it throws TypeError: action.payload is not iterable, i've tried to add it just by passing as state.arr = action.payload; and it doesn't store the input as array of objects, just simple object.
How can i add and make tweeks like i can do it in simple useState?
the code below is what i did with regular useState()
const [amen, setAmen] = useState([]);
const updateVal = (e, type) => {
setAmen((prevAmen) => {
const newAmen = [...prevAmen]; // create a copy of the current state
const index = newAmen.findIndex((item) => item.name === type); // find the index of the object with the matching name
if (e.target.value === "") {
if (index !== -1) {
newAmen.splice(index, 1);
}
} else {
if (index !== -1) {
newAmen[index] = { name: type, val: e.target.value }; // update the object at that index
} else {
newAmen.push({ name: type, val: e.target.value }); // add a new object to the array
}
}
return newAmen;
});
dispatch(setRooms(...amen));
console.log(rooms);
};
I found an answer to this if anyone wants a solution. before that let me explain what it does.
i have an array of objects that's coming from an input fields. and when these fields value changes it trigger the Redux dispatch. if the array exists it updates that array if not , well it creates new array.
the data i have is something like below
[
{name: "name1" , val: 27},
{name: "name2" , val: 26},
{name: "name3" , val: 25},
{name: "name4" , val: 24},
{name: "name5" , val: 23},
{name: "name6" , val: 22},
]
and when input onChange() triggers, it dispatch the data to redux. onChange={(e) => handleChange(name, e.target.value)}
const handleChange = (name, val) =>{
const obj = { name: name, val: val };
dispatch(setUser(obj))
}
and in the reducer
setUser: (state, action) => {
const newState = [...state.users];
const type = action.payload;
const index = newState.findIndex((item) => item.name === type.name);
if (type.val === "") {
if (index !== -1) {
newState.splice(index, 1);
}
} else {
if (index !== -1) {
newState[index] = { name: type.name, val: type.val };
} else {
newState.push({ name: type.name, val: type.val });
}
}
state.users = [...newState];
console.log(state.users);
},
hopefully this helps someone.
I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
Something similar to D. Smith's answer but a little more concise:
const toggleDone = (id) => {
setState(prevState => {
// Loop over your list
return prevState.map((item) => {
// Check for the item with the specified id and update it
return item.id === id ? {...item, done: !item.done} : item
})
})
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
I would create just the todos array using useState instead of another state, the key is creating a copy of the todos array, updating that, and setting it as the new array.
Here is a working example: https://codesandbox.io/s/competent-bogdan-kn22e?file=/src/App.js
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
title: "take out trash",
done: false
},
{
id: 2,
title: "wife to dinner",
done: false
},
{
id: 3,
title: "make react app",
done: false
}
]);
const toggleDone = (e, item) => {
const indexToUpdate = todos.findIndex((todo) => todo.id === item.id);
const updatedTodos = [...todos]; // creates a copy of the array
updatedTodos[indexToUpdate].done = !item.done;
setTodos(updatedTodos);
};
What is the correct way of updating state, in a nested object, in React with Hooks?
export Example = () => {
const [exampleState, setExampleState] = useState(
{masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b"
fieldTwoTwo: "c"
}
}
})
How would one use setExampleState to update exampleState to a (appending an field)?
const a = {
masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}
}
b (Changing values)?
const b = {masterField: {
fieldOne: "e",
fieldTwo: {
fieldTwoOne: "f"
fieldTwoTwo: "g"
}
}
})
You can pass new value like this:
setExampleState({...exampleState, masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})
If anyone is searching for useState() hooks update for object
- Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
***************************
- Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
Generally you should watch out for deeply nested objects in React state. To avoid unexpected behavior, the state should be updated immutably. When you have deep objects, you end up deep cloning them for immutability, which can be quite expensive in React. Why?
Once you deep clone the state, React will recalculate and re-render everything that depends on the variables, even though they haven't changed!
So, before trying to solve your issue, think how you can flatten the state first. As soon as you do that, you will find handy tools that will help dealing with large states, such as useReducer().
In case you thought about it, but are still convinced you need to use a deeply nested state tree, you can still use useState() with libraries like immutable.js and Immutability-helper. They make it simple to update or clone deep objects without having to worry about mutability.
I'm late to the party.. :)
#aseferov answer works very well when the intention is to re-enter the entire object structure. However, if the target/goal is to update a specific field value in an Object, I believe the approach below is better.
situation:
const [infoData, setInfoData] = useState({
major: {
name: "John Doe",
age: "24",
sex: "M",
},
minor:{
id: 4,
collegeRegion: "south",
}
});
Updating a specific record will require making a recall to the previous State prevState
Here:
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
}
}));
perhaps
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
},
minor: {
...prevState.minor,
collegeRegion: "northEast"
}));
I hope this helps anyone trying to solve a similar problem.
It's possible to use useReducer hook for managing complex state, instead of useState. To do it, first initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => ({ ...state, ...updates }),
initialState
);
And then you can update the state by only passing partial updates, like this:
updateState({ occupation: "postman" })
In 2022
If you are looking for the same functionality as this.setState ( came from the class components ) in functional components then this is the answer that helps you a lot.
For Example
You have a state like below and want to update the specific field only from the whole state then you need to use the object destructing every time and sometimes it will be irritating.
const [state, setState] = useState({first: 1, second: 2});
// results will be state = {first: 3} instead of {first: 3, second: 2}
setState({first: 3})
// To resolve that you need to use object destructing every time
// results will be state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
To solve that I came up with the useReducer approach. Please check useReducer.
const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2});
// results will be state = {first: 3, second: 2}
setState({first: 3})
// you can also access the previous state callback if you want
// results will remain same, state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
You can store that stateReducer in utils files and import it in every file if you want.
Here is the custom hook if you want.
import React from 'react';
export const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const useReducer = (initial, lazyInitializer = null) => {
const [state, setState] = React.useReducer(stateReducer, initial, init =>
lazyInitializer ? lazyInitializer(init) : init
);
return [state, setState];
};
export default useReducer;
Typescript
import React, { Dispatch } from "react";
type SetStateAction<S> = S | ((prev: S) => S);
type STATE<R> = [R, Dispatch<SetStateAction<Partial<R>>>];
const stateReducer = (state, action) => ({
...state,
...(typeof action === "function" ? action(state) : action),
});
const useReducer = <S>(initial, lazyInitializer = null): STATE<S> => {
const [state, setState] = React.useReducer(stateReducer, initial, (init) =>
lazyInitializer ? lazyInitializer(init) : init,
);
return [state, setState];
};
export default useReducer;
Thanks Philip this helped me - my use case was I had a form with lot of input fields so I maintained initial state as object and I was not able to update the object state.The above post helped me :)
const [projectGroupDetails, setProjectGroupDetails] = useState({
"projectGroupId": "",
"projectGroup": "DDD",
"project-id": "",
"appd-ui": "",
"appd-node": ""
});
const inputGroupChangeHandler = (event) => {
setProjectGroupDetails((prevState) => ({
...prevState,
[event.target.id]: event.target.value
}));
}
<Input
id="projectGroupId"
labelText="Project Group Id"
value={projectGroupDetails.projectGroupId}
onChange={inputGroupChangeHandler}
/>
You have to use Rest parameters and spread syntax (https://javascript.info/rest-parameters-spread) AND set a function with preState as the argument of the setState.
Does not work (missing function)
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState({
...state,
[key]: value
});
Does work!
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState(prevState => ({
...prevState,
[key]: value
}));
I have given both Append, Whole object update, Specific key update examples for the solve
Append and modify both can be done by a simple step. I think this is more stable and safe which has no immutable or mutable dependency.
This is how you can append new object
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
Suppose you want to modify again the masterField2 object. There can be two situation. You want to update the whole object or update a specific key of the object.
Update the whole object - So here the whole value for key masterField2 will be updated.
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
But what if you wanted to change only fieldTwoOne key inside the masterField2 object. You do the following.
let oldMasterField2 = exampleState.masterField2
oldMasterField2.fieldTwo.fieldTwoOne = 'changed';
setExampleState(prevState => ({
...prevState,
masterField2: oldMasterField2
}))
function App() {
const [todos, setTodos] = useState([
{ id: 1, title: "Selectus aut autem", completed: false },
{ id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
{ id: 3, title: "Fugiat veniam minus", completed: false },
{ id: 4, title: "Aet porro tempora", completed: true },
{ id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
]);
const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
setTodos([...todos], todos);}
return (
<div className="container">
{todos.map(items => {
return (
<div key={items.id}>
<label>
<input type="checkbox"
onChange={changeInput}
value={items.id}
checked={items.completed} /> {items.title}</label>
</div>
)
})}
</div>
);
}
Initially I used object in useState, but then I moved to useReducer hook for complex cases. I felt a performance improvement when I refactored the code.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
useReducer React docs
I already implemented such hook for my own use:
/**
* Same as useObjectState but uses useReducer instead of useState
* (better performance for complex cases)
* #param {*} PropsWithDefaultValues object with all needed props
* and their initial value
* #returns [state, setProp] state - the state object, setProp - dispatch
* changes one (given prop name & prop value) or multiple props (given an
* object { prop: value, ...}) in object state
*/
export function useObjectReducer(PropsWithDefaultValues) {
const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);
//newFieldsVal={[field_name]: [field_value], ...}
function reducer(state, newFieldsVal) {
return { ...state, ...newFieldsVal };
}
return [
state,
(newFieldsVal, newVal) => {
if (typeof newVal !== "undefined") {
const tmp = {};
tmp[newFieldsVal] = newVal;
dispatch(tmp);
} else {
dispatch(newFieldsVal);
}
},
];
}
more related hooks.
, do it like this example :
first creat state of the objects:
const [isSelected, setSelection] = useState([{ id_1: false }, { id_2: false }, { id_3: false }]);
then change the value on of them:
// if the id_1 is false make it true or return it false.
onValueChange={() => isSelected.id_1 == false ? setSelection([{ ...isSelected, id_1: true }]) : setSelection([{ ...isSelected, id_1: false }])}
I think best solution is Immer. It allows you to update object like you are directly modifying fields (masterField.fieldOne.fieldx = 'abc'). But it will not change actual object of course. It collects all updates on a draft object and gives you a final object at the end which you can use to replace original object.
Your Object for which you want to make state
let teams = {
team: [
{
name: "one",
id: "1"
},
]
}
Making State of teams object
const [state, setState] = useState(teams);
Update State like this
setState((prevState)=>({...prevState,team:[
...prevState.team,
{
name: "two",
id: "2"
}
]}))
After the updation State becomes
{
team: [
{
name: "one",
id: "1"
},
{
name: "two",
id: "2"
}
]
}
To Render items according to current state use Map function
{state.team.map((curr_team) => {
return (
<div>
<p>{curr_team.id}</p>
<p>{curr_team.name}</p>
</div>
)
})}
If you work with boolean values and arrays This can help you:
const [checkedOrders, setCheckedOrders] = useState<Record<string, TEntity>>({});
const handleToggleCheck = (entity: TEntity) => {
const _checkedOrders = { ...checkedOrders };
const isChecked = entity.id in checkedOrders;
if (isChecked) {
delete _checkedOrders[entity.id];
} else {
_checkedOrders[entity.id] = entity;
}
setCheckedOrders(_checkedOrders);
};
Answers are already there but This type isn't mentioned so take look at the example of this kind ...
const[data,setdata]= useState({
username: [
email,
"required",
//...some additional codes
],
password: [
password,
"required|password-5",
//..additional code if any..
],
})
**To update the state variable email in this example in the input field you can add similar code with your variable name **
<Input
onChangeText={(t) => setdata(prevState=>({...prevState,username:{[0]:t}}))}
value={data.username[0]}
/>
I leave you a utility function to inmutably update objects
/**
* Inmutable update object
* #param {Object} oldObject Object to update
* #param {Object} updatedValues Object with new values
* #return {Object} New Object with updated values
*/
export const updateObject = (oldObject, updatedValues) => {
return {
...oldObject,
...updatedValues
};
};
So you can use it like this
const MyComponent = props => {
const [orderForm, setOrderForm] = useState({
specialities: {
elementType: "select",
elementConfig: {
options: [],
label: "Specialities"
},
touched: false
}
});
// I want to update the options list, to fill a select element
// ---------- Update with fetched elements ---------- //
const updateSpecialitiesData = data => {
// Inmutably update elementConfig object. i.e label field is not modified
const updatedOptions = updateObject(
orderForm[formElementKey]["elementConfig"],
{
options: data
}
);
// Inmutably update the relevant element.
const updatedFormElement = updateObject(orderForm[formElementKey], {
touched: true,
elementConfig: updatedOptions
});
// Inmutably update the relevant element in the state.
const orderFormUpdated = updateObject(orderForm, {
[formElementKey]: updatedFormElement
});
setOrderForm(orderFormUpdated);
};
useEffect(() => {
// some code to fetch data
updateSpecialitiesData.current("specialities",fetchedData);
}, [updateSpecialitiesData]);
// More component code
}
If not you have more utilities here : https://es.reactjs.org/docs/update.html
I think a more elegant solution will be to create the updated state object, while retaining the previous values of state. The Object property which is needed to be updated can be provided in the form of array something like this -
import React,{useState, useEffect} from 'react'
export default function Home2(props) {
const [x, setX] = useState({name : '',add : {full : '', pin : '', d : { v : '' }}})
const handleClick = (e, type)=>{
let obj = {}
if(type.length > 1){
var z = {}
var z2 = x[type[0]]
type.forEach((val, idx)=>{
if(idx === type.length - 1){
z[val] = e.target.value
}
else if(idx > 0){
Object.assign(z , z2) /*{...z2 , [val]:{} }*/
z[val] = {}
z = z[val]
z2 = z2[val]
}else{
z = {...z2}
obj = z
}
})
}else obj = e.target.value
setX( { ...x , [type[0]] : obj } )
}
return (
<div>
<input value = {x.name} onChange={e=>handleClick(e,["name"])}/>
<input value = {x.add.full} onChange={e=>handleClick(e,["add","full"])} />
<input value = {x.add.pin} onChange={e=>handleClick(e,["add","pin"])} /><br/>
<input value = {x.add.d.v} onChange={e=>handleClick(e,["add","d","v"])} /><br/>
{x.name} <br/>
{x.add.full} <br/>
{x.add.pin} <br/>
{x.add.d.v}
</div>
)
}
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.