Map different arrays into same component - javascript

Using react, I need to pass by props data to a component, the only problem is that this data comes from two different arrays.
How can I pass it by creating only one component?
If I do this, mapping both arrays, I get two components and it has to be only one:
const Field2 = (props) => {
return (
<div className={"field2"}>
{props.thumbnails.map(a =>
<Field2Content label={a.label}
/>
)}
{props.texturasEscolhidas.map(b =>
<Field2Content name={b.name}
/>
)}
</div>
)
};
export default Field2;
If I do:
{props.thumbnails.map(a =>
<Field2Content label={a.label}
name={'hello'}
/>
)}
I get this:
The 'hello' is what I need to get from the texturasEscolhidas array.
"Color of leg" and "Colour" are created when the component is renderer, the Hello should only appear when a button is clicked, and it's dynamic, changing by which of button is pressed.

To use just one component, assuming the both arrays have the same length, you can get the label and the name by iterating one array and accessing the element of the other array by index (the second parameter in the callback of the map):
const Field2 = (props) => {
return (
<div className={"field2"}>
{props.thumbnails.map((a, index) =>
<Field2Content
label={a.label}
name={(props.texturasEscolhidas[index] || {}).name}
/>
)}
</div>
)
};

Related

How to display the desired information on click ReactJS

