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} />
</>
}
Related
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 have a component called Settings that has four menu items. When one menu item gets clicked, the state of the selected menu item gets changed, then the component gets re rendered where a function called this.returnForm() gets called to pass the selected menu item state as a prop to another component called GeneralSettings.
class Settings extends React.Component {
constructor(props) {
super(props);
this.state = {
form_fields: ['Select option'],
selectedSetting: 'general',
};
this.handleClick = this.handleClick.bind(this);
}
returnForm = (state) => {
return <GeneralSettings selectedSetting={state}/>;
};
handleClick(event) {
this.setState(
{
selectedSetting : event.currentTarget.dataset.settingspage
}
)
}
render() {
return (
<div className = "main-content-wrapper">
<div className="main-content-area settings" aria-hidden="false">
<div className="destinations-filter">
<div className="card">
<div className="card-content">
<ul className="settings-list">
<li data-settingspage="general" onClick={this.handleClick}>
<a><i
className="icon icon-settings-general"></i>General </a>
</li>
<li data-settingspage="network" onClick={this.handleClick}>
<a><i className="icon icon-settings-network"></i> Network</a>
</li>
<li data-settingspage="security" onClick={this.handleClick}>
<a><i className="icon icon-settings-security"></i> Security</a>
</li>
<li data-settingspage="log" onClick={this.handleClick}>
<a><i className="icon icon-settings-logs"></i> Logs</a>
</li>
</ul>
</div>
</div>
</div>
<div className="destinations-container">
<div className="card">
{this.returnForm(this.state.selectedSetting)}
</div>
</div>
</div>
</div>
)
}
}
In the component called GeneralSettings, I have a componentDidMount method where I pass the selected menu item prop to a function called getData() to carry out the selected menu item specific logic.
class GeneralSettings extends React.Component {
constructor(props) {
super(props);
this.settingsUrls = [
"/ui/settings/logging"
];
this.state = {
configSettings: {},
formSchema: formSchema
};
this.configSettings = {};
this.slected = "";
}
getData = (url, selectedSetting) => {
debugger;
fetch(url)
.then((response) => {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
response.json().then((response) => {
console.log(selectedSetting)
let newFormSchema = this.setNonDefaultValues(response.data, formSchema.subsections);
Object.assign(this.configSettings, response.data);
this.setState({
configSettings : this.configSettings,
formSchema: newFormSchema
});
});
}
)
.catch((err) => {
console.log('Fetch Error :-S', err);
});
};
componentDidMount() {
this.settingsUrls.map((settingUrl) => {
this.getData(settingUrl,this.props.selectedSetting)
})
}
render() {
return (
<div className="card-wrapper">
<h2>{formSchema.label.toUpperCase()}</h2>
{
formSchema.subsections.map((subSection) => {
return (
<>
<h3>{subSection['description']}</h3>
<div style={{marginBottom: '10px'}}></div>
{
subSection['input_fields'].map((inputField) => {
return buildForm(inputField, this.handleChange)
})
}
<hr></hr>
</>
)
})
}
<button className="button button-primary">Save Changes</button>
</div>
)
}
}
The problem I'm having is that when I select a new menu item in the Settings component and I pass the new selected menu item as a prop to the GeneralSettings component, the ComponentDidMount method doesn't get called again so I can't pass the new selected menu item to the getData() method. the component itself re renders however when a new menu item is clicked. If I was to console.log(this.props.selectedSetting) in the render() method of the GeneralSettings component and click the different menu items, it logs the different selected menu items. If I console.log(this.props.selectedSetting)in the ComponentDidMount method of the GeneralSettings component, it only gets logged once in the initial render and when I click the different menu items after, nothing happens. Only when I refresh the page do I see something logged in componentDidMount
My understanding was that when a component gets re rendered, componentDidMount always gets called but what is happening here is componentDidMount gets called on the initial render when I load the page and then any render after that, componentDidMount doesn't get called and therefore I can't send the new selected menu item to my getData function.
So my question is this. How do I go about sending the new selected menu item to my getData function every time a new menu item gets clicked?
Your component is not remounted because you're changing just the prop, not the component.
So, with class components, the way to call a function on each prop change is with componentDidUpdate lifecycle method (doc). Like so :
componentDidUpdate() {
this.settingsUrls.map(settingUrl => {
this.getData(settingUrl,this.props.selectedSetting)
})
}
Also, note that this method is not called for initial render, so you may want to have your getData in both componentDidMount and componentDidUpdate.
I'm working on an e-commerce and I have a problem with remove products from the cart. I tried to create a function but without success, I honestly don't know what to do to make it work...
The error I see is this:
'TypeError: this.props.data.filter is not a function'
//Shopping.js
class Shopping extends Component{
componentDidMount(){
const products = JSON.parse(localStorage.getItem('products'));
this.setState({products});
}
render(){
const products = JSON.parse(localStorage.getItem('products')) || [];
return(
<div>
<h3>Shopping Cart</h3>
{products.map((product, key) =>
<CartProduct key={key} data={product}/>
)}
</div>
)
}
}
export default Shopping;
//CartProduct.js
class CartProduct extends React.Component{
//this is where the error comes from
removeProduct(data){
const prod = this.props.data.filter(i => i.id !== data.id)
this.setState({prod})
}
render(){
return(
<div>
<img
src={this.props.data.img}
/>
<h4>{this.props.data.name}</h4>
<span>{this.props.data.description}</span>
<h4 >${this.props.data.price}</h4>
<Button
onClick={this.removeProduct.bind(this)}
>Remove</Button>
</div>
)
}
}
export default withRouter(CartProduct);
You have to pass a function that will be triggered by CardProduct component. Let's say onRemove. Implementation of this function will live inside Shopping component as your products state lives there. The only thing that will take place in your CardProduct is invoking of onRemove function with id parameter.
The following code will help:
//Shopping.js
class Shopping extends Component{
componentDidMount(){
const products = JSON.parse(localStorage.getItem('products'));
this.setState({products});
}
removeProduct = (pId) =>{
let products = this.state.products.filter(product => product.id !== pId);
this.setState({products});
}
render(){
const products = JSON.parse(localStorage.getItem('products')) || [];
return(
<div>
<h3>Shopping Cart</h3>
{products.map((product, key) =>
<CartProduct key={key} data={product} removeProduct={this.removeProduct}/>
)}
</div>
)
}
}
export default Shopping;
//CartProduct.js
class CartProduct extends React.Component{
render(){
return(
<div>
<img
src={this.props.data.img}
/>
<h4>{this.props.data.name}</h4>
<span>{this.props.data.description}</span>
<h4 >${this.props.data.price}</h4>
<Button
onClick={this.props.removeProduct(this.props.data.id)}
>Remove</Button>
</div>
)
}
}
export default withRouter(CartProduct);
As your products list is in your parent component Shopping, so you need to keep the removal power to the parent component. Simply pass product ID from the child component to parent component and delete product in the parent.
Let me know if that helps you out.
You are updating state of child component in your code while you have to update the state in parent component .make a function handleOnRemoval in parent component and pass this function as a prop to the child component and implenet the same logic in that function .when function is called in child component it will trigger the handle event of parent component
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>
);
}
}
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