Need to run .slice on React Redux Reducer state - reactjs

I have an application which is using React, Redux, and Sagas. I have a reducer which has a state that is an array of objects. I want this reducer to handle a certain action by removing the first item of the array. I understand that state needs to be immutable and therefor I cannot simply call .shift.
Here is what I am trying to do now:
const flashcards = (state = [], action) => {
switch (action.type) {
case 'MAKE_FLASHCARD':
console.log(action.payload)
return action.payload;
case 'UPDATE_FLASHCARD_ARRAY':
return ({
...state.slice(1,state.length)
})
default:
return state;
}
};
export default flashcards;
The ...state.slice(1,state.length) works the first time UPDATE_FLASHCARD_ARRAY is called. However it stops working on future attempts. I discovered this is because state is not actually removing the first index with my slice method, rather it is setting the values of the first index equal to null.
Here is a stringify of the Redux stat to help illustrate this.
Prior to calling UPDATE_FLASHCARD_ARRAY :
[{"id":7,"account_id":1,"native_word":"pig","translation":"gris"},{"id":3,"account_id":1,"native_word":"cow","translation":"ku"},{"id":1,"account_id":1,"native_word":"cheese","translation":"ost"},{"id":2,"account_id":1,"native_word":"milk","translation":"melk"},{"id":8,"account_id":1,"native_word":"spider","translation":"ederkopp"}]
After calling UPDATE_FLASHCARD_ARRAY :
{"0":{"id":3,"account_id":1,"native_word":"cow","translation":"ku"},"1":{"id":1,"account_id":1,"native_word":"cheese","translation":"ost"},"2":{"id":2,"account_id":1,"native_word":"milk","translation":"melk"},"3":{"id":8,"account_id":1,"native_word":"spider","translation":"ederkopp"}}
The slice method is clearing returning a state different than the original. Could someone point out what I am doing wrong here? All I want to do is remove the first object from the state array every time UPDATE_FLASHCARD_ARRAY is dispatched.

.slice already returns a new array you don't need to spread it, you're also spreading it into an object that's why you see {"0":...:
case 'UPDATE_FLASHCARD_ARRAY':
return state.slice(1,state.length)

Have you tried to do return state.slice(1) instead return ({...state.slice(1,state.length)})? The latter creates an object, which has same keys as Array, but not actually as Array:
Array.isArray([1, 2, 3]) === true
Array.isArray({...[1, 2, 3]}) === false
({...[1, 2, 3]}).slice(1) -> TypeError

Related

How to set initial slice state using slice reducer

