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
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'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
For My Class We Are Making A Website With React And Neither Me Or my Group Can Figure Out How To Just Render A Function In A Variable State And Make It Dynamic
My Code Is As Follows:
class App extends React.Component {
constructor(props)
{
super(props)
this.state = {
screen: this.home(),
movies: []
}
}
home = () =>{
this.state.movies.map((movie)=>{
return(
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
)
})
}
render(){
return(
<div>{this.state.screen}</div>
)
}
}
When I Run This The Error Reads
TypeError: Cannot read property 'movies' of undefined
You Can Assume That The Variable in State Movies Is Filled With An Array Of Movies Set By An API
Edit: The End Result I'm Attempting To Achieve Is To Return A Variable Or State Which Can Hold A Function Which Would Be The Different Screens/Pages To Be Rendered
If your movies array filled with data from any API call, then you can directly use that array to render the data,
class App extends React.Component {
constructor(props)
{
super(props)
this.state = {
movies: []
}
}
render(){
return(
<div>
{
this.state.movies.map((movie)=>{
return(
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
)
})
}
</div>
)
}
}
The root cause here is that this.state is not initialized when you're using it the home() invocation in the constructor.
Either way, you're not supposed to store rendered content within state.
Based on the comment, here's a refactoring, but I would recommend looking into an actual router like react-router instead.
const HomeView = ({ movies }) =>
movies.map(movie => (
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
));
const FavoritesView = ({ movies }) => <>something else...</>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
view: "home",
};
}
render() {
let view = null;
switch (this.state.view) {
case "home":
view = <HomeView movies={this.state.movies} />;
break;
case "favorites":
view = <FavoritesView movies={this.state.movies} />;
break;
}
return (
<div>
<a href="#" onClick={() => this.setState({ view: "home" })}>
Home
</a>
<a href="#" onClick={() => this.setState({ view: "favorites" })}>
Favorites
</a>
{view}
</div>
);
}
}
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>
);
}
}
Basically new to React, I'm a bit confused on how to properly pass states between components. I found a similar question already React – the right way to pass form element state to sibling/parent elements?
but I wonder if you can give me a specific answer for the code below.
Currently the structure of the app includes:
parent component - App
2 childs: SearchBar and RecipesList
The goal is to make an async search on my Meteor collection and display only the recipes that match the search term.
Right now, I'm just showing all the recipes in my Meteor collection.
I've created a stateful component named SearchBar which holds the input value as this.state.term. The idea is to pass the state to RecipesList but I'm not sure if it's the right thing to do. Alternatively I'd let App deal with the state and passing it to the childs. I believe this is a very common scenario, how do you do it?
App
class App extends Component {
render( ) {
return (
<div>
<Navbar/>
<SearchBar/>
<RecipesList/>
</div>
);
}
}
SearchBar
export default class SearchBar extends Component {
constructor( props ) {
super( props );
this.state = {
term: ''
};
}
onInputChange( term ) {
this.setState({ term });
}
render( ) {
return (
<div className=" container-fluid search-bar">
<input value={this.state.term} onChange={event => this.onInputChange(event.target.value.substr( 0, 50 ))}/>
Value: {this.state.term}
</div>
);
}
}
RecipesList
const PER_CLICK = 5;
class RecipesList extends Component {
componentWillMount( ) {
this.click = 1;
}
handleButtonClick( ) {
Meteor.subscribe('recipes', PER_CLICK * ( this.click + 1 ));
this.click++;
}
renderList( ) {
return this.props.recipes.map(recipe => {
return (
<div key={recipe._id} className="thumbnail">
<img src={recipe.image} alt="recipes snapshot"/>
<div className="caption">
<h2 className="text-center">{recipe.recipeName}</h2>
</div>
</div>
);
});
}
render( ) {
return (
<ul className="list-group">
{this.renderList( )}
<div className="container-fluid">
<button onClick={this.handleButtonClick.bind( this )} className="btn btn-default">More</button>
</div>
</ul>
);
}
}
// Create Container and subscribe to `recipes` collection
export default createContainer( ( ) => {
Meteor.subscribe( 'recipes', PER_CLICK );
return {recipes: Recipes.find({ }).fetch( )};
}, RecipesList );
App
class App extends Component {
constructor(props, ctx){
super(props, ctx)
this.state = {
searchQuery: ''
}
this.searchInputChange = this.searchInputChange.bind(this)
}
searchInputChange(event) {
this.setState({
searchQuery: event.target.value.substr( 0, 50 )
})
}
render( ) {
const { searchQuery } = this.state
return (
<div>
<Navbar/>
<SearchBar onChange={this.searchInputChange} value={searchQuery}/>
<RecipesList searchQuery={searchQuery}/>
</div>
)
}
}
The App component takes care of the state and this is then passed down to the children as props the seach term is available to RecipesList through props.searchQuery.
The searchInputChange handler is passed down to the SearchBar as props.
SearchBar
export default const SearchBar = ({value, onChange}) => (
<div className=" container-fluid search-bar">
<input value={value} onChange={onChange}/>
Value: {value}
</div>
)
Since the SearchBar delegated state to the parent component, we can use a stateless react component as we only need information from the props to render it.
In general it is always best to have a logical or stateful or controller component take care of state and the logic, this component then passes down state and methods to presentational or view components which take care of what the user sees and interacts with.
Define the state term up in to the App component.
Also write the handleInput function and pass it to the SearchBar component as porps
handleInput(val) {
this.setState({
term: val,
});
}
When something in the search bar is typed(onKeyUp) add the listener handleInput.
Also create <RecipesList searchQuery={this.state.term}/>
now in the render function RecipesList filter out the recipes you want to display from your list