So I have replicated my issue on a smaller level so its easier to work with. The goal is to have an onclick function that shows the content inside the column which is hidden as default, this will ideally be handled through setting display: none and changing to display: flex. I have to use an object hence lodash. I cant use document.getElement because all mapped components share a common classname. The data comes from a database so the amount of columns is unknown and always changing per user requirements.
I guess im looking for a method to target to the classname of the div/tag of the iteration of the map where the onclick for that container is. Any help is appreciated!
import _ from "lodash"
import React from 'react'
const dummyData = {
columnOne: "item one",
columnTwo: "item two",
columnThree: "item three"
}
function TestPage() {
return (
_.map(dummyData, (data, key) => {
return (
<>
<div className="column-container">
<p className="heading">{key}</p>
<div className="show-content-btn">V</div> //button to toggle content visibility
</div>
<p className="content">{data}</p> //content to be hidden/shown
</>
)
}
)
)
}
export default TestPage
You can use useState to store classnames and then toggle them with onClick function by changing state with setState.
See this answer. I think it contains what you want.
One way of doing this is by making a separate component for HTML which return from map function and handle state in it. Like:
import _ from "lodash";
import React from "react";
const dummyData = {
columnOne: "item one",
columnTwo: "item two",
columnThree: "item three"
};
function Data({ id, data }) {
const [show, setShow] = React.useState(false);
return (
<>
<div className="column-container">
<p className="heading">{id}</p>
<div className="show-content-btn">
<button onClick={() => setShow(!show)}>V</button>
</div>
</div>
{show && <p className="content">{data}</p>}
</>
);
}
function TestPage() {
return _.map(dummyData, (data, key) => {
return <Data key={key} id={key} data={data} />;
});
}
export default TestPage;
Related
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.
I may lose something on the way.. I want to pass object arguments to a children to dinamically render it in three different ways.
Here is my object:
const cards = [
{
imgSrc: "first",
desc: "some text"
},
{
imgSrc: "second",
desc: "some text"
},
{
imgSrc: "third",
desc: "some text"
}
];
This is the children component:
import { Box } from '#mui/system'
import React from 'react'
import "../../style/main.scss"
import first from "../../images/veloce.png"
import second from "../../images/sicuro.png"
import third from "../../images/vicino.png"
import { Typography } from '#mui/material'
function Card( source, text ) {
return (
<Box className="foundation-card">
<img src={source}/>
<Typography variant="h6">{text}</Typography>
<Typography variant="body2">{text}</Typography>
</Box>
)
}
export default Card
And then i have the parent component where i want to render multiple Card mapping the cards array:
import Card from "./Card"
import CustomDivider from "../foundation/CustomDivider"
function Values() {
return (
<Box className="vertical-box main-maxw section-margin">
<Typography variant="h4">Perchè sceglierci</Typography>
<CustomDivider />
<Box className="foundation-box values">
{cards.map((p) => {
return <Card source={p.imgSrc} text={p.desc} />
})}
</Box>
</Box>
)
}
export default Values
and then i receive this:
Error: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead.
I suppose it is a stupid error but i am in my first approach and i don't know how to move.
Thank you all.
I think the problem is that your card function is expecting positional arguments but you're calling it with an object.
<Card source={p.imgSrc} text={p.desc} />
// These two things are equivalent.
Card({source: p.imgSrc, text: p.desc})
So essentially you were assigning the source arg and object that contained both source and text.
Try changing your card function to accept an object
function Card({source, text}) {
...
}
I see some strange things.
1- where you are mapping the cards, you are not importing the cards array, or i cant see where are you getting the cards array from
2- function card is a react component so you should wrap the props with {} as this: function Card({source,text})
In my react project I am using react-window package to render nested lists. Each parent FixedSizeList row renders a component which uses another FixedSizeList. Parent List doesn't have more than 14 rows at the moment. But the child List may contain upto 2000 rows. Now my problem is, when I try to scroll through the parent List, all the child list items in the viewport seem to re rendering. This is a little bit problematic for me because in my child list item I am using d3js to draw bar chart with transition effect. So these unnecessary re rendering is giving a overall weird UI. Can anyone help me how can I stop these unnecessary renders.
Here is codesandbox link to a very simple example of my problem.
Please open the console log. After initial load the topmost log should be like this: initial console log.
Then if you clear the console and scroll the parent list, you will see log like this: console log after parent scrolling. Here you can see that the child list items of child list 0 is re rendering which is not needed for me.
Can anyone give me a solution that can stop these re rendering?
*P.S. I am not using memo since every row is updating the dom on its own.
Edit
I think this problem would solve if the parent list would stop propagating scroll event to child. I tried to add event.stopPropagation() and event.stopImmediatePropagation() in the parent list row but the output was the same as earlier.
We can use memo to get rid of components being re-rendered unnecessarily for same set of props. And use useCallback to prevent re-creation of a function and thus secure child components being re-rendered. Applying those, we can get this solution:
import "./styles.css";
import { FixedSizeList as List } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("rendering child list", parentIndex);
const InnerRow = useCallback(({ index, style }) => {
console.log("rendering child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
}, []);
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRow}
</List>
</div>
);
});
const Example = () => {
console.log("rendering parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
although the above code works fine, it can be optimized more if we use areEqual method from react-window as react memo dependency. And for more if we want to use other react hooks inside InnerRow component, we must add a middleware component of InnerRow. The full example is given below:
import { FixedSizeList as List, areEqual } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("mounting child list", parentIndex);
const data = new Array(15).fill(new Array(500).fill(1));
const InnerRowCallback = useCallback(
({ index, style }) => {
return <InnerRow index={index} style={style} />;
},
[data]
);
const InnerRow = ({ index, style }) => {
console.log("mounting child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
};
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRowCallback}
</List>
</div>
);
}, areEqual);
const Example = () => {
console.log("mounting parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
Here I am passing data array as useCallBack dependency because I want to re render the InnerRow component if data gets changed.
This is something that is easy to work around, but I was wondering if this is possible. Is it achievable at all to render a React component by accessing an object with a dynamic key?
Trying the below shows that the expected way of doing it is invalid syntax in JSX. I understand I could store the active object in the map in the state or conditionally map the object entries, but I couldn't seem to find any questions regarding this and was hoping to see if anyone has any experience with this.
Thanks for your help.
Here's the setup I have:
import React, { useState } from "react"
import {ComponentOne, ComponentTwo, ComponentThree} from "../directory"
const map = {
k1 = { name="Component 1", child=ComponentOne }
k2 = { name="Component 2", child=ComponentTwo }
k3 = { name="Component 3", child=ComponentThree }
}
const myComponent = () => {
const [active, setActive] = useState("k1")
return (
<>
<div>
{
Object.entries(map).map(([k, v]) =>
<h1 onClick={() => setActive(k)}>{ v.name }</h1>
)
}
</div>
<div>
< map[active].child />
</div>
<>
)
}
All components in the end are functions or classes that you can get a reference to and therefore access dynamically through any object.
JSX is simply a unique syntax to call that function. So first get a reference to the React component and then use JSX to render the component.
Solution Code:
import React, { useState } from "react";
import { ComponentOne, ComponentTwo, ComponentThree } from "../directory";
const map = {
k1: { name: "Component 1", child: ComponentOne },
k2: { name: "Component 2", child: ComponentTwo },
k3: { name: "Component 3", child: ComponentThree },
};
const myComponent = () => {
const [active, setActive] = useState("k1");
// Store the reference to the component you want to render in a variable
const ActiveChild = map[active].child;
return (
<React.Fragment>
<div>
{Object.entries(map).map(([k, v]) => (
<h1 onClick={() => setActive(k)}>{v.name}</h1>
))}
</div>
<div>
{/* Since the variable holds reference to a React component, you can render it JSX syntax */}
<ActiveChild />
</div>
</React.Fragment>
);
};
I have created a menu card that will show a card for each object in an array. The object holds details about that menu item. I am looking to affect one property of that menu item but I am unsure how to potentially use the key or an id to affect only that item. How can I dynamically select an object from an array based on its key or some other unique identifier?
Right now as a stand-in I use handleCategory to affect the first object in the array. I am looking instead to affect the object based on which Item card I am using.
Below is what my code looks like (basic example):
Home.js
import React, { Component } from 'react';
import { Form, Button } from 'react-bootstrap';
import Items from './items'
import Navbar from './navbar'
class Home extends Component {
constructor() {
super();
this.state = {
value: 'enter',
condimentCount: 5,
menuItems: [
{
"id": 1234,
"item_name": 'chow mein',
"category_id": 'meal'
},
{
"id": 1235,
"item_name": '',
"category_id": 'meal'
}
]
};
this.handleCategory = this.handleCategory.bind(this);
}
handleCategory = (event, id) => {
console.log('changing meal type', event.target.value)
//this.setState({value: event.target.value});
// 1. Make a shallow copy of the items
let items = [...this.state.menuItems];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[0]};
// 3. Replace the property you're intested in
item.category_id = event.target.value;
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
items[0] = item;
// 5. Set the state to our new copy
this.setState({menuItems: items});
}
render() {
let menuItems = null;
if (this.state.menuItems) {
menuItems = (
<div>
{
this.state.menuItems.map((item, i) => {
return <div key={i}>
<MenuItem
key={item.id} //IS THIS CORRECT?
item_name={item.item_name}
category_id={item.category_id}
handleCategory={this.handleCategory}
/>
</div>
})
}
</div>
)
}
return (
<div>
<Navbar />
<div>
{Items}
</div>
</div>
);
}
}
export default Home;
Items.js
import React, { Component } from 'react';
import classes from './items.module.css'
const Items = (props) => {
return (
<div className={classes.cards}>
<form>
<label>
What kind of meal item is this (e.g. meal, drink, side):
<select value={props.category_id} onChange={props.handleCategory}>
<option value="meal">meal</option>
<option value="drink">drink</option>
<option value="side">side</option>
</select>
</label>
</form
</div>
)
}
export default Items;
I'm not really sure what you're trying to do in handleCategory, but I understand your question I believe.
You can use the index from map to access the menu items in handleCategory.
this.state.menuItems.map((item, i) => {
return <div key={i}>
<MenuItem
item_name={item.item_name}
category_id={item.category_id}
handleCategory={e => this.handleCategory(e, i)}
/>
</div>
})