So, I've got a redux slice, where there are two reducers, setTodos and addTodo, the initial state is set to be empty, but soon updated using the reducer setTodos, the data of setTodos(dispatched from a component) is fetched by a network call, so it is an asynchronous task. The data is updated properly, but when I try adding a new todo, the addition is not reflected on the screen as it should, perhaps it is displayed only on reload because setTodos is executed again and the state is updated accordingly.
const myTodoSlice = createSlice({
name:'Todo',
initialState:{todos:[]},
reducers:{
addTodo:(state,action)=>{
state.todos.push(action.payload.todo)
},
setTodos:(state,action)=>{
//Approach 1 of setting the initial todo state.
state.todos = action.payload.arrayOfTodos
//Approach 2 of setting the initial todo state.
// state.todos.push(...action.payload.arrayOfTodos)
}
}
List rendering code:
const selector = useSelector((arg:RootStateOrAny)=>arg.todos.todos)
return(
<React.Fragment>
{selector?.map((item:RootStateOrAny) => <TodoItem key={Math.random()} title={item.todoTitle} desc={item.todoDescription} />)}
</React.Fragmnet>
)
Currently, I'm following approach 1, but by doing so, I'm unable to render new additions on the screen. When I tried using approach 2, I'm getting errors mostly related to re-render depth exceeded.
I'd want to know by following approach 1, why is the addition not rendered, and what changes can I make to get the desired result?
Thanks.
The issue is that React sees the same array reference (because the push is a mutation operation of the existing array instance) and ignores the rendering.
Instead of
addTodo:(state,action)=>{
state.todos.push(action.payload.todo)
},
Try like below which creates a new array instance with adding the new todo item (concat returns a new array with the new item without mutating the existing one).
addTodo:(state,action)=>{
state.todos = state.todos.concat([action.payload.todo])
},

Modify only part of the state in a react-redux app

As a practice exercise, I am writing a react-redux calculator app. My app's state is defined as:
const initialState = {
operator1: "", //first operand
operator2: "", //second operand
currentOp: "", // +, -, *, /
display:"", //the current calculator display
currentOperator:1 //which operand is being entered right now
}
currentOp holds the symbol of the operation currently being performed by the calculator, which, when entering the first operand, is empty. Therefore, when my calculator's numbers are pressed, I need to update the display, but without loosing my other state properties. I wrote my reducer like this:
import {NUMBER_PRESSED,OPERATION_PRESSED,EQUAL_PRESSED} from './actions';
const mainReducer = (state ={},action) =>
{
console.log("reducer called!");
console.log(action);
const newState = {};
//copy the operators to the new state. Only one will be changed. (Is this really necessary?)
newState.operator1 = state.operator1;
newState.operator2 = state.operator2;
switch(action.type)
{
case NUMBER_PRESSED:
if (state.currentOperator===1)
{
newState.operator1 = state.operator1 + action.payload;
newState.display= newState.operator1;
}
if(state.currentOperator===2)
{
newState.operator2 = state.operator2 + action.payload;
newState.display= newState.operator2;
}
//set the other properties of the state (Is this really necessary?)
newState.currentOperator = state.currentOperator;
newState.currentOp = state.currentOp;
console.log("The new state is:");
console.log(newState);
return newState;
case OPERATION_PRESSED:
break;
case EQUAL_PRESSED:
break;
default:
return state;
}
}
export default mainReducer;
Please note that I have not yet implemented the calculation operations, just updating the display. If I change the state variable directly, the calculator component does not update. Understandable, and this is expected behavior explained in the docs. However, it seems that I need to manually copy the entire state into a new variable so that it is preserved the next state (notice the "Is this really necessary?" comments in the code.
I have no problem copying all the app's state and returning an entirely new state object, but what happens on bigger applications with huge state trees? How is this managed? Is there a way to modify only part of the state in redux?
You can use things like the spread operator to duplicate entire objects without having to set each one manually:
const x = state.someArray.slice();
x[1] = "potato";
return {...state, someArray:x}
But to answer your concern, yes you do have to make an entire new duplicate of the state when changing it. It's not usually an issue, and doesn't take much time. If your state tree is HUGE then the solution should be splitting up that tree into separate reducers, that way you only have to duplicate and replace parts of the tree when changing the state.
1: If your state is decoupling you should use combineReducers It is recursion
2: If not, you should use es6 destructuring
3: What's more, you should consider about your state structure.(depends on your reducer code, i suggest...)
base on 2, for example
const mainReducer = (state = {},action) => {
switch(action.type) {
case NUMBER_PRESSED:
if (state.currentOperator===1) return {
...state,
operator1: state.operator1 + action.payload,
display: 'operator1'
}
if(state.currentOperator===2) return {
...state,
operator2: state.operator2 + action.payload,
display: 'operator2'
}
return state
default: return state;
}
}
If it's still huge with correctly program design....Product Design?

Redux return a fresh object composed out of our existing state

I am trying to learn redux and stumbled upon this article from hackernoon
Everything was going great until I encountered this example
export default function reducer(state = {posts: []}, action) {
switch (action.type) {
case 'Add_USER_POST':
return {
...state,
posts: [
...state.posts,
{
content: action.payload.content,
}
]
};
default:
return state;
}
}
For which the following explanation was given..
First, we added a default property posts to our default state and
initialised it with [] . Next, we simply added a switch-case block
that switches on action.type . Because our action had a type of
Add_USER_POST it will be intercepted by the 1st case in our reducer’s
switch-case and it will return a fresh object composed out of our
existing state and add the newly added post to the posts array.
Here I am unable to understand the following lines (first and the last lines)
First, we added a default property posts to our default state and initialised it with [] .
return a fresh object composed out of our existing state and add the newly added post to the posts array
Can someone help me in understanding the above code in much more simpler terms?
[Update] got the first point, can someone explain me the working of part of code associated with second point
return {
...state,
posts: [
...state.posts,
{
content: action.payload.content,
}
]
};
Things I understood from the above code, We are returning an object which have our current state, then an array of posts which consists of things inside our state.posts and action.payload.content which we get from here
{
type: 'Add_USER_POST',
payload: {
content: 'A quick brown fox jumped over the lazy dog',
}
}
As in our article.
This reducer function takes a state and an action parameters. If no parameters are passed to the reducer it simply returns an object containing an empty array of posts.
The default property :
state = {posts: []}
Sets a default object as the first parameter of the reducer function.
The spread operator is being used to combine the previous state passed, the previous posts in that state's array and add a new post with the content passed in the action.payload.content.
DOCS
Default Parameters
Spread Operator

Redux key based array not triggering new props when added to 2nd time

I am dealing with Proposals and locations.
A location can have multiple proposals.
The component is passed a location object (below as passedProps) to show all the proposals for this location :
<Proposals location={ location } key={location.id}/>
Here is are my redux props :
const mapStateToProps = (state , passedProps) => {
return {
proposals : state.propertyProposals[passedProps.location.id]
};
};
when adding a new proposal, I want to store their ids by location, so in a addition to storing the proposal, I am storing their Ids, in an array keyed by location Id.
The first proposal added and refreshed with new props just fine, however even though the second one is successfully pushed to the array, this is not triggering new props, so it does not "refresh" -- If I leave the route and come back I see the new 2nd proposal (which did not show the first time)
Here is the PROPOSAL_CREATE action for a new Proposal.
type :'PROPOSAL_CREATE',
payload :
{'e7ef104e-19ed-acc8-7db5-8f13839faae3' : {
id : 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
here is the case which handles it :
case 'PROPOSAL_CREATE':
{
const proposal = Object.values(action.payload)[0];
const locationId = proposal.locationId;
let newState = {...state}
if (locationId in newState) {
newState[locationId].push(proposal.id)
} else {
newState[locationId] = [proposal.id]
}
return newState
}
Is there an obvious reason I am not seeing the change in the component for the second entry?
Thanks
There is one issue here. Your store state is not immutable. You have used below line to make a copy:
let newState = {...state}
Here it does make copy of object but it's shallow copy, hence your array object in newState and state have the same reference. That's why redux doesn't identify the change in store and hence props are not updated in sequence.
You can clone your state by below methods:
let newState = JSON.parse(JSON.stringify(state));
OR if you use jQuery then:
let newState = $.extend(true, {}, state);
I think this will surely fix your issue.
Based on your reducer logic i think that you did not specify action type.
The one of the redux conventions is the action recognition based on type property.
I bet that you forgot to specify that property.
var properAction = {
type: 'PROPOSAL_CREATE',
payload: {
{
'e7ef104e-19ed-acc8-7db5-8f13839faae3': {
id: 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
}
I would recommend you to write action creators it will reduce your place for typos like that.
Cheers!
2 things:
I forgot that Arrays of an original object are still by reference. So even after
let newState = {...state}
newState[locationId]
has the same reference as
state[locationId]
As a result my original statement was mutating the original state, not creating a newState
and so
newState[locationId].push(proposal.id)
needed to be
newState[locationId] = state[locationId].concat(proposal.id);
or es6
newState[locationId] = [ ...state[locationId] , proposal.id ] ;

Using spread operator from es6 in react-redux app with stage-2 loader

I am using ES6 spread operator in my react-redux app like this way
var defaultState = {val:1, items:[]};
export default function(state=defaultState, action){
switch(action.type){
case 'ADD_BOOK':
state.val = 0;
return {
...state,
items:action.payload.data.result
};
The problem is each time I get fresh data from action.payload.data.result assigned to items. I want to have concatenated data from previous state. Can someone help.
This should work.
return {
...state,
items: [
...state.items,
...aciton.payload.data.result
]
}
so what you really need to do here, is concat the existing items and those returned from your payload.
Assuming this is the case, then you need to create a new array from the old items array and new. You also want to avoid mutating the existing state.
Return a completely new state from your reducer. Do this by cloning the existing state and adding your new items array:
case 'ADD_BOOK':
var newState = Object.assign({}, state, {val:0}); // clones the existing state and adds our new val
newState.items = newState.Items.concat(action.payload.data.result);
return newState;
There are cleaner ways to do this with the spread syntax or libraries such as immutable but this example will explicitly achieve what we need to do.

Resources