How to update new object into the existing object in ReactJS? - javascript

I have one object named waA which is required in final step. But in ReactJS the new object is not getting updated in the previous object using switch in a function.
The object code is given below:
waA = {
jsonObject: {
wAConfigur: {}
}
}
var updatewaA = waA.jsonObject.wAConfigur;
The button has onClick event and its function is:
function next(){
switch(case){
case 1:
const bD = {
'name': 'john',
'title': 'Boy',
}
updatewaA.configureSet = bD;
break;
case 1:
const cE = {
'age': 34,
}
updatewaA.newDate = bD;
break;
}
}
The final Object which is needed is:
{
jsonObject: {
wAConfigur: {
configureSet: {
'name': 'john',
'title': 'Boy',
},
newDate: {
'age': 34
}
}
}
}
But for some reason in ReactJS the object is not getting updated.
How can I be able to do it? Thank you.

You need to store the object inside the react state to later make it update and render new state to the dom.
import {useState} from "react"
// new state waA and updater function setWaA
const [waA, setWaA] = useState({
jsonObject: {
wAConfigur: {},
},
})
function next(number){
switch(number){
case 1:
const bD = {
'name': 'john',
'title': 'Boy',
}
// here we added configureSet
setWaA(prevWaA => {
...prevWaA,
jsonObject: {
...prevWaA.jsonObject,
configureSet = bD
}
})
const cE = {
'age': 34,
}
// here we added newDate
setWaA(prevWaA => {
...prevWaA,
jsonObject: {
...prevWaA.jsonObject,
newDate = cE
}
})
break;
}
}

Continuing on the answer given by Abhishek, in ReactJS you should take care of using objects and arrays in a functional way, keeping them constants throughout the entire application scope so that you don't change your variables during usage.
This ensures that each step the variable is used in is "new" and unchanged, rather, cloned from the previous value without actually changing the previous one. It's also best because objects and arrays are references in memory and are not actual values, hence if you assign an object or array, you are actually assigning the reference, not the value of it.
With React you can spread the object or array to clone it but take care of the object because it's not a deep clone like Lodash would do, so you have to do as specified by Abhiskek.
import { useState } from 'react'
function Task() {
const [config, setConfig] = useState(initialObjectConfig)
useEffect(() => {
setConfig(prevWaA => {
...prevWaA,
jsonObject: {
...prevWaA.jsonObject,
newDate = cE
}
})
}, [])
}
You can apply the same thought to a Switch statement but if you don't want to keep state immutability you can take a look at immer.js which is a library that can help you achieve exactly what you are looking for.

Related

What should the output of a JavaScript Class look like?

I am attempting to use inquirer to collect user input, that is turned into an object, it is then passed into a class constructor. Everything seems to working the way I want it to accept the resulting array of objects after they are passed though the classes come out in a way that is confusing me,
when I console.log the array that contains the objects that are returned by my classes this is what comes out:
[
Manager {
name: 'bob',
id: '24',
email: 'bob.com',
officeNumber: '1'
},
Engineer {
name: 'jack',
id: '347',
email: 'jack.com',
github: 'jackolantern'
},
Intern {
name: 'sally',
id: '987',
email: 'sally.com',
school: 'UCF'
}
]
Here is an example of one of the classes:
class Manager extends Employee {
constructor (employeeObj) {
super (employeeObj);
this.name = employeeObj.name;
this.id = employeeObj.id;
this.email = employeeObj.email;
this.officeNumber = employeeObj.officeNumber;
}
getOfficeNumber () {
return this.officeNumber;
}
getRoll () {
return "Manager";
}
}
module.exports = Manager;
and this is how the objects are passed into the classes:
const prompts = async () => {
let employeeObj = {};
const employee = await inquirer.prompt(addEmployee);
switch(employee.addEmployee){
case 'Manager':
employeeObj = await managerQuestions();
const manager = new Manager(employeeObj);
output.push(manager);
if(employeeObj.addAnother){
return prompts();
} else {
complete();
}
break;
case 'Engineer':
employeeObj = await engineerQuestions();
const engineer = new Engineer(employeeObj)
output.push(engineer)
if(employeeObj.addAnother){
return prompts();
} else {
complete();
}
break;
case 'Intern':
employeeObj = await internQuestions();
const intern = new Intern(employeeObj)
output.push(intern)
if(employeeObj.addAnother){
return prompts();
} else {
complete();
}
break;
default:
console.log('you have reached the default switch statement. thant should not happen. Please try again!');
}
}
what I cant seem to figure out, is why the "roll" for each object (Manager, Engineer, Intern) is being placed outside the corresponding object and not inside it. I was able to add a this.roll = "manager" inside the constructor and as expected it added in a property called roll with a value of "manager" which will work just fine for what I need to do, but how do I get rid of that Manager, Engineer, and Intern that shows up before each object in the output array, or can it be moved inside the object?
thank you for taking the time to read through all this.

