How to access child key-values on objects and clonate that object - javascript

I'm tryin to make a list from an object on React. Before I continue, I'll share my code:
const genres = {
Rock: {
album1: '',
album2: '',
},
Jazz: {
album1: '',
album2: '',
},
Pop: {
album1: '',
album2: '',
}
};
let myFunc = genress => {
let newObject = {};
Object.keys(genress).map(gen => {
newObject[gen] = 'a';
let newChild = newObject[gen];
let oldChild = genress[gen];
Object.keys(oldChild).map(gen2 => {
newChild[gen2] = 'b';
let newGrandChild = newChild[gen2];
console.log(newGrandChild);
})
});
return newObject;
}
myFunc(genres);
I wanna render that object on a list.
<ul>
<li>Rock
<ul>
<li>album1</li>
<li>album2</li>
</ul>
</li>
</ul>
...And so on
Before placing it on React I'm trying it on a normal function. I'm making a new object just to make sure I'm accesing the right values. The problem is, when I return the new object at the end of the function it returns the genres but not the albums, only the 'a' I set in the first Object.key. The console.log on the second Object.key logs undefined, and can't figure out why this is happening.
My idea is to have access to every level on the object so I can set them to variables and return them on the render's Component. I'll make more levels: Genres -> Bands -> Albums -> songs.
Thanks so much in advance :)

