Child component inside .map function display all children instances [duplicate] - reactjs

This question already has answers here:
React onClick function fires on render
(13 answers)
Closed 3 years ago.
I am building a website that allows a user to create a job opening, assign to it different requirements and delete it.
I have two main components:
HRdashboard is the parent component in which all the job openings are listed.
MyOpeningCard is the card that contains information of a single job opening. It comes from the .map function in the parent component:
HRdashboard
function HRdashboard({changeAuthLoginHR}) {
const [HRuser, setHRuser] = React.useState([]);
const [openings, setOpenings] = React.useState([]);
useEffect( ()=> {
let unmountedOpenings = false
async function getAllOpenings(){
let response = await axios("http://www.localhost:3000/api/v1/openings");
if(!unmountedOpenings)
setOpenings(response.data)
}
let jwt = window.localStorage.getItem('jwt')
let result = jwtDecode(jwt)
setHRuser(result)
changeAuthLoginHR()
getAllOpenings()
return () => {
unmountedOpenings = true
}
}, [],
)
return(
<div>
<Container style={{marginTop: "50px"}}>
<Row>
{openings.map(opening =>
<Col>
<MyOpeningCard key={opening.id} opening={opening} /> # Here is where the child is
</Col>
)}
</Row>
</Container>
</div>
)
}
MyOpeningCard
function MyOpeningCard({opening}) {
return(
<div>
<Card style={{ width: '18rem', marginTop: "15px"}}>
<Card.Body>
<Card.Title>{opening.title}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">Requirements: </Card.Subtitle>
<Card.Text>
{opening.requirements[0].requirements.map(requirement => <li>{requirement}</li>)}
</Card.Text>
<div>
<Card.Link href="#" onClick={console.log(opening.id)}>Click Here</Card.Link> # If I click here, it console.log all the IDs, not only this opening id.
</div>
</Card.Body>
</Card>
</div>
)
}
My question:
If I click on Click Here in my child component, it triggers console.log(opening.id).
I was expecting to see in console only the opening.id of the Job Opening I click. However, I see all the ids of the openings.
Why is it so?

Because your onClick prop is calling a function which immediately runs the same time your child component is rendered, without even clicking, for all the child components.
onClick={console.log(opening.id)}
So, when you use the .map() method, all of your <MyOpeningCard /> components get rendered, each of them immediately calling console.log() again and again, hence you see all the ids logged in the console.
You should pass a callback, instead, to the onClick prop which will be executed only when a click happens.
Change it to
onClick={() => { console.log(opening.id) }}

Parentheses after a function name executes the function.
even-though you didn't mean to do that, that's why wrapping it with an additional Arrow Function is needed, it happens as:
() => console.log('something'), this Arrow Function is not going to be invoked until the click actually occurs.
while if you pass console.log this way -no parenthesses-, it'll be logging the whole Event only upon the Click event occurrence.

Related

function called for every row in the datagrid although I want to call it only onClick in react

