As you can see in the two components below, i want to delete the recipes(in app component) from a button click in the panelcomponent,
i have a method in app to delete the recipe, and a prop(onclick) send to child panelcomponent. Panel then gets the index from the map of recipes, and after the button click it executes the handleDelet method to send the index back to parent. but No this is not working !
class App extends React.Component {
state={
addRecipe:{recipeName:"",ingredients:[]},
recipes:[{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]}]
}
handleDelete = (index) => {
let recipes = this.state.recipes.slice();
recipes.splice(index,1); //deleting the index value from recipe
this.setState({recipes}) //setting the state to new value
console.log(index,recipes)
}
render() {
return (
<div className="container">
<PanelComponent recipes={this.state.recipes} onClick={()=>this.handleDelete(index)}/>
<ModalComponent />
</div>
);
}
}
class PanelComponent extends React.Component {
handleDelete = (index) => {
this.props.onClick(index); //sending index to parent after click
console.log(index)
}
render() {
return (
<PanelGroup accordion>
{this.props.recipes.map( (recipe,index) => {
return(
<Panel eventKey={index} key={index}>
<Panel.Heading>
<Panel.Title toggle>{recipe.recipeName}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
<ListGroup>
{recipe.ingredients.map((ingredient)=>{
return(<ListGroupItem>{ingredient}</ListGroupItem>);
})}
</ListGroup>
<Button bsStyle="danger" onClick={()=>this.handleDelete(index)}>Delete</Button>
<EditModalComponent />
</Panel.Body>
</Panel>
);
})}
</PanelGroup>
);
}
}
Thea actual error in your code is that while using arrow function in the onClick in parent, you are passing the wrong parameter, instead of {()=>this.handleDelete(index)} what you should write is
{(value)=>this.handleDelete(value)}, However, that also not necessary and you could simple write {this.handleDelete} in App since your handleDelete function is already binded and it received the values from the Child component.
render() {
return (
<div className="container">
<PanelComponent recipes={this.state.recipes} onClick={(value)=>this.handleDelete(value)}/>
<ModalComponent />
</div>
);
}
The difference in writing {()=>this.handleDelete(index)} vs {(value)=>this.handleDelete(value)} is that in the first case, you are explicitly passing the index that you get from the map function in your App component while in the second case, the value passed from the child component when you execute this.props.onClick(value) is being provided to the handleDelete function.
you are sending the function wrongly as props. you are sending the result of the function as props rather than the function itself
class App extends React.Component {
state={
addRecipe:{recipeName:"",ingredients:[]},
recipes:[{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]}]
}
handleDelete = (index) => {
let recipes = this.state.recipes.slice();
recipes.splice(index,1); //deleting the index value from recipe
this.setState({recipes}) //setting the state to new value
console.log(index,recipes)
}
render() {
return (
<div className="container">
//change here
<PanelComponent recipes={this.state.recipes} onClick={this.handleDelete}/>
<ModalComponent />
</div>
);
}
}
Related
I'm new to React and I'm wondering if one can update the content of another component based on the events from another component.
I have a two react components. One of the components gets its data loaded to it when the page loads and the other component should have its data rendered based on the first components onClick method.
One of the components gets its data loaded to it from an API, that I then show in a list. I then have a method that should handle the clicked items called itemClicked, when an item gets clicked the data in the other component should update.
itemClicked = () => {
// Get the ID of the clicked item, and use it in the other component
console.log(e.target.attributes[0].value);
}
// ...
<ul>
{items.map(item => (
<li onClick={this.itemClicked} key={item._id} itemID={item._id}> {item.name} </li>
))}
</ul>
I now this might be a bad question to ask, but I'm new to React and trying to learn a bit more about states and props.
Edit.
This is the parent component where I load the two components:
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
currentItem: {}
}
}
onItemClick(item) {
this.setState({
currentItem: item
})
}
render() {
return <>
<div className='dashboard'>
<div className='dashboard__wrapper'>
<SelectItem onItemClick="{this.onItemClick}" />
<EditItem item="{this.state.currentItem}" />
</div>
</div>
</>
}
}
export default Dashboard
I updated the SelectItem component as well:
itemClicked = (e) => {
console.log(e.target.attributes[0].value);
if (this.props.onItemClick) {
this.props.onItemClick(e.target.attributes[0].value)
}
}
But now it complains that this line:
this.props.onItemClick(e.target.attributes[0].value)
Isn't a function:
Uncaught TypeError: this.props.onItemClick is not a function
Sure you can.
If the list component and view component are both in same parent component. The parent component can have a state property e.g.currentItem. The currentItem will be set by passing a callback as property to the list component. The callback should be called when list item is clicked.
//List Component
itemClicked = () => {
// Get the ID of the clicked item, and use it in the other component
console.log(e.target.attributes[0].value);
//call the callback
if(this.props.onItemClick){
this.props.onItemClick(e.target.attributes[0].value)
}
}
...
<ul>
{items.map(item => (
<li onClick={this.itemClicked} key={item._id} itemID={item._id}> {item.name} </li>
enter code here
))}
</ul>
Page Component
this.state = {
currentItem: {}
}
onItemClick(item){
this.setState({
currentItem: item
})
}
render(){
return <>
<ListComponent onItemClick={this.onItemClick} />
<ViewComponent item={this.state.currentItem} />
</>
}
I a learning react and stuck at this place. I am creating an app In which user will see a list of product with different id and name. I have created another component in which the detail of the product will open . I am collection the id and value of the particular product in my addList component by onClick function. And now i want to send those value in DetailList component so that i can show the detail of that particular product.
A roadmap like
Add list -> (user click on a product) -> id and name of the product passes to the DetailList component -> Detail list component open by fetching the product detail.
Here is my code of Add list component
export default class Addlist extends Component {
constructor(props) {
super(props)
this.state = {
posts : []
}
}
passToDetailList(id) {
console.log( id)
}
async componentDidMount() {
axios.get('http://localhost:80/get_add_list.php')
.then(response => {
console.log(response);
this.setState({posts: response.data})
})
.catch(error => {
console.log(error);
})
}
render() {
const { posts } = this.state;
// JSON.parse(posts)
return (
<Fragment>
<div className="container" id="listOfAdd">
<ul className="addUl">
{
posts.map(post => {
return (
<li key={post.id}>
<div className="row">
<div className="col-md-4">
<img src={trialImage} alt=""></img>
</div> {/* min col end */}
<div className="col-md-8">
<b><h2>{post.add_title}</h2></b>
{/* This button is clicked by user to view detail of that particular product */}
<button onClick={() => this.passToDetailList(post.id)}>VIEW</button>
</div> {/* min col end */}
</div> {/* row end */}
</li>
);
})}
</ul>
</div>{/* container end */}
</Fragment>
)
}
}
You should pass the data through the routes -
<Route path="/details/:id" component={DetailList} /> // router config
passToDetailList(id) {
this.props.history.push('/details/'+id)
}
and then in the DetailList Component, you can access the value through -
console.log(this.props.match.params.id) - //here is the passed product Id
You need to elevate the state for id to a common parent between AddList and DetailList and then create a function in parent component to set the id and pass the id and setId function to your AddList Component through props , then just use setId function to set the id state in passToDetailList function.
finally you can use the id in your DetailList Component to fetch its details
so Here is how your AddList Component would look like:
export default class Addlist extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
passToDetailList(id) {
this.props.setId(id);
}
// The rest of your code
}
and here is how your DetailList Component will look like:
export default class DetailList extends Component {
componentDidMount(){
// Use the id to fetch its details
console.log(this.props.id)
}
}
and finally here is your CommonParent Component:
export default class CommonParent extends Component {
constructor(props) {
super(props);
this.state = {
id: ''
};
this.setId = this.setId.bind(this);
}
setId(id){
this.setState({
id
})
}
render(){
return(
<>
<AddList setId={this.setId} />
<DetailList id={this.state.id} />
</>
)
}
}
if your Components are very far from each other in component tree you can use react context or redux for handling id state
I'm trying to render dynamically a collection of component using componentDidUpdate.
This is my scenario:
var index = 0;
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
componentList: [<ComponentToRender key={index} id={index} />]
};
this.addPeriodHandler = this.addPeriodHandler.bind(this);
}
componentDidUpdate = () => {
var container = document.getElementById("container");
this.state.componentList.length !== 0
? ReactDOM.render(this.state.componentList, container)
: ReactDOM.unmountComponentAtNode(container);
};
addHandler = () => {
var array = this.state.componentList;
index++;
array.push(<ComponentToRender key={index} id={index} />);
this.setState = {
componentList: array
};
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.addHandler}>
Add Component
</button>
<div id="container" />
</div>
);
}
}
The problem is that componentDidUpdate work only one time, but it should work every time that component's state change.
Thank you in advance.
This is not how to use react. With ReactDOM.render() you are creating an entirely new component tree. Usually you only do that once to initially render your app. Everything else will be rendered by the render() functions of your components. If you do it with ReactDOM.render() you are basically throwing away everything react has already rendered every time you update your data and recreate it from scratch when in reality you may only need to add a single node somewhere.
Also what you actually store in the component state should be plain data and not components. Then use this data to render your components in the render() function.
Example for a valid use case:
class MyComponent extends Component{
state = {
periods: []
};
handleAddPeriod = () => {
this.setState(oldState => ({
periods: [
...oldState.periods,
{/*new period data here*/}
],
});
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.handleAddPeriod}>
Add Component
</button>
<div id="container">
{periods.map((period, index) => (
<ComponentToRender id={index} key={index}>
{/* render period data here */}
</ComponentToRender>
))}
</div>
</div>
);
}
}
}
Also you should not work with global variables like you did with index. If you have data that changes during using your application this is an indicator that is should be component state.
try
addHandler = () =>{
var array = this.state.componentList.slice();
index++;
array.push(<ComponentToRender key={index} id={index}/>);
this.setState=({
componentList: array
});
}
if that works, this is an issue with the state holding an Array reference that isn't changing. When you're calling setState even though you've added to the Array, it still sees the same reference because push doesn't create a new Array. You might be able to get by using the same array if you also implement shouldComponentUpdate and check the array length of the new state in there to see if it's changed.
I checked quite a lot of examples but found most of them having events fired in Child Component.
Can someone please suggest how can I call the Parent Component's function in child with the click event on Parent Component? Thanks.
Parent Component (app.js):
Class App extends Component {
handleClick = (e, id, text) => {
e.preventDefault();
this.setState({val: text})
}
render() {
return (
<div>
<Form val={this.state.val} Click={this.handleClick.bind(this) }/>
<Button onClick={(e) => this.handleClick(e, todo.id, todo.text)}>
<Icon>edit_icon</Icon>
</Button>
</div>
)
}
}
Child Component (form.js):
this.props.Click(); //where should i call this function since my button is in parent component
Class Form extends Component{
render() {
const { text } = this.state;
return (
<TextField
value={text}
color="secondary"
/>
)
}
}
}
If you want to call it in your Child component, you need an event to trigger it or maybe a condition. So, for example in your form.js, we will trigger it with a button click form your child component
render() {
const { text } = this.state;
return (
<TextField
value={text}
color="secondary"
/>
<Button onClick={this.props.Click} />
)
}
}
Maybe, using a Button in your child component is not a great choice for your case since you already have a Button to call the Click function in your parent component, this Button in child component I made is only for example
One way you can do this is use a ref to call the function..
// create the ref
constructor() {
super();
this.myFormRef = React.createRef()
}
// click handler for the button
handleClick = (e, id, text) => {
// here you have the child instance which gives you access to its functions
this.myFormRef.someChildMethodThatIsOnTheChildClass()
}
render() {
// notice here we use the ref on the form via... ref={this.myFormRef}
return (
<Form val={this.state.val} ref={this.myFormRef} Click={this.handleClick.bind(this) }/>
<Button onClick={(e) => this.handleClick(e, todo.id, todo.text)}>
<Icon>edit_icon</Icon>
</Button>
)
)
I would like to note though that it doesn't seem to make much sense as to why you want to do this. You should probably re-think your architecture. Also what is the button press supposed to be doing? submitting the form?
I have a parent component in React who display a list of items, also a state of (selectedList) who give an active class to this list item + display others components depending of the active class. This is the parent component.
In a child, I display the form where I can set a new list and an event onSubmit where I insert it in a Collection (meteor+mongo)
The problem is I can't make a relation between the new item (id) and the parent component cause I would like to select the list newly created (so give active class and display other components). Then I think I should update the state.selectedListId but I don't know how in a child, I can send it to the parent component ?
Here is few lines of codes:
PARENT ELEMENT (PAGE)
class TodosPage extends Component {
constructor(props) {
super(props);
this.state = {
listSelected: "",
};
}
selectList(listId) {
this.setState({ listSelected: listId });
}
renderLists() {
return this.props.lists.map((list) => (
<List
selectedItemId={this.state.listSelected}
selectList={() => this.selectList(list._id)}
key={list._id}
list={list}
countPendingTasks={this.countPendingTasks(list._id)}
/>
));
}
render() {
return (
<div className="container">
<ListForm />
<ListGroup>
{this.renderLists()}
</ListGroup>
CHILD ELEM (ListForm)
handleSubmit(event) {
event.preventDefault();
const name = ReactDOM.findDOMNode(this.refs.nameInput).value.trim();
Meteor.call('lists.insert', name, (err, listId) => {
console.log("in method insert = " + listId);
// HERE I CAN HAVE THE GOOD ID
});
ReactDOM.findDOMNode(this.refs.nameInput).value = '';
}
render() {
return (
<Form bsClass="col-xs-12" onSubmit={this.handleSubmit.bind(this)} >
<FormGroup bsClass="form-group">
<FormControl type="text" ref="nameInput" placeholder="Add New List" />
</FormGroup>
</Form>
);
}
Then, I can have the good ID in HandleSubmit but I don't know how to give it back to the parent component ..
Thanks for help
Have the parent (TodosPage) pass a function as a prop to its child (ListForm). Then onSubmit, have ListForm call the function.
class TodosPage extends React.Component {
handleListFormSubmit = (goodId) => {
// do something with goodId
}
render() {
return <ListForm onSubmit={this.handleListFormSubmit} />;
}
}
class ListForm extends React.Component {
handleSubmit = (event) => {
// get GOOD ID from the form, then call the parent function
// [...]
this.props.onSubmit(goodId);
}
render() {
<Form onSubmit={this.handleSubmit}>
{/* form stuff here */}
</Form>
}
}
In fact, it was pretty simple,
I just used my SelectList function and like #Ty Le said sent it to the child via prop, but to set the new state, I have to add in my parent constructor:
this.selectList = this.selectList.bind(this);
Or I get an error: this.setState is undefined ..
Thanks