From what I can understand is that you are iterating over the object incorrectly.
The reason why 'a' is the only thing showing up is that you are hard coding that every time you run the loop and setting that current key that that value.
So essentially your code does not work because you set the value of the current key to be 'a' which is a string so there are no keys on 'a' so the second loop does not produce anything.
newObject[gen] = 'a'; // you are setting obj[Rock]='a'
let newChild = newObject[gen]; // this is just 'a'
let oldChild = genress[gen]; // this is just 'a'
Object.keys(newObject[gen]) // undefined
What I think you are trying to do is iterate over the object and then render the contents of that object in a list.
Let me know if the below answers your question.
You can see the code working here:
https://codesandbox.io/s/elastic-dhawan-01sdc?fontsize=14&hidenavigation=1&theme=dark
Here is the code sample.
import React from "react";
import "./styles.css";
const genres = {
Rock: {
album1: "Hello",
album2: "Rock 2"
},
Jazz: {
album1: "",
album2: ""
},
Pop: {
album1: "",
album2: ""
}
};
const createListFromObject = (key) => {
return (
<div>
<h1>{key}</h1>
<ul>
{Object.entries(genres[key]).map(([k, v], idx) => (
<li key={`${key}-${k}-${v}-${idx}`}>{`Key: ${k} Value ${v}`}</li>
))}
</ul>
</div>
);
};
export default function App() {
return (
<div className="App">{Object.keys(genres).map(createListFromObject)}</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Related

Why I can't delete a user object this way?

Hello Everyone !
I'm new to ReactJS, and I'm trying to do some simple projects to get the basics.
I started a little project consisting at adding and deleting user of a list with React Hooks (manipulating the state).
I can properly add a new User to my Userlist and display it, but when it comes to delete a user, nothing happened.
I found the solution, but I can't explain it, that's why I'm asking for your help !
Here is my App.js file with the DeleteUser function that works
import style from './App.module.css';
import React, { useState } from 'react';
import UserList from './components/UserList';
import UserForm from './components/UserForm'
let USERS = [
{
id: 1,
name: 'John',
age: 27
},
{
id: 2,
name: 'Mark',
age: 24
}
]
const App = () => {
const [userss, SetUsers] = useState(USERS);
const AddNewUser = (user) => {
SetUsers((prevList) => {
let updatedList = [...prevList];
updatedList.unshift(user);
return updatedList;
});
};
const DeleteUser = user => {
SetUsers((prevList) => {
let updated = prevList.filter(el => el.id !== user.id);
return updated;
});
};
return (
<div className={style.root}>
<UserForm liftUpNewUser={AddNewUser} />
<UserList users={userss} liftUpUserToDelete={DeleteUser} />
</div>
);
}
export default App;
My Question is:
Why does the DeleteUser function writtten this way (below) doesn't work ? Knowing that it is the same logic as the AddNewUser function.
const DeleteUser = user => {
SetUsers((prevList) => {
let updated = [...prevList];
updated.filter(el => el.id !== user.id);
return updated;
});
};
Sorry in advance for my english!
Hope someone can help me =)
This line in your code...
updated.filter(el => el.id !== user.id);
... is a no-op, as value of updated array never gets changed. filter returns a new array instead, and this new array gets assigned to a variable in the first snippet.
The side effect of this is that React won't have to compare those arrays by value: their references will be different. It wouldn't have been the case if filter worked the way you expected it to work, making the changes in-place, similar to Array.splice.
Because Array.filter method does not modify the original array but returns a new one.

Vue 3, composition API, Array of refs doesn't work

Please see below code.
<template>
<div v-for="item in arr" :key="item">{{ item }}</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "TestArr",
setup() {
const arr = [];
arr.push(ref("a"));
arr.push(ref("b"));
arr.push(ref("c"));
return { arr };
}
};
</script>
And the output is below
{ "_rawValue": "a", "_shallow": false, "__v_isRef": true, "_value": "a" }
{ "_rawValue": "b", "_shallow": false, "__v_isRef": true, "_value": "b" }
{ "_rawValue": "c", "_shallow": false, "__v_isRef": true, "_value": "c" }
expected output
a
b
c
I have to call item.value in the template to make it work.
What's the work around for this scenario in vue3?
Cheers!
You are doing it wrong; try following
setup() {
const arr = ref([]);
arr.value.push("a");
arr.value.push("b");
arr.value.push("c");
return { arr };
}
There is no point adding ref items to a normal array. The Array itself should be ref.
Some information about using array with ref() and reactive() which may be helpful.
Recently, I am learning composition API by developing a simple todo list app. I ran into some problems when dealing with array by using ref() and reactive() and found some behaviors which may be helpful for folks who are learning composition API too, so I wrote down some words here. If there is something wrong, please tell me!
1. What is the problem when I use reactive() to deal with array?
So...at first everything just work as I expected until I working on developing delete function.
I tried to build a button which will trigger the deleteHandler function when it been click. And the deleteHandler would filter out the element in todos:
Here is my code:
<template>
<div>
<h1>reactive</h1>
<button #click="add">click</button>
<div v-for="item in todos" :key="item">
<button #click="mark(item)">mark</button>
<span>{{item}}</span>
<button #click="deleteHandler(item.id)">delete</button>
</div>
</div>
</template>
<script>
import {reactive, ref} from "vue";
export default {
name: "ReactiveMethod",
setup(){
let todos = reactive([])
const id = ref(0);
function add(){
todos.push({id:id.value, name:"hallo", state:"undone"});
id.value += 1
}
function mark(item){
if(item.state === "undone"){
item.state = "done"
}else{
item.state = "undone"
}
}
function deleteHandler(id){
const temp = todos.filter((element)=>{
return element.id !== id
})
todos = temp
}
return {
todos,
id,
deleteHandler,
add,
mark
}
}
}
</script>
However, I face a crucial problem, since the filter function would not mutate the original value but return a new value. Vue could not detect the change inside todos.
To solve this problem, I rewrite my code. Instead of assigning todos to reactive([]), I warpped the array with object like this -> reactive({todos:[]}). And it works !
<template>
<div>
<h1>reactive</h1>
<button #click="add">click</button>
<div v-for="item in todos" :key="item">
<button #click="mark(item)">mark</button>
<span>{{item}}</span>
<button #click="deleteHandler(item.id)">delete</button>
</div>
</div>
</template>
<script>
import {reactive, ref, toRefs} from "vue";
export default {
name: "ReactiveMethod",
setup(){
const state = reactive({
todos:[]
})
const id = ref(0);
function add(){
state.todos.push({id:id.value, name:"hallo", state:"undone"});
id.value += 1
}
function mark(item){
if(item.state === "undone"){
item.state = "done"
}else{
item.state = "undone"
}
}
function deleteHandler(id){
const temp = state.todos.filter((element)=>{
return element.id !== id
})
state.todos = temp
}
return {
...toRefs(state),
id,
deleteHandler,
add,
mark
}
}
}
</script>
conclusion
It seems that vue could only watch on the change with same reference(object in JavaScript is called by reference), but could not detect the change when the reference is changed. As a resault, I think "wrap the array inside object" is a better way to deal with array in composition API.
2. ref() for primitive value and reactive() value?
According to the most information we could found, It seems that we can make a conclusion:
ref() for primitive value and reactive() value
However, if we write some code like this, Vue is still able to detect the change inside it:
const obj = ref({name:"charles"});
return{
...toRefs(obj)
}
The reason is that when we pass data into ref(), it would first check whether the data been sended is primitive or object. If it is object, ref() would call reactive() to deal with it.In other words, reactive() is the one who actually take on the job behind the scene.
little conclusion
At this stage, it seems that we can use ref() anytime. However, I think it's better to use reactive() for object and ref() for primitive to make difference!(If you have any ideas about this topic, please share it to me !)
This is the correct answer
setup() {
const arr = ref([]);
arr.value.push("a");
arr.value.push("b");
arr.value.push("c");
console.log(arr.value)
return { arr };
}
This option is possible, but the first is much better.
const arr = reactive([]);
arr.push("a")
arr.push("b")
arr.push("c")
console.log(arr)
They should be accessed using value field :
setup() {
const arr = [];
arr.push(ref("a").value);
arr.push(ref("b").value);
arr.push(ref("c").value);
return { arr };
}
but this is a bad practice, your should define your array as ref then push values to it :
setup() {
const arr = ref([]);
arr.value.push("a");
arr.value.push("b");
arr.value.push("c");
return { arr };
}
another crafted solution is to init the array with that values :
setup() {
const arr = ref(["a","b","c"]);
return { arr };
}

