Iterating objects in React JS - javascript

What is the Error in the below code? I tried to iterate over the array of objects using the map. but I am receiving an error. Please find attached the code for reference.
import "./styles.css";
export default function App() {
const sample = [{
samplePath : "/website",
par:{
abc:"123",
def: "678",
ghi:"456"}
}];
const createURL = (re)=> {
const q = Object.entries(re.par)
.map(([key, value]) => key +'='+value).join("&");
return "?"+ q;
}
console.log(createURL(sample));
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
I am receiving the following error

You're trying to call Object.entries on re.par. In this case, re is an array, so calling .par on it returns undefined. I'm not sure what your desired result is, but if you access the first element in the array and then .par, maybe that's what you're after?
const sample = [{
samplePath : "/website",
par:{
abc:"123",
def: "678",
ghi:"456"}
}];
const createURL = (re)=> {
const q = Object.entries(re.par)
.map(([key, value]) => key +'='+value).join("&");
return "?"+ q;
}
console.log(createURL(sample[0]));

In your example sample is an array, containing one object.
So, either, remove the [] such that sample is an object like you are referring to it in your code. Or, Object.entries(re[0].par)

Related

Getting error: "Objects are not valid as a React child" on array rendering

I'm trying to do a simple task. Rendering contents of an array:
const [comments, setComments] = useState([]); // an array to begin with
handleFetch = () => {
...
...
const obj = resp.data.comments;
const new_array = new Array();
new_array.push(obj);
setComments(new_array);
...
}
return (
{comments.forEach(comm => <p>{comm.post}</p>)}
);
comments (resp.data.comments) come in as an array of objects:
comments = [{'post': 'something', 'timestamp': 'some time'}]
Error output I'm getting:
Error: Objects are not valid as a React child (found: object with keys {post, timestamp}). If you meant to render a collection of children, use an array instead. (But I'm using an array. An array of objects)
Since resp.data.comments is an array, you can directly set to it to state comments using setComments(resp.data.comments) and use Array.prototype.map function in jsx to render.
const [comments, setComments] = useState([]); // an array to begin with
handleFetch = () => {
...
...
setComments(resp.data.comments);
...
}
return (
<>
{comments.map(comm => <p>{comm.post}</p>)}
</>
);
You need to use .map() to return the value. So you can fix like this:
return (
<>
{comments.map((comm, index) => <p key={index}>{comm.post}</p>)}
</>
);

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 to access child key-values on objects and clonate that object

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>

use object in useEffect 2nd param without having to stringify it to JSON

In JS two objects are not equals.
const a = {}, b = {};
console.log(a === b);
So I can't use an object in useEffect (React hooks) as a second parameter since it will always be considered as false (so it will re-render):
function MyComponent() {
// ...
useEffect(() => {
// do something
}, [myObject]) // <- this is the object that can change.
}
Doing this (code above), results in running effect everytime the component re-render, because object is considered not equal each time.
I can "hack" this by passing the object as a JSON stringified value, but it's a bit dirty IMO:
function MyComponent() {
// ...
useEffect(() => {
// do something
}, [JSON.stringify(myObject)]) // <- yuck
Is there a better way to do this and avoid unwanted calls of the effect?
Side note: the object has nested properties. The effects has to run on every change inside this object.
You could create a custom hook that keeps track of the previous dependency array in a ref and compares the objects with e.g. Lodash isEqual and only runs the provided function if they are not equal.
Example
const { useState, useEffect, useRef } = React;
const { isEqual } = _;
function useDeepEffect(fn, deps) {
const isFirst = useRef(true);
const prevDeps = useRef(deps);
useEffect(() => {
const isFirstEffect = isFirst.current;
const isSame = prevDeps.current.every((obj, index) =>
isEqual(obj, deps[index])
);
isFirst.current = false;
prevDeps.current = deps;
if (isFirstEffect || !isSame) {
return fn();
}
}, deps);
}
function App() {
const [state, setState] = useState({ foo: "foo" });
useEffect(() => {
setTimeout(() => setState({ foo: "foo" }), 1000);
setTimeout(() => setState({ foo: "bar" }), 2000);
}, []);
useDeepEffect(() => {
console.log("State changed!");
}, [state]);
return <div>{JSON.stringify(state)}</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
The above answer by #Tholle is absolutely correct. I wrote a post regarding the same on dev.to
In React, side effects can be handled in functional components using useEffect hook. In this post, I'm going to talk about the dependency array which holds our props/state and specifically what happens in case there's an object in the dependency array.
The useEffect hook runs even if one element in the dependency array changes. React does this for optimisation purposes. On the other hand, if you pass an empty array then it never re-runs.
However, things become complicated if an object is present in this array. Then even if the object is modified, the hook won't re-run because it doesn't do deep object comparison between these dependency changes for that object. There are couple of ways to solve this problem.
Use lodash's isEqual method and usePrevious hook. This hook internally uses a ref object that holds a mutable current property that can hold values.
It’s possible that in the future React will provide a usePrevious Hook out of the box since it is a relatively common use case.
const prevDeeplyNestedObject = usePrevious(deeplyNestedObject)
useEffect(()=>{
if (
!_.isEqual(
prevDeeplyNestedObject,
deeplyNestedObject,
)
) {
// ...execute your code
}
},[deeplyNestedObject, prevDeeplyNestedObject])
Use useDeepCompareEffect hook as a drop-in replacement for useEffect hook for objects
import useDeepCompareEffect from 'use-deep-compare-effect'
...
useDeepCompareEffect(()=>{
// ...execute your code
}, [deeplyNestedObject])
Use useCustomCompareEffect hook which is similar to solution #2
I prepared a CodeSandbox example related to this post. Fork it and check it yourself.
Your best bet is to use useDeepCompareEffect from react-use. It's a drop-in replacement for useEffect.
const {useDeepCompareEffect} from "react-use";
const App = () => {
useDeepCompareEffect(() => {
// ...
}, [someObject]);
return (<>...</>);
};
export default App;
Plain (not nested) object in dependency array
I just want to challenge these two answers and to ask what happen if object in dependency array is not nested. If that is plain object without properties deeper then one level.
In my opinion in that case, useEffect functionality works without any additional checks.
I just want to write this, to learn and to explain better to myself if I'm wrong. Any suggestions, explanation is very welcome.
Here is maybe easier to check and play with example: https://codesandbox.io/s/usehooks-bt9j5?file=/src/App.js
const {useState, useEffect} = React;
function ChildApp({ person }) {
useEffect(() => {
console.log("useEffect ");
}, [person]);
console.log("Child");
return (
<div>
<hr />
<h2>Inside child</h2>
<div>{person.name}</div>
<div>{person.age}</div>
</div>
);
}
function App() {
const [person, setPerson] = useState({ name: "Bobi", age: 29 });
const [car, setCar] = useState("Volvo");
function handleChange(e) {
const variable = e.target.name;
setPerson({ ...person, [variable]: e.target.value });
}
function handleCarChange(e) {
setCar(e.target.value);
}
return (
<div className="App">
Name:
<input
name="name"
onChange={(e) => handleChange(e)}
value={person.name}
/>
<br />
Age:
<input name="age" onChange={(e) => handleChange(e)} value={person.age} />
<br />
Car: <input name="car" onChange={(e) => handleCarChange(e)} value={car} />
<ChildApp person={person} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-
dom.development.js"></script>
<div id="root"></div>
You can just expand the properties in the useEffect array:
var obj = {a: 1, b: 2};
useEffect(
() => {
//do something when any property inside "a" changes
},
Object.entries(obj).flat()
);
Object.entries(obj) returns an array of pairs ([["a", 1], ["b", 2]]) and .flat() flattens the array into:
["a", 1, "b", 2]
Note that the number of properties in the object must remain constant because the length of the array cannot change or else useEffect will throw an error.

Categories

Resources