I got this function:
const DataEdit = ({id}) => {
console.log(id)
}
and this column inside my DataGrid(imported from #miu/x-data-grid):
{
field:'Delete',
headerName:'Delete',
width:100,
sortable:false,
disableClickEventBubbling:true,
renderCell: (params) => {
return(
<div>
<IconButton
aria-label="add an alarm"
onClick ={DataEdit(params)}
>
<Delete
color="error"
style={{cursor:'pointer'}}
/>
</IconButton>
</div>
)
}
},
for some reason the DataEdit function sends console log for every row in the DataGrid in the page, first of all, why is that? second of all, how can I make it work only when I click the IconButton.
Thanks in advance.
As you created it, it is a normal function so it will be loaded in stack and executed as component renders (it will return value to onClick). On the other hand if you rewrite it as:
onClick ={() => DataEdit(params)}
it will create a function DataEdit and assign it to onClick, thus will be executed only when clicked.

how to render a same component one or multiple time using event handler

I want to build form in Which there are some text fields as shown in picture
in this form there is a button and after clicking the Add Units button a new form will appear
how can i render this sub form by using button onClick Event-Handler and i also want to render it as many times as i click the button , if i click button one time then it shows the the sub form only one time if i click the button two times then it will show two times
-The main issue is I want the sub form to appear as many time i click the button
(optional if i want to remove the rendered sub form using a button click then please mention the code )
use of react hooks is preferable
you can ask me anything related to question
Here is an example of how to solve your problem. You don't need useEffect, and you don't need three separate state variables. You just need one array of objects.
import React, { useState } from "react";
import ContainerSlot from "./ContainerSlot";
function ReceiptContainer() {
const [containers, setContainers] = useState([]);
const handleAddContainer = () => {
// adding an empty object; you will likely need to
// initialize this object with whatever values are stored
// in the container form
setContainers((current) => [...current, {}]);
};
const handleRemove = (index) => {
const current = [...containers];
current.splice(index, 1);
setContainers(current);
};
return (
<div className="receiptContainer container">
{/* Heading */}
<div className="receiptContainer__heading mt-3">
<h4>Container Invoice</h4>
<hr />
</div>
<button onClick={handleAddContainer}>Add container</button>
<div className="receiptContainer__container">
{/* You will likely need to pass whatever values are in
the container to your ContainerSlot component */}
{containers.map((container, index) => {
const handleRemoveClick = () => {
handleRemove(index);
};
return (
<div
key={`container-${index}`}
style={{ display: "flex", alignItems: "center" }}
>
<div>
<ContainerSlot {...container} />
</div>
<div>
<button onClick={handleRemoveClick}>Remove</button>
</div>
</div>
);
})}
</div>
</div>
);
}
export default ReceiptContainer;
EDIT: updated to show how to remove an item

React not rendering all elements within a list of components

I have a system in which when the user presses a button, a new component is pushed to a list of components and the state is updated. I render the list of components using {}, but only the first element is rendered.
I've used console.log to ensure that my list is actually updating. All the questions I've seen so far for this problem involve using a class that extends React.Component. Since I'm using a function to render, I don't see how I can use those solutions
export default function ExampleManager() {
const [examples, setExamples] = React.useState([<Example key={0}/>);
function handleClick() {
examples.push(<Example key={examples.length}/>);
setExamples(examples);
}
return (
<>
{examples}
<Button variant="outlined" color="primary" onClick={handleClick}>
Add Example
</Button>
</>
);
}
If the button was to be clicked multiple times, I would expect there to be multiple Example components, however at the moment only the first element works
As far as I know, examples is immutable and isn't updated by using examples.push().
Change your handleClick to the following code, to remove the reference of your example variable:
function handleClick() {
// create a new array to prevent referencing the old on
setExamples([
...examples,
<Example key={examples.length}/>
]);
}
Nonetheless you shouldn't add components per se into your array. Try to split values and its representation like the following:
function ExampleManager() {
const [examples, setExamples] = React.useState([0]);
const handleClick = () => setExamples([...examples, examples.length])
return (
<>
{examples.map((item, key) => <Example key={key} data={item} />)}
<Button variant="outlined" color="primary" onClick={handleClick}>
Add Example
</Button>
</>
)
}

Getting id from an event handler that was passed to a button component in React?

I have an App component that renders a MenuBar component. In that MenuBar, I render ListItems (from material-ui) and pass the following as a prop:
onClick = (e) => {
const id = e.target.id;
console.log(id);
console.log('called');
}
the MenuBar component:
render() {
const onClick = this.props.onClick;
const titles = ["Home", "About", "Docket", "Polls", "News"];
const listItems = titles.map(title =>
<ListItem button id={title} onClick={onClick} key={title}>
<ListItemText primary={title} />
</ListItem>);
return (
<List
component="ul"
className="menu-bar">
{listItems}
</List>
);
}
I want to use this title, which I try to retrieve from the event, so that I can render a Home component, or About component, etc, depending on which ListItem is selected. But when I run this and click randomly on some of the ListItems in my browser, the title is only logged to the console sometimes (seemingly randomly). What is the best way for me to access which ListItem was selected, or why is the console logging the title only sometimes?
Here is what I get in the browser console:
Docket
called
called
Polls
called
News
called
The reason why you don't consistently get id is that the html element triggering the click event is not always what you think. Please see this sandbox for an example. When you click on any of the ListItem, sometimes you get (if you click to the left of where the letter is, based on my trials):
<div class="MuiButtonBase-root-1883 MuiListItem-root-1871 MuiListItem-default-1874 MuiListItem-gutters-1879 MuiListItem-button-1880" tabindex="0" role="button" id="a">...</div>
where the id is present. But other times you get:
<span class="MuiTypography-root-1892 MuiTypography-subheading-1899 MuiListItemText-primary-1889">a</span>
where the id is missing.
A solution to this is to pass title directly to the onClick function:
<ListItem button id={title} onClick={() => onClick(title)}>
...
</ListItem>
Accordingly, the onClick function should be updated as follows:
onClick = (title) => {
console.log(title);
console.log('called');
}
currentTarget is what worked it for me. (instead of just target)
I've read elsewhere that maybe MUI is resulting in unexpected behavior. Similar to what Claire Lin stated, in the Material UI List (and for me, it was nested in a MUI Drawer as well), when clicking on a button, the actual target was different from what I expected. The solution I found is to use the following:
Try e.currentTarget.getAttribute('value') , where "value" is data placed on the ListItem.
Here's how I added it. See "value" being assigned, and then logged below in the onClick. (...I tried to remove most of the extra code, but there is still a bit of superfluous code in there just bc it provides context.)
return (
//removed extra code for brevity
<Drawer
anchor={'left'}
open={drawerOpen}
onClose={toggleDrawerOpen(false)}
>
<List>
{itemsList.map((item, index) => {
const { text, icon, id } = item
return (
<ListItem
button
key={id}
value={index} // <<<< HERE!!!
onClick={(e) => {
console.log(e.currentTarget.getAttribute('value')) // <<<< HERE!!!
setDrawerOpen(false)
}}
>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText primary={text} />
</ListItem>
)
})}
</List>
</Drawer>
...Again, I used "value" and assigned it the "index".
currentTarget is the trick.

React - Setting state to target with onClick method

I am trying to recreate a tabs component in React that someone gave me and I am getting stuck while getting the onClick method to identify the target.
These are the snippets of my code that I believe are relevant to the problem.
If I hardcode setState within the method, it sets it appropriately, so the onClick method is running, I am just unsure of how to set the tab I am clicking to be the thing I set the state to.
On my App page:
changeSelected = (event) => {
// event.preventDefault();
this.setState({
selected: event.target.value
})
console.log(event.target.value)
};
<Tabs tabs={this.state.tabs} selectedTab={this.state.selected}
selectTabHandler={() => this.changeSelected}/>
On my Tabs page:
{props.tabs.map(tab => {
return <Tab selectTabHandler={() => props.selectTabHandler()} selectedTab={props.selectedTab} tab={tab} />
})}
On my Tab page:
return (
<div
className={'tab active-tab'}
onClick={props.selectTabHandler(props.tab)}
>
{props.tab}
</div>
When I console.log(props.tab) or console.log(event.target.value) I am receiving "undefined"
There are a few issues causing this to happen. The first issue is that you wouldn't use event.target.value in the Content component because you aren't reacting to DOM click event directly from an onClick handler as you are in Tab, instead you are handling an event from child component. Also keep in mind that event.target.value would only be applicable to input or similar HTML elements that have a value property. An element such as <div> or a <span> would not have a value property.
The next issues are that you aren't passing the tab value from Tabs to Content and then from within Content to it's changeSelected() handler for selectTabHandler events.
In addition the onClick syntax in Tab, onClick={props.selectTabHandler(props.tab)} is not valid, you will not be able to execute the handler coming from props and pass the props.tab value. You could instead try something like onClick={() => props.selectTabHandler(props.tab)}.
Content - need to pass tab value coming from child to changeSelected():
render() {
return (
<div className="content-container">
<Tabs
tabs={this.state.tabs}
selectedTab={this.state.selected}
selectTabHandler={tab => this.changeSelected(tab)}
/>
<Cards cards={this.filterCards()} />
</div>
);
}
Tabs - need to pass tab coming from child to selectTabHandler():
const Tabs = props => {
return (
<div className="tabs">
<div className="topics">
<span className="title">TRENDING TOPICS:</span>
{props.tabs.map(tab => {
return (
<Tab
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
);
})}
</div>
</div>
);
};
export default Tabs;
Also don't forget the unique key property when rendering an array/list of items:
<Tab
key={tab}
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
Here is a forked CodeSandbox demonstrating the functionality.

Resources