I'm currently rendering two different components based on the value of shouldRenderPlanA - however, despite different components being rendered (depending on the value) - I pass both the same props. How can I condense this to reduce repeated code?
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA ? (
<StyledLabelOptionOne
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
) : (
<StyledLabelOptionTwo
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
)
))}
</StyledRow>
</>
);
To pass same Props to multiple React component or to pass multiple Props to a React component, you can use Object unpacking/destruction within components.
function Component() {
const propPack = {
variousProps1: variousProps1,
variousProps2: variousProps2,
variousProps3: variousProps3,
};
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
))}
</StyledRow>
</>
);
}
This is commonly used to pass all the parent props down to children
function Component(props) {
return (
condition
? <StyledLabelOptionOne {...props} />
: <StyledLabelOptionTwo {...props} />
)
}
You can also conditionally pick the component for rendering (but this IMHO is less readable)
function Component() {
const PickedComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<PickedComponent
variousProps1={variousProps1}
variousProps2={variousProps2}
variousProps3={variousProps3}
/>
))}
</StyledRow>
</>
);
}
For conditions/props derived from within .map() simply move the code within the map callback
function Component() {
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => {
const propPack = {
variousProps1: variousProps1,
variousProps2: opt.value,
};
const PickedComponent = opt.condition ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
)
})}
</StyledRow>
</>
);
}
Note how arrow function within map has becomed an arrow function with a complete block. From (opt) => (first_instruction) to (opt) => { first_instruction; return (second_instruction); }. This allows us to add code before rendering at each map() cycle.
You could assign both options to a variable which contains a union of both component types.
Combining and then spreading the props from an object may also be beneficial, depending on where those props come from. If they are taken from opt inside the map then this second step is probably not required:
const LabelComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<LabelComponent
prop1={opt.prop1}
prop2={opt.prop2}
/>
))}
</StyledRow>
</>
);
You could use the React Context API. This would enable you to share the props across multiple children without passing it to each one of them explicitly.
Related
I am getting the following error:
react Each child in a list should have a unique "key" prop.
Parent component:
{data.products
.slice(4, 9)
.map(
({
onSale,
slug,
imageMain,
productHeadline,
outOfStock,
onSalePrice,
}) => (
<>
{onSale === true ? (
<HomeFeatured
slug={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
) : (
<></>
)}
</>
)
)}
Child component:
function HomeFeatured({
slug,
imageMain,
productHeadline,
onSalePrice,
outOfStock,
}) {
return (
<div key={slug} className="xl:w-72 lg:w-48 xs:w-60 transition ease-in-out delay-110 hover:-translate-y-1 hover:scale-105 duration-200 ">
<div className="rounded overflow-hidden shadow-lg">
</div>
</div>
);
}
I have added a key to the parent div but I am still getting the error in the console. I even added a key to the parent component and still would not work. I have done very similar array mapping in other parts of my application but this part has been componentised. Is this the reason why it may not be rendering possibly??
I feel like I have added the key and React is just being picky but I still would like to solve this issue.
In react you can simplify the one-line ternary statement likewise:
{onSale && (
<HomeFeatured
slug={slug}
key={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
)}
This eliminates the empty <></> problem.
The map function gives unique id along with the element every time it iterates through an Array, you can use it as a key.
.map((data, idx) => (
<HomeFeatured key={idx}/>
))
You Should add the key in the parent component. React needs the key on the element you returning in the map function. It can be a component or JSX.
{data.products
.slice(4, 9)
.map(
({
onSale,
slug,
imageMain,
productHeadline,
outOfStock,
onSalePrice,
}) => (
<>
{onSale === true ? (
<HomeFeatured
slug={slug}
key={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
) : (
<></>
)}
</>
)
)}
You have to assign key value not inside the function. But where it is referenced. Like :
{data.products
.slice(4, 9)
.map(
({
onSale,
slug,
imageMain,
productHeadline,
outOfStock,
onSalePrice,
}) => (
<>
{onSale === true ? (
<HomeFeatured
key={slug}
slug={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
) : (
<div key={slug}></div>
)}
</>
)
)}
The key property should be put on the outer most element you are mapping, it doesnt matter that in this case it's a component, it should get a key prop.
am trying to show Noteitem component which is returned inside a map function.
{notes.map((note) => {
return (
<Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />
);
})}
notes should be an array for map function to work. You can check it in following way if notes is not null and is an array using notes.length and apply map function
{notes && notes.length && notes.map((note) => {
return (
<Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />
);
})}
You can put if/else statement inside JSX to check the variable whether is exist or not
return (
<>
{
notes.length
? 'fallback'
: notes.map(note => <Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />)
}
</>
)
IIFE
{(() => {
if ("check note") {
// fallback
}
return notes.map((note: NoteProps) => (
<Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />
));
})()}
I have a custom React Component that I made to render loading and error states conditionally, this is the code:
const StateHandler = ({ requestData, children }) => {
return requestData.loading
? <LoadingIndicator />
: requestData.error
? <ErrorMessage />
: requestData.done && children;
};
It receives an object like this:
const requestData = {
loading: false, //or true
error: false, //or true
done: false //or true
}
It works, but it has a problem, suppose I use it from another component, like this:
return (
<>
<> ... some other stuff </>
<StateHandler requestData={requestData}>
<>
<p>{user.name}</p>
</>
</StateHandler>
</>
)
The problem is, the variable user will be undefined until its populated with data from an API, and {user.name} will throw "Cannot read property 'name' of undefined".
Is there any way to prevent the children being evaluated? Thanks in advance.
No there is not any way i think to prevent from children being evaluated but below can help you with the error:
return (
<>
<> ... some other stuff </>
<StateHandler requestData={requestData}>
<>
<p>{user && user.name ? user.name : ""}</p>
</>
</StateHandler>
</>
)
This way if user is undefined then it will skip evaluating and rendering user.name and instead would render nothing, and hence no error.
If you can use optional chaining, you could keep the simplicity of your code with a slight change.
return (
<>
<> ... some other stuff </>
<StateHandler requestData={requestData}>
<>
<p>{user?.name}</p>
</>
</StateHandler>
</>
)
If you can't use optional chaining, Sakshi's answer would be best.
I would like to know how can i destruct object within .map function using javascript, i have react js component and within return method i have the code below:
return (
<>
{setItems.map(setItem => (
const { childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode} = setItem
....
</>
and i have the next error: Parsing error: Unexpected token ... = setItem, i thought what it is
EsLinterror and used // eslint-disable-next-line to disable it, but it didn't work.
UPD full return code:
return (
<div className={generalServiceItemClassName} key={guuid()}>
{setItems.map(setItem => (
const { childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode} = setItem
<div
key={guuid()}
className={cx(columnSizeClass, "service-items__item")}
data-test="service-items"
>
{setItem.learnMore ? (
<LearnMore
className="service-items__item-learn-more-container"
learnMoreLink={setItem.learnMore}
text={}
textClassName="service-items__item-texts-learn-more"
learnMoreText={learnNode ? learnNode.setItem : null}
>
{renderItem(setItem)}
</LearnMore>
) : (
renderItem(setItem)
)}
</div>
))}
</div>
)
You can't have a const declaration within an expression, and when you use the concise form of an arrow function (=> without a { after it), the body is an expression.
You can destructure in the parameter list, though. For instance:
{setItems.map(({childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode}) => (
// ...use `learnNode` here...
In context:
return (
<div className={generalServiceItemClassName} key={guuid()}>
{setItems.map(({childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode}) => (
<div
key={guuid()}
className={cx(columnSizeClass, "service-items__item")}
data-test="service-items"
>
{setItem.learnMore ? (
<LearnMore
className="service-items__item-learn-more-container"
learnMoreLink={setItem.learnMore}
text={}
textClassName="service-items__item-texts-learn-more"
learnMoreText={learnNode ? learnNode.setItem : null}
>
{renderItem(setItem)}
</LearnMore>
) : (
renderItem(setItem)
)
}
</div>
))}
</div>
);
Try something like this. (destructure and renaming)
const setItems = [{ abc: 5 }];
return (
<>
{setItems.map((setItem) => {
const { abc: xyz } = setItem;
return <div>{xyz}</div>;
})}
</>
);
// Alternate way, simplified.
return (
<>
{setItems.map(({ abc: xyz }) => (
<div>{xyz}</div>
))}
</>
);
although I defined a key for SearchDropDownItem it shows a warning
component DropDown
filteredItems.length > 0 ? (
filteredItems.map(item => {
return (
<SearchDropDownItem
item={item}
buttonTitle={{ buttonJoin: content.buttonJoin }}
onItemSelect={onItemSelect}
/>
);
})
) : (
<SearchDropDownItem emptyList={content.noCommunityFound} />
)
searchDropDownItem component :
const SearchDropDownItem = ({
item = { },
onItemSelect,
buttonTitle = "",
emptyList
}) => {
return (
<DropdownItem key={item.id || 1}>
{!emptyList ? (
<Box>
<Span>{item.name} </Span>
<JoinButton
item={item}
index={item.id}
onSuccess={onItemSelect}
content={buttonTitle}
/>
</Box>
) : (
<Box>
<Span>{item.emptyList}</Span>
</Box>
)}
</DropdownItem>
);
};
Warning: Each child in a list should have a unique "key" prop. Check the render method of SearchBox.
in SearchDropDownItem (at SearchBox/index.jsx:52)
You should place the key where you use the SearchDropdownItem, so in the loop.
filteredItems.length > 0 ? (
filteredItems.map(item => {
return (
<SearchDropDownItem
key={item.id} // <-- This is where it has to be
item={item}
buttonTitle={{ buttonJoin: content.buttonJoin }}
onItemSelect={onItemSelect}
/>
);
})
) : (
<SearchDropDownItem emptyList={content.noCommunityFound} />
)
Docs on keys in React: https://reactjs.org/docs/lists-and-keys.html#keys
I got the same warning message:
Warning: Each child in a list should have a unique "key" prop.
However, my problem and solution were a bit different than the accepted answer. I thought adding my solution to this question might help someone.
I am using a 3rd party component, which has a unique key. However, when I used a loop to dynamically generate several instances of the component, I got the warning message above.
The warning disappeared after I added a key prop to the component. This key is NOT part of the props for the component.
let filterJSX = [];
let i = 0;
for (let [key1, value1] of Object.entries(state.filterListNew)) {
i++;
filterJSX.push(
<MultiSelectModalField
key={i.toString()} // I added this one
items={optionList}
uniqueKey="value"
displayKey="name"
// more properties here...
/>
);
}