Javascript Object destructuring syntax confusion [duplicate] - javascript

I recently came across this piece of code on a website
const List = ({ items }) => (
<ul className="list">
{items.map(item => <ListItem item={item} />)}
</ul>
);
Why have they wrapped the items in curly braces and is it a prop

This is called a "destructuring". Actually, you're passing an object as an argument to the function, but the destructuring uses only the named properties of the object.
const destructuring = ({ used }) => console.log(used);
const properties = {
unused: 1,
used: 2,
};
destructuring(properties); // 2
You can even use it for creating variables.
const properties = {
name: 'John Doe',
age: 21,
};
const { name, age } = properties;
console.log(name, age); // John Doe 21

I'm a newbie to React, but I think yes, items is a prop, and passing {items} as an argument destructures the props object, and thus the function uses only the prop items, in order to simplify the code. This way you can use items in the functional component, instead of props.items. For example, I tested a similar situation in the following code. Using destructuring looks like this:
const ListItem = ({content}) => (
<li>{content}</li>
);
...
<ListItem key={index} content={content} />
Whereas if you used props it would look like this:
const ListItem = (props) => (
<li>{props.content}</li>
);
...
<ListItem key={index} content={content} />
So for your example, using props would look like this:
const List = (props) => (
<ul className="list">
{props.items.map(item => <ListItem item={item} />)}
</ul>
);
Meanwhile, destructuring allows you to simplify to items, rather than props.items which is what was being done in your original code:
const List = ({ items }) => (
<ul className="list">
{items.map(item => <ListItem item={item} />)}
</ul>
);
Cheers!
--Omar

Related

How to map an array inside an array in UseEffect

Below image will be the response I get from useEffect
My useEffect will look like this:
useEffect(() => {
const getStatesData = async () => {
await fetch ('http://localhost:3001/covid19')
.then((response) => response.json())
.then((data) => {
const dataStates = data.states
const states = dataStates.map((state2) => (
{
name: state2.name,
total: state2.total
}
));
setStates(states)
})
}
getStatesData();
}, []);
I'm trying to get name and total inside districts, how can I do that?
I've tried using adding districts: state2.districts in useEffect, and adding {state.districts.map(({name, total}) => ({name} ))} directly in the <div> but it's returning an error: Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
I'm currently using material-ui to help me get thigs easier, here is my full code excluding the useEffect:
<div className="App">
{states.map(state => (
<Accordion expanded={expanded === state.name}
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={classes.heading}>{state.name} ({state.total}) </Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
!! DISTRICTS NAME HERE (DISTRICTS TOTAL HERE) !!
</Typography>
</AccordionDetails>
</Accordion>
))}
</div>
Instead of mapping districts like this:
{state.districts.map(({name, total}) => ({name} ))}
do it like this:
{state.districts.map(({name, total}) => `${name} (${total})`)}
or even better to a component like this:
{state.districts.map(({name, total}) => <div>{name} ({total})<div>)}
within the Typography component.
The reason being, the mapping state.districts.map(({name, total}) => ({name} )) returns an object for each district, and React doesn't recognizes objects as renderable components.

How to change property of one object in an array of objects in React state?