Why we are making copy of reference types in react before we mutate them?

I am new in react world. I have this example code here where with the deletePersonHandler method i am deleting some objects from the array.
class App extends Component {
state = {
persons: [
{ name: "peter", age: 24 },
{ name: "john", age: 25 },
{ name: "jake", age: 30 }
],
showPersons: true
}
deletePersonHandler = index => {
const persons = this.state.persons;
persons.splice(index,1);
this.setState({persons:persons})
}
togglePersonsHandler = () => {
this.setState({ showPersons: !this.state.showPersons })
}
render() {
let persons = null;
if (this.state.showPersons) {
persons = (
<div>
{this.state.persons.map((person, index) => {
return <Person
click={() => this.deletePersonHandler(index)}
name={person.name}
age={person.age}
key={index}
/>
})}
</div>
);
}
return (
<div className="App">
{persons}
<button onClick={this.togglePersonsHandler}>Click to hide/show</button>
</div>
)
}
}
export default App;
and it is working fine.
My question is:
when this works - without making copy in this case on persons
deletePersonHandler = index => {
const persons = this.state.persons;
persons.splice(index,1);
this.setState({persons:persons})
}
why the recommended way is to make the COPY FIRST AND THEN MODIFY THE REFERENCE TYPE ?
deletePersonHandler = index => {
const persons = [...];
persons.splice(index,1);
this.setState({persons:persons})
}
I am explaining this based on my experience with object in JavaScript. I had one publisher and subscriber code where there was an array of object which used to keep track of some message number and their handler like this
let observer = {"8000":[handler1, handler2]};
So when something happens i publish 8000 message and all handlers get executed like this
for(var item in observer[8000]){
//execute handler/
}
. Till here it was working pretty cool. Then I started removing handler when it has been processed. So after removing handler length of array observer[8000] reduced by 1. So in next sequence it could not find next handler which didn't execute(Objects are pass by reference in JavaScript). So to resolve this I had to make a copy of array object before directly modifying this. In short if object has many dependencies then before processing make copy of it then process or if it is used only single place then use in place processing. It depends on situation, there aren't any strict rules to follow like copy then process.

How do I create a new JSON object inside a react hook?

