redux react update array after copying array - javascript

I have a question I'm facing a problem that I can't update array after copying it.
If I copy an array and don't update the id, when I type something on input the text will appear in the same way where I copy it.
here is my Initialstate
const initialState = [
{
id: random numbers,
options: [{ id: random numbers , value: '' },
],
},
];
it will have a lot of option
and I just would like to update options id
this is what i tried
case COPY_QUESTION: {
const newArray = [...state];
const copyQuestion = newArray[action.payload];
copyQuestion.options.map((option) =>
Object.assign({}, option, {
id: random number,
}),
);
return [...state, copyQuestion];
}
thanks for reading my question.

it's caused due to call-by-reference.
As I can see in your code,
You are copying the reference of an array which might have the reason to overwrite details of the original array when you are typing. You can copy the values of the original array by using Javascript Object Prototype
so in that, you need to destruct your array or break your reference in the duplicate array.
example
let A = [a,b,c] //original Array
let B = JSON.parse(JSON.strigify(A)) // duplicate Array

Related

update data with useState in React js

I am new to react. I have faced one issue and not able to solve it. I am looking for your help.
I have an array which I have listed below. All data are looped and displayed in the view. From my current array, I want to update the count of dietry array[] which is inside the fruits array.
This is my useState
const [foods, setFood] = useState(fruits)
if I console.log(foods) it gives data as below.
fruits: [
{
id: 1,
name: 'Banana',
family: 'abc',
price: 2.99,
isEnabled: true,
dietary: [
{
id:1,
disabled: false,
group: null,
selected: false,
text: 'N/A',
value: '858090000',
count:0
},
{
id:2,
disabled: true,
group: null,
selected: true,
text: 'N/A',
value: '80000',
count:0
},
}
This data are looped in a view page and there is onClick handleIncrement method which should increment the count of dietary array of index 0 or index1 etc whichever index sent from handleIncremnt() method.
This is my method
const handleIncrementCount = (dietary_index) => {
setFood(foods =>
foods.map((food,index) =>
dietary_index === food[index] ? {...food, qty:food.count+1}:food
)
);
}
I am not able to increase the count, in my view page counts are 0 even if i click the increment button.It shows some error within map
Any help is highly appreciated
I ma looking for a solutions
There are a few issues with your handleIncrementCount function. Firstly, you are trying to use the dietary_id parameter to find the correct food object to update, but you are using it to access the index property of the food object instead. This will always return undefined, and so the function will not be able to find the correct object to update.
Secondly, you are trying to update the qty property of the food object, but this property does not exist in the food object. Instead, you need to update the count property of the correct dietary object inside the food object.
Here is how you can fix these issues:
const handleIncrementCount = (dietary_id) => {
setFood(foods =>
foods.map(food => {
// Find the dietary object with the correct id
const dietary = food.dietary.find(d => d.id === dietary_id);
// If the dietary object was found, increment its count
if (dietary) {
dietary.count += 1;
}
// Return the updated food object
return food;
})
);
};
With this change, the handleIncrementCount function should be able to find the correct food object and update the count property of the correct dietary object.
Note that this function is using the Array.prototype.map() and Array.prototype.find() methods to transform the foods array and find the correct dietary object to update. These methods are commonly used in JavaScript to transform and find elements in arrays. It is worth reading up on these methods and understanding how they work in order to better understand this code.

How to prevent adding duplicate objects into array in state in ReactJS?

I am fetching from an API that returns data in the form of an array. Lets say I have an array of 5 objects in this array which are exactly the same. I want to prevent duplicates by checking if duplicate exists before adding to my restaurants State. Below are snippets of my code:
const [restaurants, setRestaurants] = useState([])
const checkForDuplicateBusiness = (businessArray)=>{
businessArray.forEach(singleBusiness=>{
if(!restaurants.some(restaurant=> restaurant.id === singleBusiness.id)){
setRestaurants(restaurants=>[...restaurants, singleBusiness])
console.log(restaurants) //returns []
}
})
}
The problem is that when I am checking with this line
if(!restaurants.some(restaurant=> restaurant.id === singleBusiness.id))
the restaurants state is always empty. I understand that setState is async so the state is not updated by the time im checking it for the next iteration in the forEach loop. Im not sure what to do.
When I console log the restaurants via useEffect like:
useEffect(()=>{
console.log(restaurants)
},[restaurants])
it will show 5 identical objects.
[
0: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
1: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
2: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
3: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
4: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
]
I am confused on this behavior because if i have exactly 5 copies of the object, that means my setRestaurants(restaurants=>[...restaurants, singleBusiness]) was working properly indicative from the spread operator. but my check if statement isnt?
The other solution I've thought of is to store my fetched data in a temporary array and perform the "preventDuplicate" logic in there, and then set it to my restaurants. But I'm not sure if this is the most efficient method. Is there a better way or the "React" way for this?
Any suggestions are appreciated. Thank you!
The setStateSetter function gives you the updated state. Use it higher:
function uniqueById(items) {
const set = new Set();
return items.filter((item) => {
const isDuplicate = set.has(item.id);
set.add(item.id);
return !isDuplicate;
});
}
const updateRestaurantsUnique = (newItems) => {
setRestaurants((restaurants) => {
return uniqueById([...restaurants, ...newItems]);
});
};

React Redux - Reducer with CRUD - best practices?

I wrote simple reducer for User entity, and now I want to apply best practices for it, when switching action types and returning state. Just to mention, I extracted actions types in separate file, actionsTypes.js.
Content of actionsTypes.js :
export const GET_USERS_SUCCESS = 'GET_USERS_SUCCESS';
export const GET_USER_SUCCESS = 'GET_USER_SUCCESS';
export const ADD_USER_SUCCESS = 'ADD_USER_SUCCESS';
export const EDIT_USER_SUCCESS = 'EDIT_USER_SUCCESS';
export const DELETE_USER_SUCCESS = 'DELETE_USER_SUCCESS';
First question, is it mandatory to have actions types for FAILED case? For example, to add GET_USERS_FAILED and so on and handle them inside usersReducer?
Root reducer is:
const rootReducer = combineReducers({
users
});
There is code of usersReducer, and I put comments/questions inside code, and ask for answers (what are best practices to handle action types):
export default function usersReducer(state = initialState.users, action) {
switch (action.type) {
case actionsTypes.GET_USERS_SUCCESS:
// state of usersReducer is 'users' array, so I just return action.payload where it is array of users. Will it automatically update users array on initial state?
return action.payload;
case actionsTypes.GET_USER_SUCCESS:
// What to return here? Just action.payload where it is just single user object?
return ;
case actionsTypes.ADD_USER_SUCCESS:
// what does this mean? Can someone explain this code? It returns new array, but what about spread operator, and object.assign?
return [...state.filter(user => user.id !== action.payload.id),
Object.assign({}, action.payload)];
case actionsTypes.EDIT_USER_SUCCESS:
// is this ok?
const indexOfUser = state.findIndex(user => user.id === action.payload.id);
let newState = [...state];
newState[indexOfUser] = action.payload;
return newState;
case actionsTypes.DELETE_USER_SUCCESS:
// I'm not sure about this delete part, is this ok or there is best practice to return state without deleted user?
return [...state.filter(user => user.id !== action.user.id)];
default:
return state;
}
}
I'm not an experienced developer but let me answer your questions what I've learned and encountered up to now.
First question, is it mandatory to have actions types for FAILED case?
For example, to add GET_USERS_FAILED and so on and handle them inside
usersReducer?
This is not mandatory but if you intend to give a feedback to your clients it would be good. For example, you initiated the GET_USERS process and it failed somehow. Nothing happens on client side, nothing updated etc. So, your client does not know it failed and wonders why nothing happened. But, if you have a failure case and you catch the error, you can inform your client that there was an error.
To do this, you can consume GET_USERS_FAILED action type in two pleases for example. One in your userReducers and one for, lets say, an error or feedback reducer. First one returns state since your process failed and you can't get the desired data, hence does not want to mutate the state anyhow. Second one updates your feedback reducer and can change a state, lets say error and you catch this state in your component and if error state is true you show a nice message to your client.
state of usersReducer is 'users' array, so I just return
action.payload where it is array of users. Will it automatically
update users array on initial state?
case actionsTypes.GET_USERS_SUCCESS:
return action.payload;
This is ok if you are fetching whole users with a single request. This means your action.payload which is an array becomes your state. But, if you don't want to fetch all the users with a single request, like pagination, this would be not enough. You need to concat your state with the fetched ones.
case actionsTypes.GET_USERS_SUCCESS:
return [...state, ...action.payload];
Here, we are using spread syntax.
It, obviously, spread what is given to it :) You can use it in a multiple ways for arrays and also objects. You can check the documentation. But here is some simple examples.
const arr = [ 1, 2, 3 ];
const newArr = [ ...arr, 4 ];
// newArr is now [ 1, 2, 3, 4 ]
We spread arr in a new array and add 4 to it.
const obj = { id: 1, name: "foo, age: 25 };
const newObj = { ...obj, age: 30 };
// newObj is now { id: 1, name: "foo", age: 30 }
Here, we spread our obj in a new object and changed its age property. In both examples, we never mutate our original data.
What to return here? Just action.payload where it is just single user
object?
case actionsTypes.GET_USER_SUCCESS:
return ;
Probably you can't use this action in this reducer directly. Because your state here holds your users as an array. What do you want to do the user you got somehow? Lets say you want to hold a "selected" user. Either you can create a separate reducer for that or change your state here, make it an object and hold a selectedUser property and update it with this. But if you change your state's shape, all the other reducer parts need to be changed since your state will be something like this:
{
users: [],
selectedUser,
}
Now, your state is not an array anymore, it is an object. All your code must be changed according to that.
what does this mean? Can someone explain this code? It returns new
array, but what about spread operator, and object.assign?
case actionsTypes.ADD_USER_SUCCESS:
return [...state.filter(user => user.id !== action.payload.id), Object.assign({}, action.payload)];
I've already tried to explain spread syntax. Object.assign copies some values to a target or updates it or merges two of them. What does this code do?
First it takes your state, filters it and returns the users not equal to your action.payload one, which is the user is being added. This returns an array, so it spreads it and merges it with the Object.assign part. In Object.assign part it takes an empty object and merges it with the user. An all those values creates a new array which is your new state. Let's say your state is like:
[
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
]
and your new user is:
{
id: 3, name: "baz"
}
Here what this code does. First it filters all the user and since filter criteria does not match it returns all your users (state) then spread it (don't forget, filter returns an array and we spread this array into another one):
[ { id: 1, name: "foo"}, { id: 2, name: "bar" } ]
Now the Object.assign part does its job and merges an empty object with action.payload, a user object. Now our final array will be like this:
[ { id: 1, name: "foo"}, { id: 2, name: "bar" }, { id: 3, name: "baz" } ]
But, actually Object.assign is not needed here. Why do we bother merging our object with an empty one again? So, this code does the same job:
case actionsTypes.ADD_USER_SUCCESS:
return [...state.filter(user => user.id !== action.payload.id), action.payload ];
is this ok?
case actionsTypes.EDIT_USER_SUCCESS:
const indexOfUser = state.findIndex(user => user.id === action.payload.id);
let newState = [...state];
newState[indexOfUser] = action.payload;
return newState;
It seems ok to me. You don't mutate the state directly, use spread syntax to create a new one, update the related part and finally set your state with this new one.
I'm not sure about this delete part, is this ok or there is best
practice to return state without deleted user?
case actionsTypes.DELETE_USER_SUCCESS:
return [...state.filter(user => user.id !== action.user.id)];
Again, it seems ok to me. You filter the deleted user and update your state according to that. Of course there are other situations you should take into considiration . For example do you have a backend process for those? Do you add or delete users to a database? If yes for all the parts you need to sure about the backend process success and after that you need to update your state. But this is a different topic I guess.

How does spread operator work in an array vs. obj?

I'm learning Redux from this tutorial and I don't get how the spread operator below works in both the object and array. If ...state returns the same thing, how can it work in both situations? I thought it will just return an array, so it will work inside the SHUTTER_VIDEO_SUCCESS because it'll just spread whatever is inside the state into the new array in addition to the action.videos, but how will this work inside the SELECTED_VIDEO case? There is no key to place it in. The spread operator grabs the array not the key value pair from the default initialState right?
initialState.js
export default {
images: [],
videos: []
};
someComponent.js
import initialState from './initialState';
import * as types from 'constants/actionTypes';
export default function ( state = initialState.videos, action ) {
switch (action.type) {
case types.SELECTED_VIDEO:
return { ...state, selectedVideo: action.video }
case types.SHUTTER_VIDEO_SUCCESS:
return [...state, action.videos];
default:
return state;
}
}
UPDATE
Spread syntax allows you to spread an array into an object (arrays are technically objects, as is mostly everything in js). When you spread an array into an object, it will add a key: value pair to the object for each array item, where the key is the index and the value is the value stored at that index in the array. For example:
const arr = [1,2,3,4,5]
const obj = { ...arr } // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }
const arr2 = [{ name: 'x' }, { name: 'y' }]
const obj2 = { ...arr2 } // { 0: { name: 'x' }, 1: { name: 'y' } }
You can also spread strings into arrays and objects as well. For arrays, it will behave similarly as String.prototype.split:
const txt = 'abcdefg'
const arr = [...txt] // ['a','b','c','d','e','f', 'g']
For objects, it will split the string by character and assign keys by index:
const obj = { ...txt } // { 0:'a',1:'b',2:'c',3:'d',4:'e',5:'f',6:'g' }
So you may be getting data that sort of works when you spread an array into an object. However, if the example you gave is what you're actually using, you're going to run into problems. See below.
=============
In the case of reducers in redux, when you use the spread syntax with an array it spreads each item from your array into a new array. It's basically the same as using concat:
const arr = [1,2,3]
const arr2 = [4,5,6]
const arr3 = [...arr, ...arr2] // [1,2,3,4,5,6]
// same as arr.concat(arr2)
With an object, the spread syntax spreads key: value pairs from one object into another:
const obj = { a: 1, b: 2, c: 3 }
const newObj = { ...obj, x: 4, y: 5, z: 6 }
// { a: 1, b: 2, c: 3, x: 4, y: 5, z: 6 }
These are two ways to help keep your data immutable in your reducers. The spread syntax copies array items or object keys/values rather than referencing them. If you do any changes in nested objects or objects in arrays, you'll have to take that into account to make sure you get new copies instead of mutated data.
If you have arrays as object keys then you can spread the entire object into a new one and then override individual keys as needed, including keys that are arrays that need updating with spread syntax. For example, an update to your example code:
const initialState = {
images: [],
videos: [],
selectedVideo: ''
}
// you need all of your initialState here, not just one of the keys
export default function ( state = initialState, action ) {
switch (action.type) {
case types.SELECTED_VIDEO:
// spread all the existing data into your new state, replacing only the selectedVideo key
return {
...state,
selectedVideo: action.video
}
case types.SHUTTER_VIDEO_SUCCESS:
// spread current state into new state, replacing videos with the current state videos and the action videos
return {
...state,
videos: [...state.videos, ...action.videos]
}
default:
return state;
}
}
This shows updating a state object and specific keys of that object that are arrays.
In the example you give, you're changing the structure of your state on the fly. It starts as an array, then sometimes returns an array (when SHUTTER_VIDEO_SUCCESS) and sometimes returns an object (when SELECTED_VIDEO). If you want to have a single reducer function, you would not isolate your initialState to just the videos array. You would need to manage all of your state tree manually as shown above. But your reducer should probably not switch the type of data it's sending back depending on an action. That would be an unpredictable mess.
If you want to break each key into a separate reducer, you would have 3 (images, videos and selectedVideo) and use combineReducers to create your state object.
import { combineReducers } from 'redux'
// import your separate reducer functions
export default combineReucers({
images,
videos,
selectedVideos
})
In that case each reducer will be run whenever you dispatch an action to generate the complete state object. But each reducer will only deal with its specific key, not the whole state object. So you would only need array update logic for keys that are arrays, etc.
According to the tutorial:
create-react-app comes preinstalled with babel-plugin-transform-object-rest-spread that lets you use the spread (…) operator to copy enumerable properties from one object to another in a succinct way. For context, { …state, videos: action.videos } evaluates to Object.assign({}, state, action.videos).
So, that's not a feature of ES6. It uses a plugin to let you use that feature.
Link: https://babeljs.io/docs/plugins/transform-object-rest-spread/
An array is also a key/value-pair but the key is an index. It's using ES6 destructuring and the spread syntax.
Redux docs on the subject
You may also want to read up on ES6 property value shorthand (or whatever it is called):
ES6 Object Literal in Depth
Whenever you find yourself assigning a property value that matches a property name, you can omit the property value, it’s implicit in ES6.

How to update the value of a single property within a state object in React.js?

So I have the following object structure:
const SamplePalette = {
id: 1,
name: "Sample Palette",
description: "this is a short description",
swatches: [
{
val: "#FF6245",
tints: ["#FFE0DB", "#FFA797"],
shades: ["#751408", "#C33F27"]
},
{
val: "#FFFDA4",
tints: ["#FFFFE1"],
shades: ["#CCCB83"]
},
{
val: "#BFE8A3",
tints: ["#E7FFD7"],
shades: ["#95B77E"]
}
]
}
Let's imagine that this object is managed by the state of my app like this:
this.state = {
currentPalette: SamplePalette,
}
My question is how would I go about updating the val property of a given swatch object in the swatches array? Or more generally - how do I only update pieces of this object?
I tried using the update helper as well as to figure out how Object.assign() works, however I've been unsuccessful and frankly can't really grasp the syntax by just looking at examples.
Also, since I'm going to be modifying this object quite a lot, should I look into maybe using Redux?
[EDIT]
I tried #maxim.sh suggestion but with no success:
this.setState(
{ currentPalette: {...this.state.currentPalette,
swatches[0].val: newValue}
})
Consider you have new new_swatches
I think the clearer way is to get array, update it and put back as:
let new_swatches = this.state.currentPalette.swatches;
new_swatches[0].val = newValue;
this.setState(
{ currentPalette:
{ ...this.state.currentPalette, swatches: new_swatches }
});
Also you have : Immutability Helpers or https://github.com/kolodny/immutability-helper
Available Commands
{$push: array} push() all the items in array on the target.
{$unshift: array} unshift() all the items in array on the target.
{$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
{$set: any} replace the target entirely.
{$merge: object} merge the keys of object with the target.
{$apply: function} passes in the current value to the function and updates it with the new returned value.

Categories

Resources