Prevent prop from overwriting the data

I'm new to vue.js and struggling with the following scenario.
I send an array filled with objects via props to my router-view.
Inside one of my router-view components I use this array in multiple functions, reference it with 'this.data' and safe it inside the functions in a new variable so I don't overwrite the actual prop data.
However the functions overwrite the original prop data and manipulate the data of the prop.
Here is an abstract example of my question:
App.vue
<template>
<div>
<router-view :data='data'></router-view>
</div>
</template>
<script>
export default {
data: function() {
return {
data: [],
};
},
created: function() {
this.getData();
},
methods: {
getData: function() {
this.data = // array of objects
},
}
route component:
<script>
export default {
props: {
data: Array,
},
data: function() {
return {
newData1 = [],
newData2 = [],
}
}
created: function() {
this.useData1();
this.useData2();
},
methods: {
useData1: function() {
let localData = this.data;
// do something with 'localData'
this.newData1 = localData;
}
useData2: function() {
let localData = this.data;
// do something with 'localData'
this.newData2 = localData;
}
}
}
</script>
The 'localData' in useData2 is manipulated from changes in useData1, whereby I don't overwrite the data prop.
Why do I overwrite the prop and how can i prevent it?
The problem you're experiencing a side effect of copying this.data by reference, rather than value.
The solution is to use a technique commonly referred to as cloning. Arrays can typically be cloned using spread syntax or Array.from().
See below for a practical example.
// Methods.
methods: {
// Use Data 1.
useData1: function() {
this.newData1 = [...this.data]
},
// Use Data 2.
useData2: function() {
this.newData2 = Array.from(this.data)
}
}
#Arman Charan is right on his answer. Object and arrays are not primitive types but reference.
There is an awesome video explanation here => JavaScript - Reference vs Primitive Values/ Types
So for reference types you first have to clone it on another variable and later modify this variable without the changes affecting the original data.
However for nested arrays and objects in high level the spead and Array.from will not work.
If you are using Lodash you can use _.cloneDeep() to clone an array or an object safely.
I like functional programming and I use Lodash which I strongly recommend.
So you can do:
let original_reference_type = [{ id:1 }, { id: 2 }]
let clone_original = _.cloneDeep(original_reference_type)
clone_original[0].id = "updated"
console.log(original_reference_type) //[{ id:1 }, { id: 2 }] => will not change
console.log(clone_original) // [{ id: "updated" }, { id: 2 }]
Suggestion: For simple arrays and objects use:
Objects:
let clone_original_data = {...original_data} or
let clone_original_data = Object.assign({}, original_data)
Arrays:
let clone_original_data = [...original_data] or
let clonse_original_data = original_data.slice()
For complex and high nested arrays or Objects go with Lodash's _.cloneDeep()
I think this is most readable, "declarative" way:
First, install lodash npm i lodash. Then import desired function, not the whole library, and initialize your data with array from props.
<script>
import cloneDeep from 'lodash/cloneDeep'
export default {
props: {
data: Array
},
data () {
return {
// initialize once / non reactive
newData1: cloneDeep(this.data),
newData2: cloneDeep(this.data)
}
}
}
</script>

Ensuring immutability of the original when setting and deleting from a map object

I've been studying how to accomplish immutability/functional programming in JavaScript and I am having difficulties with the following code. The object returned from the generateExample function contains methods to add and remove keys/values from a map, by iterating over an array of objects.
Scenario 1 simply uses the map methods set and delete methods and mutates the map. Scenario 2 creates a new map prior to setting and deleting keys/values.
I could clone the map for each new set, in each foreach loop, but I'd imagine that would be too computationally expensive.
I could also use Immutable.js (Map) and have a new data structure returned for each set and delete. I don't really want to do this.
The object returned from generateExample in this example would be the only object that that could set and delete to the map, so perhaps worrying about immutability in this is an example is not important?
What I am trying to understand is what is the best way to maintain immutability of the original map when setting and deleting keys/values and if it's even relevant?
const sampleList = [
{
id: 'dog',
value: 'Dog',
},
{
id: 'cat',
value: 'Cat',
},
]
SCENARIO 1
const example = function generateExample() {
const container = new Map()
return {
add(list) {
list
.forEach((x) => {
container.set(x.id, x)
})
},
remove(list) {
list
.forEach((x) => {
container.delete(x.id)
})
},
read(id) {
return container.get(id)
},
}
}
SCENARIO 2
const example = function generateExample() {
let container = new Map()
return {
add(list) {
container = new Map(container)
list
.forEach((x) => {
container.set(x.id, x)
})
},
remove(list) {
container = new Map(container)
list
.forEach((x) => {
container.delete(x.id)
})
},
read(id) {
return container.get(id)
},
}
}
const test = example()
test.add(sampleList)
console.log(test.read('dog'))
In both cases you have mutations. In the first scenario the mutation is over Map object, in the second scenario the mutation is over container value itself then again on Map object.I'll suggest not to have this layer but just treating the Map objects as they are, but if the case must be like this, I'll prefer to do something like this
const example = function generateExample(defaultList) {
let container = new Map(defaultList)
return {
add(list) {
container = new Map(container.concat(list));
},
remove(list) {
container = new Map(container.filter(item => !list.includes(item)))
},
read(id) {
return container.get(id)
},
}
}

How to update single value inside specific array item in redux

I have an issue where re-rendering of state causes ui issues and was suggested to only update specific value inside my reducer to reduce amount of re-rendering on a page.
this is example of my state
{
name: "some name",
subtitle: "some subtitle",
contents: [
{title: "some title", text: "some text"},
{title: "some other title", text: "some other text"}
]
}
and I am currently updating it like this
case 'SOME_ACTION':
return { ...state, contents: action.payload }
where action.payload is a whole array containing new values. But now I actually just need to update text of second item in contents array, and something like this doesn't work
case 'SOME_ACTION':
return { ...state, contents[1].text: action.payload }
where action.payload is now a text I need for update.
You can use map. Here is an example implementation:
case 'SOME_ACTION':
return {
...state,
contents: state.contents.map(
(content, i) => i === 1 ? {...content, text: action.payload}
: content
)
}
You could use the React Immutability helpers
import update from 'react-addons-update';
// ...
case 'SOME_ACTION':
return update(state, {
contents: {
1: {
text: {$set: action.payload}
}
}
});
Although I would imagine you'd probably be doing something more like this?
case 'SOME_ACTION':
return update(state, {
contents: {
[action.id]: {
text: {$set: action.payload}
}
}
});
Very late to the party but here is a generic solution that works with every index value.
You create and spread a new array from the old array up to the index you want to change.
Add the data you want.
Create and spread a new array from the index you wanted to change to the end of the array
let index=1;// probably action.payload.id
case 'SOME_ACTION':
return {
...state,
contents: [
...state.contents.slice(0,index),
{title: "some other title", text: "some other text"},
...state.contents.slice(index+1)
]
}
Update:
I have made a small module to simplify the code, so you just need to call a function:
case 'SOME_ACTION':
return {
...state,
contents: insertIntoArray(state.contents,index, {title: "some title", text: "some text"})
}
For more examples, take a look at the repository
function signature:
insertIntoArray(originalArray,insertionIndex,newData)
Edit:
There is also Immer.js library which works with all kinds of values, and they can also be deeply nested.
You don't have to do everything in one line:
case 'SOME_ACTION': {
const newState = { ...state };
newState.contents =
[
newState.contents[0],
{title: newState.contents[1].title, text: action.payload}
];
return newState
};
I believe when you need this kinds of operations on your Redux state the spread operator is your friend and this principal applies for all children.
Let's pretend this is your state:
const state = {
houses: {
gryffindor: {
points: 15
},
ravenclaw: {
points: 18
},
hufflepuff: {
points: 7
},
slytherin: {
points: 5
}
}
}
And you want to add 3 points to Ravenclaw
const key = "ravenclaw";
return {
...state, // copy state
houses: {
...state.houses, // copy houses
[key]: { // update one specific house (using Computed Property syntax)
...state.houses[key], // copy that specific house's properties
points: state.houses[key].points + 3 // update its `points` property
}
}
}
By using the spread operator you can update only the new state leaving everything else intact.
Example taken from this amazing article, you can find almost every possible option with great examples.
This is remarkably easy in redux-toolkit, it uses Immer to help you write immutable code that looks like mutable which is more concise and easier to read.
// it looks like the state is mutated, but under the hood Immer keeps track of
// every changes and create a new state for you
state.x = newValue;
So instead of having to use spread operator in normal redux reducer
return {
...state,
contents: state.contents.map(
(content, i) => i === 1 ? {...content, text: action.payload}
: content
)
}
You can simply reassign the local value and let Immer handle the rest for you:
state.contents[1].text = action.payload;
Live Demo
In my case I did something like this, based on Luis's answer:
// ...State object...
userInfo = {
name: '...',
...
}
// ...Reducer's code...
case CHANGED_INFO:
return {
...state,
userInfo: {
...state.userInfo,
// I'm sending the arguments like this: changeInfo({ id: e.target.id, value: e.target.value }) and use them as below in reducer!
[action.data.id]: action.data.value,
},
};
Immer.js (an amazing react/rn/redux friendly package) solves this very efficiently. A redux store is made up of immutable data - immer allows you to update the stored data cleanly coding as though the data were not immutable.
Here is the example from their documentation for redux:
(Notice the produce() wrapped around the method. That's really the only change in your reducer setup.)
import produce from "immer"
// Reducer with initial state
const INITIAL_STATE = [
/* bunch of todos */
]
const todosReducer = produce((draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find(todo => todo.id === action.id)
todo.done = !todo.done
break
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
})
break
default:
break
}
})
(Someone else mentioned immer as a side effect of redux-toolkit, but you should use immer directly in your reducer.)
Immer installation:
https://immerjs.github.io/immer/installation
This is how I did it for one of my projects:
const markdownSaveActionCreator = (newMarkdownLocation, newMarkdownToSave) => ({
type: MARKDOWN_SAVE,
saveLocation: newMarkdownLocation,
savedMarkdownInLocation: newMarkdownToSave
});
const markdownSaveReducer = (state = MARKDOWN_SAVED_ARRAY_DEFAULT, action) => {
let objTemp = {
saveLocation: action.saveLocation,
savedMarkdownInLocation: action.savedMarkdownInLocation
};
switch(action.type) {
case MARKDOWN_SAVE:
return(
state.map(i => {
if (i.saveLocation === objTemp.saveLocation) {
return Object.assign({}, i, objTemp);
}
return i;
})
);
default:
return state;
}
};
I'm afraid that using map() method of an array may be expensive since entire array is to be iterated. Instead, I combine a new array that consists of three parts:
head - items before the modified item
the modified item
tail - items after the modified item
Here the example I've used in my code (NgRx, yet the machanism is the same for other Redux implementations):
// toggle done property: true to false, or false to true
function (state, action) {
const todos = state.todos;
const todoIdx = todos.findIndex(t => t.id === action.id);
const todoObj = todos[todoIdx];
const newTodoObj = { ...todoObj, done: !todoObj.done };
const head = todos.slice(0, todoIdx - 1);
const tail = todos.slice(todoIdx + 1);
const newTodos = [...head, newTodoObj, ...tail];
}
Pay attention to the data structure:
in a project I have data like this
state:{comments:{items:[{...},{...},{...},...]} and to update one item in items I do this
case actionTypes.UPDATE_COMMENT:
const indexComment = state.comments.items.findIndex(
(comment) => comment.id === action.payload.data.id,
);
return {
...state,
comments: {
...state.comments,
items: state.comments.items.map((el, index) =>
index === indexComment ? { ...el, ...action.payload.data } : el,
),
},
};
Note: in newer versions (#reduxjs/toolkit), Redux automatically detects changes in object, and you don't need to return a complete state :
/* reducer */
const slice = createSlice({
name: 'yourweirdobject',
initialState: { ... },
reducers: {
updateText(state, action) {
// updating one property will cause Redux to update views
// only depending on that property.
state.contents[action.payload.id].text = action.payload.text
},
...
}
})
/* store */
export const store = configureStore({
reducer: {
yourweirdobject: slice.reducer
}
})
This is how you should do now.

Updating json with lodash

Actually I need to handle mysite frontend fully with json objects(React and lodash).
I am getting the initial data via an ajax call we say,
starred[] //returns empty array from server
and am adding new json when user clicks on star buton it,
starred.push({'id':10,'starred':1});
if the user clicks again the starred should be 0
current_star=_findWhere(starred,{'id':10});
_.set(curren_star,'starred',0);
but when doing console.log
console.log(starred); //returns
[object{'id':10,'starred':0}]
but actually when it is repeated the global json is not updating,while am performing some other operations the json is like,
console.log(starred); //returns
[object{'id':10,'starred':1}]
How to update the global , i want once i changed the json, it should be changed ever.Should I get any idea of suggesting some better frameworks to handle json much easier.
Thanks before!
Working with arrays is complicated and usually messy. Creating an index with an object is usually much easier. You could try a basic state manager like the following:
// This is your "global" store. Could be in a file called store.js
// lodash/fp not necessary but it's what I always use.
// https://github.com/lodash/lodash/wiki/FP-Guide
import { flow, get, set } from 'lodash/fp'
// Most basic store creator.
function createStore() {
let state = {}
return {
get: path => get(path, state),
set: (path, value) => { state = set(path, value, state) },
}
}
// Create a new store instance. Only once per "app".
export const store = createStore()
// STARRED STATE HANDLERS
// Send it an id and get back the path where starred objects will be placed.
// Objects keyed with numbers can get confusing. Creating a string key.
const starPath = id => ['starred', `s_${id}`]
// Send it an id and fieldId and return back path where object will be placed.
const starField = (id, field) => starPath(id).concat(field)
// import to other files as needed
// Add or replace a star entry.
export const addStar = item => store.set(starPath(item.id), item)
// Get a star entry by id.
export const getStar = flow(starPath, store.get)
// Get all stars. Could wrap in _.values() if you want an array returned.
export const getStars = () => store.get('starred')
// Unstar by id. Sets 'starred' field to 0.
export const unStar = id => store.set(starField(id, 'starred'), 0)
// This could be in a different file.
// import { addStar, getStar, getStars } from './store'
console.log('all stars before any entries added:', getStars()) // => undefined
const newItem = { id: 10, starred: 1 }
addStar(newItem)
const star10a = getStar(10)
console.log('return newItem:', newItem === star10a) // => exact match true
console.log('star 10 after unstar:', star10a) // => { id: 10, starred: 1 }
console.log('all stars after new:', getStars())
// Each request of getStar(10) will return same object until it is edited.
const star10b = getStar(10)
console.log('return same object:', star10a === star10b) // => exact match true
console.log('return same object:', newItem === star10b) // => exact match true
unStar(10)
const star10c = getStar(10)
console.log('new object after mutate:', newItem !== star10c) // => no match true
console.log('star 10 after unstar:', getStar(10)) // => { id: 10, starred: 0 }
console.log('all stars after unstar:', getStars())
I think the problem is in mutating original state.
Instead of making push, you need to do the following f.e.:
var state = {
starred: []
};
//perform push
var newItem = {id:10, starred:1};
state.starred = state.starred.concat(newItem);
console.log(state.starred);
//{ id: 10, starred: 1 }]
var newStarred = _.extend({}, state.starred);
var curr = _.findWhere(newStarred, {id: 10});
curr.starred = 0;
state = _.extend({}, state, {starred: newStarred});
console.log(state.starred)
//{ id: 10, starred: 0 }]
To solve this in a more nice looking fashion, you need to use either React's immutability helper, or ES6 stuff, like: {...state, {starred: []}} instead of extending new object every time. Or just use react-redux =)

Categories

Resources