I have two issues first how do I add/update the JSON items within a hook?
The other being that React won't let me use the name stored from a previous JSON file.
I am open to other solutions, basically, as my input field are dynamically generated from a JSON file I'm unsure of the best way to store or access the data that's input into them I think storing them in a react hook as JSON and then passing them though as props to another component is probably best.
What I want to happen is onChange I would like the quantity value to be stored as a JSON object in a Hook here's my code:
React:
import React, { useState, useEffect } from 'react';
import Data from '../shoppingData/Ingredients';
import Quantities from '../shoppingData/Quantities';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState('pageOne');
//where I want to store the JSON data
const [Quantities, setQuantities] = useState({});
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show('pageOne');
} else {
setPageone_show('pageOne hide');
}
}, [props.showOne]);
return (
<div className={'Shopping_Content ' + pageone_show}>
//generates input fields from JSON data
{Data.map((Ingredients) => {
const handleChange = (event) => {
// this is where I'd like the Hook to be updated to contain instances of the ingredients name and quantity of each
setQuantities(
(Ingredients.Name: { ['quantities']: event.target.value })
);
console.log(Quantities);
};
return (
<div className="Shopping_input" key={Ingredients.Name}>
<p>
{Ingredients.Name} £{Ingredients.Price}
</p>
<input
onChange={handleChange.bind(this)}
min="0"
type="number"
></input>
</div>
);
})}
<div className="Shopping_Buttons">
<p onClick={props.next_ClickHandler}>Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
JSON file:
//Json data for the shopping ingredients
export default [
{
Name: 'Bread',
Price: "1.10",
},
{
Name: 'Milk',
Price: "0.50",
},
{
Name: 'Cheese',
Price: "0.90",
},
{
Name: 'Soup',
Price: "0.60",
},
{
Name: 'Butter',
Price: "1.20",
}
]
Assuming your Quantities object is meant to look like:
{
<Ingredient Name>: { quantities: <value> }
}
you need to change your handleChange to look like this
const handleChange = (event) => {
setQuantities({
...Quantities,
[Ingredients.Name]: {
...(Quantities[Ingredients.Name] ?? {}),
quantities: event.target.value
}
});
};
Explanation
When updating state in React, it is important to replace objects rather than mutating existing ones, as this is what tells React to rerender components. This is commonly done using the spread operator, and with array functions such as map and filter. For example:
const myObject = { test: 1 };
myObject.test = 2; // Mutates existing object, wrong!
const myNewObject = { ...myObject, test: 2 }; // Creates new object, good!
Note the spread operator doesn't operate below the first level, what I mean by that is, objects within the object will be copied by reference, for example:
const myObject = { test : { nested: 1 } };
const myObject2 = { ...myObject };
myObject2.test.nested = 2;
console.log(myObject.test.nested); // outputs 2
Also in my answer, I have used the nullish coalescing operator (??), this will return it's right operand if the left operand is null or undefined, for example:
null ?? 'hello'; // resolves to "hello"
undefined ?? 'world'; // resolves to "world"
"foo" ?? "bar"; // resolves to "foo"
In my answer I used it to fallback to an empty object if Quantities[Ingredients.Name] is undefined.
Finally, I used square brackets when using a variable as an object key as this causes the expression to be evaluated before being used as a key:
const myKey = 'hello';
const myObject = {
[myKey]: 'world';
};
console.log(myObject); // { hello: 'world' }

TypeError: this.state.persons.map is not a function Thanks

I'm new to react and only understand the basics. I got this project from someone to look at, but I'm scratching my head since morning with this problem:
Uncaught TypeError: this.state.persons.map is not a function.
Please, if you can try to try to go over it in easy but in under the hood way. Thank You!
import React, { useState, Component } from 'react';
import './App.css';
import Person from './Person/Person';
import person from './Person/Person';
import { render } from '#testing-library/react';
class App extends Component {
state =
{
persons:[
{id: '123', name:'Max', age: 28 },
{id: '124',name:'Mari', age: 26 },
{id: '125',name: 'laly', age: 20 }
],
showPersons: false
}
nameChangeHandler=( event,id ) =>
{ const personIndex = this.state.persons.findIndex(p=>{
return p.id === id;
});
const person = {...this.state.persons[personIndex]
};
person.name=event.target.value;
const persons=[ ...this.state.persons];
persons[personIndex]=person;
this.setState(
{
persons:person
}
)
}
togglePersonHandler = ()=>
{
const doesShow = this.state.showPersons;
this.setState ({showPersons: !doesShow});
}
deletePersonHandler= (personIndex)=> {
//const persons = this.state.persons;
const persons = [...this.state.persons]
persons.splice(personIndex,1);
this.setState({persons:persons});
}
render()
{
const style ={
backgroundColor:'yellow',
font:'inherit',
border:'1px solid blue',
padding:'8px',
cursor:'pointer'
};
let persons=null;
if (this.state.showPersons){
persons= (
<div>
{this.state.persons.map((person,index)=> {return <Person click={() => this.deletePersonHandler(index)}
name = {person.name}
age = {person.age}
key={person.id}
change ={(event) => this.nameChangeHandler(event,person.id)}
/>
})};
</div>)
};
return (
<div className="App">
<h1>Hi This is react App</h1>
<button style={style} onClick={this.togglePersonHandler}> Toggle Persons</button>
{persons}
</div>
);
}
}
export default App;
Error lies here :
nameChangeHandler=( event,id ) =>
{ const personIndex = this.state.persons.findIndex(p=>{
return p.id === id;
});
const person = {...this.state.persons[personIndex]
};
person.name=event.target.value;
const persons=[ ...this.state.persons];
persons[personIndex]=person;
this.setState(
{
persons:person ------------ > You are assigning a single object to a list in your state, so your map is giving an error, it must be {persons: persons}
}
)
}
Whenever you see ___ is not a function, try looking at whatever the function is referring to.
In this case you're using map, which is an Array function. So you need to verify if the array (the thing at the left of the dot) is actually an array.
The function call:
this.state.persons.map
The array you need to pay attention to is persons. So try to look for the place in the code where persons is not getting recognized an array.
You have a typo here, you are assigning and object to state instead of an array. in the funcion nameChangeHandler.
this.setState(
{
persons:person
}
)
With a simple console inside the render or just watching it inside the Components Menu in the browser you can notice it bro.
It probably happens here, where you replace the array of persons with a single person:
this.setState(
{
persons: person
}
)
You probably want to do this instead:
this.setState({persons});

Categories

Resources