How to destructure an Object from Array with useState in React - javascript

I need to destructure the filter Object from Array with useState in React, but I can't get it.
const [filter, setFilter] = useState([]);
const { column, comparison, value } = filter;
console.log(column); // undefined
I tried braces, brackets, and still getting undefined.
Does anyone knows how to get that values?
The filter object:
filter: [
{
column: 'population',
comparison: 'greater than',
value: '100000',
}
]
Console log:

Your filter is an array, not an object. Try:
const { column, comparison, value } = filter[0];
Or you can destrcuture your filter array and then do the object destructiong:
const [firstItem] = filter;
const { column, comparison, value } = firstItem;

Based on your latest screenshot of the entire component, the problem is that you are destructuring the filterByNumericValues array before it is hydrated with the data. Then your console.log has the correct data because it is in the useEffect hook AFTER the state has been updated with the data.
Since it looks like you are only using the column, comparison, and value variables in the checkFilterByNumeric function, I would destructure the state in that scope.
function checkFilterByNumeric(planet) {
const { column, comparison, value } = filterByNumericValues[0];
if (comparison === "maior que") {
...
}

The easiest way is to destructure the object as the first element of the array.
But how you're getting filter into the component, whether it's via an API request in a useEffect, component props, a third-party state management library like Redux, or even React's own context, will determine how you can log/render the information.
The data at the point where you're trying to log/render it simply might not be available at that time.
Here's an small example that passes in filter as a component prop, and destructures the object from the array.
const { useState } = React;
function Example({ data }) {
const [ filter, setFilter ] = useState(data);
// The object that is the first element of the array
const [{ column, comparison, value }] = filter;
return <div>{column}</div>;
}
const filter = [{
column: 'population',
comparison: 'greater than',
value: '100000'
}];
ReactDOM.render(
<Example data={filter} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

You can do this, for example:
const defaultValue = { column: 'empty', comparison: 'empty', value: 0 }
const { column, comparison, value } = filter[0] || defaultValue;

Related

How can i update a useState from an array?

Why does not work the useState for array? i am passing a object for props, now i am printing inside of useEffect his value, at the console.log it have the correct value that value is an array.
i catch that array then i tryed to update the usestate but does not worked
const ComponentHeader = ({ csp }) => {
const [materials, setMaterials] = useState(null)
useEffect(() => {
const { mat } =csp
console.log('Console...', mat)
setMaterials(mat)
}, [csp])
in the template: i am using antd to show in a list
return (
{materials && (
<Form.List initialValue={materials}>
i put a console inside of useEffects
useEffect(() => {
const { mat } =cp
console.log('HEEERRREEE', mat)
}, [])
this is the output:
jsx:26 HEEERRREEE { mat: Array(1)}
jsx:26 HEEERRREEE { mat: Array(2)}
should print 2 elements, but only print 1 element
The useEffect is a hook that handles side effects, and having this second array parameter allows side effects to be run whenever any value changes. so you need to check if csp is changing. Hope this answer your question thanks!

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 };
}

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' }

Adding a key-value pair to an object inside loop using React useState hook

I'm trying to add a key-value pair to an object using useState inside map function. However, the spread operation in setContainer({...container, [data.pk]: data.name}); seems to be ineffective.
The code snippet below exactly illustrates the problem that I'm trying to solve. In fact, in my original code container is declared at another ContextProvider component and referenced using useContext, but I replaced it with useState for simpliticy.
I guess it has to do with the asynchronous nature of setState hook, but I don't fully understand what's going on under the hood.
Please let me know what is causing this issue and how to yield the outcome I expected.
Thank you.
const {useState, useEffect} = React;
const myData = [{pk: 1, name:'apple'},
{pk: 2, name:'banana'},
{pk: 3, name:'citrus'},
]
const Example = () => {
const [container, setContainer] = useState({});
useEffect(()=>{
myData.map(data=>{
console.log(`During this iteration, a key-value pair (key ${data.pk} and value '${data.name}') should be added to container`);
setContainer({...container, [data.pk]: data.name});
})
}, [])
return (
<div>
<div>result I expected: {"{'1': 'apple', '2': 'banana','3': 'citrus'}"}</div>
<div>result: {JSON.stringify(container)}</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Update
The main reason I wanted to use useState in loops was to use the state as a container of refs of components. However, I found it inefficient to do so, because it caused too many rerenders and was hard to determine when the loop finished. After a lot of searching, I ended up declaring useRef({}) in the Context Provider and use it as a refs container (which seems to fit the actual purpose it was created I guess). The refs container can be used as following:
const refsContainer = useRef({});
import React from 'react';
const Notes = () => {
const setRef = (noteId) => (element) => {
refsContainer.current[noteId] = element
};
return (
<Masonry>
{pubData?.map(publication => {
return <PublicationCard innerRef={setRef(publication.noteId)}/>
})}
</Masonry>
);
};
export default Notes;
const pubCardRef = refsContainer.current[rank.note.noteId];
window.scrollTo({top: pubCardRef?.offsetTop - 80, behavior: 'smooth'})
Here's what's going on in your component step by step.
container and setContainer are initialized using the useState hook
the effect in which you map over myData is registered, also it will only run once in the entire life of the component because of the empty array [] you passed as the second argument
the callback you registered in the effect is run. this is what it could look like if you console.log'd the container as well as the key-value pair
(key 1 value 'apple'); container {}
(key 2 value 'banana'); container {}
(key 3 value 'citrus'); container {}
Wait what, shouldn't container be updating on each iteration?
Well yes, but not in the way you expect. The container referenced in useEffect retains its original {} value in each iteration of myData.map. What's effectively going on then is
setContainer({ ...{}, 1: 'apple ' }) // RERENDER
setContainer({ ...{}, 2: 'banana' }) // RERENDER
setContainer({ ...{}, 3: 'citrus' }) // RERENDER
citrus is the final display since it was the last rerender.
Here's a way you can achieve your desired result
const {useState, useEffect} = React;
const myData = [{pk: 1, name:'apple'},
{pk: 2, name:'banana'},
{pk: 3, name:'citrus'},
]
const Example = () => {
const [container, setContainer] = useState({});
useEffect(()=>{
setContainer(myData.reduce((obj, data) => ({ ...obj, [data.pk]: data.name }), {}))
}, [])
return (
<div>
<div>result I expected: {"{'1': 'apple', '2': 'banana','3': 'citrus'}"}</div>
<div>result: {JSON.stringify(container)}</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You're right, it's because of asynchronous nature of setState hook, it basically runs in batch.
From react docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
You can create an object first and set it once the loop is completed. Also you shouldn't use map here. Since you're not creating a new array.
useEffect(() => {
const dataObj = {};
myData.forEach(data => {
dataObj[data.pk] = data.name;
});
setContainer(dataObj);
}, []);
const [data,setData]= useState({});
const subData=[{type:'data1'},{type:'data2'}]
// expect result for data = {'data1':false,'data1':false}
useEffect(()=>{
setData(subData?.reduce((obj,data) => ({...obj,[data.type]:false }), {}))
},[])

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