I have an array of JavaScript objects that I holding in React State, and on a click, I change the property of one of the objects in the array.
I got the following to work without mutating state, but my current setState() syntax also adds the same object to the end of the array again.
How can I simply change the state of one of the objects in my array of objects in state, without adding another object and without mutating state?
import React, { useState } from 'react';
interface IFlashcard {
noun: string;
article: string;
show: boolean;
}
const initialFlashcards = [
{
noun: 'Dependency',
article: 'die Dependency, die Dependencys',
show: false
},
{
noun: 'Kenntnis',
article: 'die Kenntnis, die Kenntnisse',
show: false
},
{
noun: 'Repository',
article: 'das Repository, die Repositorys',
show: false
},
{
noun: 'Kenntnis',
article: 'die Kenntnis, die Kenntnisse',
show: false
}
];
function LanguageFlashcards() {
const [flashcards, setFlashcards] = useState(initialFlashcards);
const toggleFlashcard = (flashcard: IFlashcard) => {
flashcard.show = !flashcard.show;
setFlashcards([...flashcards, flashcard]);
}
return (
<>
<h2>Language Flashcards</h2>
<ul>
{flashcards.map((flashcard: IFlashcard) => {
return (
<>
<li><span onClick={() => toggleFlashcard(flashcard)}>{flashcard.noun}</span>
{flashcard.show && (
<>
: {flashcard.article}
</>
)}
</li>
</>
)
})}
</ul>
</>
);
}
export default LanguageFlashcards;
Your example is in fact mutating state here:
flashcard.show = !flashcard.show;
At this point, flashcard refers directly to an object in state, so altering one of its properties is a mutation.
You need a way to identify the objects in state so that you can extract one, clone it individually, and then insert it back into a cloned state array in its original position. Without changing any of your data, you could do this by passing the array position of the flashcard when you call toggleFlashcard.
{flashcards.map((flashcard: IFlashcard, i: number) => {
return (
<>
<li><span onClick={() => toggleFlashcard(i)}>{flashcard.noun}</span>
{flashcard.show && (
<>
: {flashcard.article}
</>
)}
</li>
</>
)
})}
Now the toggleFlashcard event handler should look something like this:
const toggleFlashcard = (i: number) => {
const clonedCard = {...flashcards[i]};
clonedCard.show = !clonedCard.show;
const clonedState = [...flashcards];
clonedState[i] = clonedCard;
setFlashcards(clonedState);
}
If you don't want to mutate anything, please try this solution.
const toggleFlashcard = (flashcard: IFlashcard) => {
const flashcardIndex = flashcards.findIndex(f => f === flashcard);
const newFlashcards = [...flashcards];
newFlashcards[flashcardIndex]= { ...flashcard, show: !flashcard.show };
setFlashcards(newFlashcards);
};
And this is not related to the main topic but the key attribute is missing here.
{flashcards.map((flashcard: IFlashcard, index: number) => {
...
<li key={index}><span onClick={() => toggleFlashcard(flashcard)}>{flashcard.noun}</span>
If you don't specify the key attribute, you will see React warnings.
To do what you want you could do something like this
const toggleFlashcard = (flashcardIndex: number) => {
const flashcardsDeepCopy: IFlashcard[] = JSON.parse(JSON.stringify(flashcards));
flashcardsDeepCopy[flashcardIndex].show = !flashcard.show;
setFlashcards(flashcardsDeepCopy);
}
In jsx you need to pass the index
<ul>
{flashcards.map((flashcard: IFlashcard, index) => {
return (
<>
<li><span onClick={() => toggleFlashcard(index)}>{flashcard.noun}</span>
{flashcard.show && (
<>
: {flashcard.article}
</>
)}
</li>
</>
)
})}
</ul>
Your problem is with your toggleFlashcard function you think you aren't mutating the original state but you are given that javascript passes your object by reference. If you were to just do
const toggleFlashcard = (flashcard: IFlashcard) => {
flashcard.show = !flashcard.show;
setFlashcards([...flashcards]);
}
It would work however that isn't really react practice. What you would need to do is return a brand new array without modifying the original object. youll need some sort of identifier to filter out which flash card is which. maybe appending the index of the item to the renderer or pass in something from the backend.

Passing props inorder to use the string value

I have the following functional component and de-structuring the parameter props:
const MyTimes = ({ myWindowGroup, name, fieldArrayName }) => (
<FieldArray
name={name}
render={(arrayHelpers) => (
<React.Fragment>
{myWindowGroup.fieldArrayName.map((myTime, index) => (
and I am calling the component with the following props:
<MyTimes
myWindowGroup={myWindowGroup}
fieldArrayName={"myTimes"}
name={`myWindowGroups.${index}.myTimes`}
/>
My question is as I am new to React and that is, how can I pass/use the fieldArrayName={"myTimes"} string value of myTimes into the MyTime component above so that I can replace the value of fieldArrayName.map to be myTimes.map ?
I've tried it the way it is and not working.
Use dynamic keys. See Bracket Notation.
myWindowGroup[fieldArrayName].map(....
const myWindowGroup = {
myTimes: ['This is the value array you want'],
foo: [],
};
console.log(myWindowGroup['myTimes']);
console.log(myWindowGroup['foo']);
You can just do
{myWindowGroup[fieldArrayName].map((myTime, index) => (
As I understand your question correctly, you can achieve desired output by following
const MyTimes = ({ myWindowGroup, name, fieldArrayName }) => (
<FieldArray
name={name}
render={(arrayHelpers) => (
<React.Fragment>
{myWindowGroup[fieldArrayName].map((myTime, index) => (
// your logic here
))}

How to use .map() to iterate an object and create number of <Item />

I have created an object with label and value keys. Now What I want to do is create for each item in the object by using .map() function.
// my object
const ageData = [...Array(71)].map((x, i) => ({ label: i + 10, value: i + 10 }));
//My object looks like 0: {label: 10, value: 10} 1: {label: 11, value: 11}
//What I want to create
<Item label=10 value=10 />
<Item label=11 value=11 />
//My current non-working solution
ageData.map(obj => <Item label=obj.label value=obj.value />)
As commenters mentioned above the syntax needs a bit work. It could be something like this using a functional component:
const Items = props => {
return (
<div>
{props.someArray.map(obj => (<Item label={obj.label} value={obj.value} />))}
</div>
)
}
Then return the following from a functional component, or your render method, where you want to display the items:
<Items someArray={ageData} />
Where ageDate is your array.
Note that you were missing the {} around your expressions inside the JSX code, as well as parentheses around the JSX expression your were returning from your map.
Bonus:
You can write your functional component shorter like this, if you just need it to return an expression like above:
const Items = props => (
<div>
{props.someArray.map(obj => (<Item label={obj.label} value={obj.value} />))}
</div>
)

React Transferring Props except one

React suggests to Transfer Props. Neat!
How can I transfer all of the props but one?
render: function(){
return (<Cpnt {...this.propsButOne}><Subcpnt one={this.props.one} /></Cpnt>);
}
You can use the following technique to consume some of the props and pass on the rest:
render() {
var {one, ...other} = this.props;
return (
<Cpnt {...other}>
<Subcpnt one={one} />
</Cpnt>
);
}
Source
What you need to do is to create a copy of the props object and delete the key(s) you don't want.
The easiest would be to use omit from lodash but you could also write a bit of code for this (create a new object that has all the keys of props except for one).
With omit (a few options at the top, depending on what package you import/ES flavor you use):
const omit = require('lodash.omit');
//const omit = require('lodash/omit');
//import { omit } from 'lodash';
...
render() {
const newProps = omit(this.props, 'one');
return <Cpnt {...newProps}><Subcpnt one={this.props.one} /></Cpnt>;
}
If you have a lot of props you don't want in ...rest e.g. defaultProps, it can be annoying to write all of them twice. Instead you can create it yourself with a simple loop over the current props like that:
let rest = {};
Object.keys(this.props).forEach((key, index) => {
if(!(key in MyComponent.defaultProps))
rest[key] = this.props[key];
});
Thank you #villeaka!
Here's an example of how I used your solution for other people to better understand it's usage.
I basically used it to create a stateless wrapping-component that I then needed to pass its props to the inner component (Card).
I needed the wrapper because of the rendering logic inside another top level component that used this wrapper like this:
<TopLevelComponent>
{/* if condition render this: */}
<CardWrapper {...props}> {/* note: props here is TLC's props */}
<Card {..propsExceptChildren}>
{props.children}
</Card>
</CardWrapper>
{/* if other condition render this: */}
{/* ... */}
{/* and repeat */}
</TopLevelComponent>
where several conditions determine what comes after the H4 in the wrapper (see actual rendered node tree below).
So basically, I didn't want to duplicate code by writing the entire part that comes before {children} in the example below, for each arm of the conditional in the top level component that renders multiple variants of the wrapper from above example:
const CardWrapper: React.FC<IRecentRequestsCardProps> = (props) => {
const { children, ...otherProps } = props;
return (
<Card {...otherProps} interactive={false} elevation={Elevation.ONE}>
<H4>
Unanswered requests
</H4>
{children}
</Card>
);
};
And concrete usage in a React render function:
if (error)
return (
<CardWrapper {...props}>
<SimpleAlert title="Eroare" intent={Intent.DANGER}>
{error}
</SimpleAlert>
</CardWrapper>
);
if (loading)
return (
<CardWrapper {...props}>
<NonIdealState
icon="download"
title="Vă rog așteptați!"
description="Se încarcă cererile pentru articole..."
/>
</CardWrapper>
);
if (!data)
return (
<CardWrapper {...props}>
<NonIdealState
icon="warning-sign"
title="Felicitări!"
description="Nu există cereri fără răspuns."
/>
</CardWrapper>
);
// etc.
So the above just adds the H4 header before the children of the wrapper and also passes down the props that it has been passed down to, to the inner Card component.
The simplest way I found so far:
const obj = {
a: '1',
b: '2',
c: '3'
}
const _obj = {
...obj,
b: undefined
}
This will result in _obj having all the props except b
Try this:
function removeProps(obj, propsToRemove) {
let newObj = {};
Object.keys(obj).forEach(key => {
if (propsToRemove.indexOf(key) === -1)
newObj[key] = obj[key]
})
return newObj;
}
const obj = {nome: 'joao', tel: '123', cidade: 'goiania'}
const restObject = removeProps(obj, ['cidade', 'tel'])
console.log('restObject',restObject)
restObject
{
nome:"joao"
}
I had this issue when extending Material UI. A component would emit a warning if an unknown property was passed at all. I solved it slightly differently by specifically deleting the properties I didn't want to pass:
const passableProps = { ...props } as Partial<typeof props>;
delete passableProps.customValidity;
return (
<TextField { ...passableProps } // ...
);

Categories