My page is divided into right and left parts. On the left side there are 3 buttons, and on the right side there is a list with 3 texts. They are in different files. I want the corresponding text to come first in the list when the button is clicked. The functionality is similar to tabs, but I don’t know how to implement it, because the components are in different files and are not connected. How can I do that?
//Buttons.js
const btnArr = [
["Togle Text 1"],
["Togle Text 2"],
["Togle Text 3"],
];
const Buttons = () => {
return (
<div style={{ width: "50%" }}>
{btn.map((btn, index) => (
<Button
key={index}
text={btn}
/>
))}
);
};
//Text.js
const btnArr = [
["Text 1"],
["Text 2"],
["Text 3"],
];
const Texts = () => {
return (
<div style={{ width: "50%" }}>
{texts.map((txt, index) => (
<Text
key={index}
text={txt}
/>
))}
);
};
Parent Component
You'll want to use a useState in a parent component of both Texts and Buttons. That way you can keep track of which button has been clicked, and you'll pass Buttons a way to update which has been clicked. You'll also be able to pass Texts the value of which text is currently selected.
That parent component could look like this:
const [selectedText, setSelectedText] = useState(0);
return (
<div
>
<Buttons onSelect={setSelectedText} />
<Texts selectedText={selectedText} />
</div>
);
Buttons Component
Next we'll handle the Buttons Component. You can see in the above codeblock we are passing Buttons a prop called onSelect which we'll use to update the selectedText state.
Here's what that component could look like:
export const Buttons = ({ onSelect }) => {
return (
<div>
{btnArr.map((btn, index) => (
<Button key={index} text={btn} onClick={() => onSelect(index)} />
))}
</div>
);
};
Now, whenever a button is clicked, the selectedText state variable in the Parent will be updated to the index of the button clicked.
Texts Component
The Texts Component is a little bit trickier because we need to show the selected Text before the other Texts.
Since we are passing in selectedText as a prop, we can use that as we are creating the list. Our Texts component should look like this:
export const Texts = ({ selectedText }) => {
The most basic way to order the list is by placing our selectedText item first, followed by the mapped over text elements, but with the selectedText item filtered out. It may make more sense to look at the code:
{<Text text={texts[selectedText]} />}
{texts
.filter((txt, index) => index !== selectedText)
.map((txt, index) => (
<Text key={index} text={txt} />
))}
That way will work just fine, but if you don't want to have a <Text ... /> in two places, we can avoid that by using the following code instead. The more complicated way to do this is by using sort: we can sort through the text array to order them and then map over them like this:
{texts
.sort((txt, txt2) =>
txt === texts[selectedText]
? -1
: txt2 === texts[selectedText]
? 1
: 0
)
.map((txt, index) => (
<Text key={index} text={txt} />
))}
I've put together a full example of this on CodeSandbox here:
Extending the List
The advantage of doing it this way is that you can easily add more items to the list of buttons/text. If we simply add a ["Toggle Text 4"] to the button list and a ["Text 4"] to the text list, you can see that everything still just works.
The CodePen example demonstrates this.
Working with Different Files
In my explanation, we worked with three separate files for our code: a parent file, Texts.js, and Buttons.js.
Here's how you can use the Texts and Buttons component from inside the parent:
In the parent file at the top, import the other two files like this:
import { Texts } from "./Texts";
import { Buttons } from "./Buttons";
Then inside Texts.js, make sure to have the word export before the component is defined like this:
export const Texts = ({ selectedText }) => {
Do the same in Buttons.js:
export const Buttons = ({ onSelect }) => {
This allows us to use code from one file in a separate file. This guide gives a bit more explanation on how that works.
You can figure out that by Lifting State Up. You can try this but this isn't the best practice you can try make to order with id.
Buttons.js
const btnArr = [["Togle Text 1"], ["Togle Text 2"], ["Togle Text 3"]];
const Buttons = (props) => {
return (
<div style={{ width: "50%" }}>
{btnArr.map((btn, index) => (
<button key={index} onClick={() => props.onButtonClick(index)}>
{btn}
</button>
))}
</div>
);
};
export default Buttons;
Texts.js
const textArr = [["Text 1"], ["Text 2"], ["Text 3"]];
const Texts = (props) => {
return (
<div style={{ width: "50%" }}>
{<p>{textArr[props.order]}</p>}
{textArr.map((txt, index) => {
return index != props.order ? <p key={index}>{txt}</p> : null;
})}
</div>
);
};
export default Texts;
App.js
import { useState } from "react";
import Buttons from "./Buttons";
import Texts from "./Text";
function App() {
const [textIndex, setTextIndex] = useState(0);
function onButtonClick(buttonIndex) {
console.log(buttonIndex);
setTextIndex(buttonIndex);
}
return (
<>
<Buttons onButtonClick={onButtonClick} />
<Texts order={textIndex} />
</>
);
}
export default App;
Notice: I change the <Button> Component and the <Text> Component to facilitate the example
My advice would be to look into React-Redux. This is a state-management system that exists "outside" your component structure in a store. This allows non-related components to speak to each other.
Another option, though less clean would be to send the information from one component to the first parent that contains both components through callbacks, then pass the information through props to the other child component.
EDIT: Redux may be too complex, and too much effort depending on the complexity of the project. Passing through callbacks and props should be enough.

Passing an array to a React component and then accessing values within that array in the child component

working with React (but issue is Javascript I think) whereby I am trying to pass an array to a React component, and then within the child component (that consumes the array) - take values out of the array.
What I want to do is access the value: "_label" in my array. I've tried to do various versions of something like: key._label but to no success!
Parent:
<StyledHorizontalAttributes>
{objects[0].attributeCollection.questions.map((question) => (
<FirstRepeatAttributeLabelAssistant key={question.key} />
))}
{console.log(objects[0].attributeCollection.questions)} // returns [StringAttributeModel (below)]
</StyledHorizontalAttributes>
Child:
const FirstRepeatAttributeLabelAssistant = ({ label, assistant, key }) => {
return (
<StyledGroup
as={Row}
>
<StyledLabelWrapper>label</StyledLabelWrapper>
{/* {isGroupedInput && ( */}
<Column
size={12}
/>
<Column>
<StyledAssistantWrapper>assistant</StyledAssistantWrapper>
</Column>
</StyledGroup>
);
};
Array:
Yu forgot pass label to Child component:
<FirstRepeatAttributeLabelAssistant key={question.key} label={question._label} />
And using {} to get value from label:
<StyledLabelWrapper>{label}</StyledLabelWrapper>
Look like, you are trying to access the object _contributions
Then you first need to put it into props:
<StyledHorizontalAttributes>
{objects[0].attributeCollection.questions.map((question) => (
<FirstRepeatAttributeLabelAssistant key={question.key} {...question._contributions} />
))}
</StyledHorizontalAttributes>;
With that spread operator, you will have all the props from the _contributions object:
const FirstRepeatAttributeLabelAssistant = ({ label, assistant, key }) => {
return (
...
);
};

How to rerender .map() list

I'm trying to rerender a list when I click a button that sends the first element of the array to the last position, however, when I click the button the component doesn't rerender, even thou the console.log shows that the array has changed:
codesandbox
import React, { useState } from "react";
const DailySchedule = () => {
const [exerciseList, setExerciseList] = useState([
"exercise 1",
"exercise 2",
"exercise 3"
]);
return (
<div>
<section>
<h2>Warm-up</h2>
<ul>
{exerciseList.map((exrcs, idx) => {
return (
<li>
{exrcs}{" "}
{idx === 0 && (
<button
onClick={() => {
exerciseList.push(exerciseList.shift());
setExerciseList(exerciseList);
console.log(exerciseList);
}}
>
Done
</button>
)}
</li>
);
})}
</ul>
</section>
</div>
);
};
export default DailySchedule;
Its because you're modifying the exerciseList array directly, which you shouldn't do, as the state update will see that the list is the same and not trigger a re-render. Instead make a copy of the array, and then use setExerciseList:
const newList = [...exerciseList]
newList.push(newList.shift())
setExerciseList(newList)
This issue is because array reference is not changed.
onClick={() => {
const list = [...exerciseList]
list.push(list.shift());
setExerciseList(list);
}}
This is because the array reference in state is not changed. Update the setState call like this,
<button
onClick={() => {
exerciseList.push(exerciseList.shift());
setExerciseList([...exerciseList]);
console.log(exerciseList);
}}
>
Done
</button>
You have to change the array reference to reflect that in the state.
setExerciseList([...exerciseList]);
Working code - https://codesandbox.io/s/react-playground-forked-ohl1u
As others have pointed out, you're mutating your state directly by using .push() and .shift(). This is a "no-no" in the react-world. Instead, you can treat your array as immutable by not changing your original state, but rather by producing a new array so that your state will update correctly. One approach to do this is to destructure the first item from your array and obtain the rest of your array in an array called rest, then set your new state using the destructured variables using the spread syntax (...):
onClick={() => setExerciseList(([first, ...rest]) => [...rest, first])}

Insert a React component into an array

I have a query about the best way to go about this. So i have a stateless component called <Banner/> which just displays an image and some text.
I then have an array of objects which generates a list of features on the homepage of my site. There's roughly 15 objects in this listGroups array so it renders 15 <Group/> components one after the other. The code for this is below
{listGroups.map((group, i) => (group?.assets?.length > 0) && (
<Group key={group.id} {...group} showTitle={i !== 0} large={i === 0} />
))}
I would like to insert my <Banner/> component into this list in a specific position, ideally after the first <Group/> is rendered. I can use array.splice and add the component into a specific position into the array but it isn't rendered on the page so I'm obviously missing something here.
The end result would be something like this
<Group/>
<Banner/>
<Group/>
<Group/>
<Group/>
and so on
Any help would be appreciated.
You can create an array of JSX.Elements e.g.
const arr: JSX.Elements[] = [];
listGroups.forEach((group, i) => {
if(i == 1) arr.push(<Banner/>);
// add your Groups
})
and you can render the arr.
You have various ways to achieve this.
In React you can render array of elements inside JSX, just like any other variable. If you wish to render components based some data that comes from api you could as well map your data and pass data to component. "key" property is required in both cases so React knows when structure changes.
Live example on CodeSandbox https://codesandbox.io/s/bold-grass-p90q9
const List = [
<MyComponent key="one" text="im 1st!" />,
<MyComponent key="two" text="im 2nd" />,
<MyComponent key="three" text="im 3rd" />
];
const data = [
{ text: "1st string" },
{ text: "2st string" },
{ text: "3st string" }
];
export default function App() {
return (
<div className="App">
<h3>Render array</h3>
{List}
<h3>Map from data</h3>
{data.map(({ text }) => (
<MyComponent key={text} text={text} />
))}
</div>
);
}
Check this and let me know if this is what you want
Sandbox
https://codesandbox.io/s/crazy-lalande-mx5oz
//Have taken limit as 10 for demo
export default function App() {
function print() {
let group = [];
for (let i = 0; i <= 10; i++) {
group.push(<Group key={i} />);
}
group.splice(1, 0, <Banner/>);// adding Banner at 1st index
return group;
}
return <div>{print()}</div>;
}

How to pass props to subcomponent in Reactjs

I am attempting to pass props from a parent component function to a child in React without any luck. I am utilizing the React Data Table Component's filtering functionality. The example documentation in Storybook is great, however I want to change the search box into a list of tags, that when clicked, will filter the data table just the same.
The data I'm trying to parse into individual "tags" is structured like this:
[{"id":"09090","first_name":"Cynthia","last_name":"McDonald","email":"email1#gmail.com","profile_url":"https:myprofile.com/1","types":["professor","science"]},
{"id":"03030","first_name":"Ryan","last_name":"Burke","email":"email2#gmail.com","profile_url":"https://myprofile.com/2","types":["student","science"]},
{"id":"05050","first_name":"Stewart","last_name":"Hook","email":"email3#gmail.com","profile_url":"https://myprofile.com/3","types":["professor","math"]}]
I am trying to create a unique tag list of the "types" attribute that acts as a filter onClick instead of onChange, just like the original search textbox example. So based on the sample data, we would end up with tags for professor, science, student, and math.
If you look at the code in Storybook, more specifically, line 45:
const subHeaderComponentMemo = React.useMemo(() =>
<Filter onFilter={value => setFilterText(value)} />, []);
My data is loaded via an API call and I am trying to pass that data to the subHeaderComponentMemo with props, i.e.,
const subHeaderComponentMemo = React.useMemo(() =>
<Filter data={people} onFilter={value => setFilterText(value)} />, []);
I am then trying to receive and loop through that data on line 20 and am replacing most of that code so that it will render the unique tags from the types attribute of the data.
Storybook code:
const Filter = ({ onFilter }) => (
<TextField id="search" type="search" role="search" placeholder="Search Title" onChange={e => onFilter(e.target.value)} />
);
My failed code
const Filter = ({props, onFilter }) => {
// Get a unique array of types
const types = [...new Set(props.types.map(type => type))];
return types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
));
};
This is of course resulting in an epic fail. The module isn't even displaying.
I'm new to React, so any insight/help would be greatly appreciated.
Updated code based on feedback
const Filter = ({ data, onFilter }) => {
console.dir(data);
if (data.length === 0) {
return <div>No data</div>;
}
const types = [...new Set(data.types.map(type => type))];
return (
<>
{types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
))}
;
</>
);
};
One of the errors I got when I made the changes was "cannot read property of 'map' undefined", which I equated to the fact that this component may be rendering before the data gets there and it is empty. So I added an if with a return in case this happens.
I also rewrote the return the statement in Filter based on the feedback because it did look like it would loop and try to render a new component for each iteration.
However, the issue still stands with the data not being present. The table is now loading with the data, but this component is just returning the "No data" div as if nothing ever happened. I'm not sure if this is due to the useMemo hook or what. Besides that, no errors.
Thank you for your time and feedback.
Another update
UseMemo needed the prop to be a dependency in its array... I'm finally seeing data when I console.log it.
const subHeaderComponentMemo = useMemo(
() => <Filter data={people} onFilter={value => setFilterText(value)} />,
[people]
);
However, I'm getting the 'map' undefined error again, as if my data is not in the structure I expect it to be in. I have not changed it and this is the code I'm using to try to access it (as shared before):
const types = [...new Set(data.types.map(type => type))];
Thanks again everyone... getting closer!
you should rewrite you component signature to match the one you are trying to use
const Filter = ({props, onFilter }) => // you are expecting a props name props
<Filter data={people} onFilter={value => setFilterText(value)} />, []); // you are passing data and setFilterText
so you should change it to
const Filter = ({data, onFilter }) =>

Categories

Resources