How to display the desired information on click ReactJS - javascript

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.

Related

Setting Selected State to Mapped Components

I have a mapped component which iterates through API data. It passes props to each one and therefore each card looks different. See example below.
https://gyazo.com/39b8bdc4842e5b45a8ccc3f7ef3490b0
With the following, I would like to achieve two goals:
When the component is selected, it uses state to STAY SELECTED, and changes the colour as such to lets say blue for that selected component.
I hope this makes sense. How do I index a list as such and ensure the colour and state remains active based on this selection?
See below.
The level above, I map the following cards using these props.
{
jobs.length > 0 &&
jobs.map(
(job) =>
<JobCard key={job.id} job={job}
/>)
}
I am then using the following code for my components:
const JobCard = ({ job }) => {
const responseAdjusted = job.category.label
const responseArray = responseAdjusted.split(" ")[0]
return (
<CardContainer>
<CardPrimary>
<CardHeader>
<CardHeaderTopRow>
<Typography variant = "cardheader1">
{job.title}
</Typography>
<HeartDiv>
<IconButton color={open ? "error" : "buttoncol"} sx={{ boxShadow: 3}} fontSize ="2px" size="small" fontSize="inherit">
<FavoriteIcon fontSize="inherit"
onClick={()=> setOpen(prevOpen => !prevOpen)}/>
</IconButton>
</HeartDiv>
</CardHeaderTopRow>
<Typography variant = "subtitle4" color="text.secondary">
{job.company.display_name}
</Typography>
</CardHeader>
<CardSecondary>
</CardSecondary>
</CardPrimary>
</CardContainer>
)
}
You can attach a handler on the <CardPrimary> component by passing a function to the onClick event. That way whenever you click anywhere on the card div, the function will be triggered.
const [isSelected, setIsSelected] = useState(false);
<CardPrimary onClick={() => setIsSelected(true)} className={isSelected ? "css-class-to-highlight-div" : undefined>
....
</CardPrimary>
If I'm understanding what you're asking for, which I believe is to have your component be highlighted when it is clicked, then you need to modify the 'CardContainer' component to render with an 'onClick' parameter.
Example:
function CardContainer(props) {
const cssClass = 'highlighted';
const my_id = props.id || 'need_an_id';
var clearExistingHighlight = () => [...document.getElementByClassName(cssClass)].forEach((elem)=>elem.classList.remove(cssClass));
var isHighlighted = () => document.getElementById(my_id).classList.contains(cssClass);
var setHighlighted = (e) => {
clearExistingHighlight();
e.target.classList.add(cssClass);
}
return (
<div id={my_id} onClick={setHighlighted}>Cheeseburger fry</div>
)
}
If you don't want the highlight to disappear, you can get rid of the clearExistingHighlight function. Or if you want it to toggle, I recommend a modification of #sid's answer:
const {useState} = React;
function CardContainer(props) {
const [isSelected, setIsSelected] = useState(false);
<div onClick={() => setIsSelected(!isSelected)} className={isSelected ? "highlighted" : undefined>
}
style.css:
.highlighted {
background-color: 'orange';
}
You can do all of this without any react hook and rely instead on CSS classes. You can use the 'isHighlighted' method to determine if a given component is highlighted or not.

How do I calculate the size of a react component before rendering?

I'm working on a react component that renders chips of different sizes based on different data. I don't want the area where the chips are rendered to exceed 2 rows and if they do exceed 2 rows then hide the rest of the chips and render an icon for to click on for more info. Something similar tot he following:
The problem is I don't know how long the text in the chips can be so I don't know when to hide the additional chips and render the info icon.
I don't need a complete solution I'm looking for some guidance on how this could be achievable in react because I don't think I can get the measurements of the chip component before they are rendered, right?
Here is a little jsfiddle example: http://jsfiddle.net/54yc9kuv/16/
const TodoApp = () => {
return (
<div><Parent /></div>
);
}
const Parent = () => {
return (
<div className="parentComp">
<ChildOne />
<ChildTwo />
</div>
);
}
const ChildOne = () => {
return (<div className="childOne">Some random descriptions about the items</div>)
}
const ChildTwo = () => {
const chipData = ["Apples & Kiwis", "Orange", "Grape", "Peach", "Mangoes, Cherry & Peach"];
return (
<div className="childTwo">
{chipData.map(name => (
<Chip key={name} name={name} />
))}
</div>)
}
const Chip = ({name}) => {
return <div className="chip">{name}</div>
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
In the example above I don't want "Mangoes, Cherry & Peach" to render instead have some icon render that you can click on if the chips start exceeded two rows.

How to avoid re-render in React?

I am making a simple accordion which has text editor inside it.
If we click expand text then the text editor gets opened and if we enter some text inside the editor and click shrink, then the accordion gets closed.
Again if click on the expand text of accordion where we made the changes, then the text already entered is missing inside it.
I can understand that this re render every time we click on the expand text. Also this code,
<Text> {toggleValue === index && item.content && <EditorContainer />} </Text>
check for the item clicked then it gets opened so re render happens here and hence I am losing the entered text.
Complete working example:
https://codesandbox.io/s/react-accordion-forked-dcqbo
Could you please kindly help me to retain the value entered inside the text editor despite of the clicks over the text Expand/Shrink?
Put the editor's state into a persistent parent component. Since the NormalAccordion encompasses all editors, and you want persistent state just one editor, use another component, so that the state doesn't get lost when the editor unmounts, then pass it down for the editor to use:
const OuterEditorContainer = ({ toggleValue, setToggleValue, item, index }) => {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const toggleHandler = (index) => {
index === toggleValue ? setToggleValue(-1) : setToggleValue(index);
};
return (
<Accordion>
<Heading>
<div
style={{ padding: "10px", cursor: "pointer" }}
className="heading"
onClick={() => toggleHandler(index)}
>
{toggleValue !== index ? `Expand` : `Shrink`}
</div>
</Heading>
<Text>
{toggleValue === index && item.content && (
<EditorContainer {...{ editorState, setEditorState }} />
)}
</Text>
</Accordion>
);
};
const NormalAccordion = () => {
const [toggleValue, setToggleValue] = useState(-1);
return (
<div className="wrapper">
{accordionData.map((item, index) => (
<OuterEditorContainer
{...{ toggleValue, setToggleValue, item, index }}
/>
))}
</div>
);
};
// text_editor.js
export default ({ editorState, setEditorState }) => (
<div className="editor">
<Editor
editorState={editorState}
onEditorStateChange={setEditorState}
toolbar={{
inline: { inDropdown: true },
list: { inDropdown: true },
textAlign: { inDropdown: true },
link: { inDropdown: true },
history: { inDropdown: true }
}}
/>
</div>
);
You could also put the state into the text_editor itself, and always render that container, but only conditionally render the <Editor.
You need to save the entered text and pass it as props from the parent component to EditorContainer.
Right now everytime you render it (e.g. when we click expand)
It looks like you set an empty state.
Something like:
EditorContainer
editorState: this.props.editorState || EditorState.createEmpty()
onEditorStateChange = (editorState) => {
// console.log(editorState)
this.props.setEditorState(editorState);
};
And in Accordion:
{toggleValue === index &&
item.content &&
<EditorContainer
editorState={this.state.editorState[index]}
setEditorState={newText => this.setState({...this.state, newText}) />}
Didn't try to execute it, but I think that's the way to achieve it.
Ps: Class components are almost not used anymore. Try to use function components and learn about useState hook, looks so much cleaner in my opinion

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

Map different arrays into same component

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

Categories

Resources