I am starting to learn react so I am still grasping some concepts, now I am doing something where I need to update/render a component after one in selected in the parent one, I've read about lifting up the state, but the stuff I've done it does not work, and I have no idea what I might be doing wrong.
The first component Parent:
constructor(props) {
super(props);
this.state = { selectedDish: null, }
this.onDishSelect = this.onDishSelect.bind(this);
}
onDishSelect(selectedDish) {
this.setState({ selectedDish });
}
render() {
const menu = this.props.dishes.map((dish) => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
onClick={() => this.onDishSelect(dish)}>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle>{dish.name}</CardTitle>
</CardImgOverlay>
</Card>
</div>
);
});
return (
<div className="container">
<div className="row">
{menu}
</div>
<DishDetail onClick={this.onDishSelect} />
</div>
);
}
}
export default Menu;
and the child
export default class DishDetail extends Component {
constructor(props) {
super(props);
this.state = { selectedDish: null }
this.renderDish = this.renderDish.bind(this);
}
renderDish(dish) {
if (dish != null)
return (
<Card>
<CardImg top src={dish.image} alt={dish.name} />
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
);
else
return (
<div></div>
);
}
render() {
return (
<div className="row">
<div className="col-12 col-md-5 m-1">
{this.renderDish(this.props.selectedDish)}
</div>
</div>
)
}
}
When I run it I am able to see the cards of the menu, the idea is that when I click a component from the menu I can see the details in another card of the one cliked, but it is not updated, the detail component is not shown/displayed/rendered
Any Idea what I am doing wrong or missing, thanks in advance.
Your wrong is where you passed onClick to DishDetail component you should pass selectedDish to DishDetail
parent component
constructor(props) {
super(props);
this.state = { selectedDish: null, }
this.onDishSelect = this.onDishSelect.bind(this);
}
onDishSelect(selectedDish) {
this.setState({ selectedDish });
}
render() {
const menu = this.props.dishes.map((dish) => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
onClick={() => this.onDishSelect(dish)}>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle>{dish.name}</CardTitle>
</CardImgOverlay>
</Card>
</div>
);
});
return (
<div className="container">
<div className="row">
{menu}
</div>
<DishDetail selectedDish={this.state.selectedDish} /> //add selectedDish here
</div>
);
}
}
DishDetail should accept a prop called Dish and that is it. It shouldn't have any state.
// parent render function
const { selectedDish } = this.state;
return (
<div className="container">
<div className="row">
{menu}
</div>
{selectedDish && <DishDetail dish={selectedDish} />}
</div>
);
// DishDetail
class DishDetail extends Component {
render() {
return (
<div className="row">
<div className="col-12 col-md-5 m-1">
{/* some jsx with this.props.dish */}
</div>
</div>
)
}
}
To keep things simple, you could even make DishDetail a function instead of a class:
export const DishDetail = ({dish}) => (
<div className="row">
<div className="col-12 col-md-5 m-1">
{/* some jsx with dish */}
</div>
</div>
);
Related
I am stumped. I have been trying to get a simple cleartext button to work. I have tried all the option available on this platform but nothing is working for me. React Hook useCallback cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook. I have no idea why this error is occurring. I am pretty new to react can anyone please help? I am trying to clear the text on a click of the clear button.
import React, { Component, useCallback, useState } from "react";
import {
Button,
Input,
Footer,
Card,
CardBody,
CardImage,
CardTitle,
CardText
} from "mdbreact";
import blankImg from "./blank.gif";
import "./style.css";
import "./flags.min.css";
import countriesList from "./countries.json";
class App extends Component {
state = {
search: ""
};
renderCountry = country => {
const { search } = this.state;
var code = country.code.toLowerCase();
const handleClick = useCallback(() => {
e.target.value = '';
}, []);
return (
<div className="col-md-3" style={{ marginTop: "20px" }}>
<Card>
<CardBody>
<p className="">
<img
src={blankImg}
className={"flag flag-" + code}
alt={country.name}
/>
</p>
<CardTitle title={country.name}>
{country.name.substring(0, 15)}
{country.name.length > 15 && "..."}
</CardTitle>
</CardBody>
</Card>
</div>
);
};
onchange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const filteredCountries = countriesList.filter(country => {
return country.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
return (
<div className="flyout">
<main style={{ marginTop: "4rem" }}>
<div className="container">
<div className="row">
<div className="col">
<Input
label="Search Country"
icon="search"
onChange={this.onchange}
/>
<button onClick={handleClick}> Click to clear</button>
</div>
<div className="col" />
</div>
<div className="row">
{filteredCountries.map(country => {
return this.renderCountry(country);
})}
</div>
</div>
</main>
<Footer color="indigo">
<p className="footer-copyright mb-0">
© {new Date().getFullYear()} Copyright
</p>
</Footer>
</div>
);
}
}
export default App;
It is because you try to use hooks in a class based component. You can only use hooks like useCallback in functional components. Therefore you are mixing the concepts of object oriented and functional programming.
The following should do the trick:
import React, { Component, useCallback, useState } from "react";
import {
Button,
Input,
Footer,
Card,
CardBody,
CardImage,
CardTitle,
CardText
} from "mdbreact";
import blankImg from "./blank.gif";
import "./style.css";
import "./flags.min.css";
import countriesList from "./countries.json";
class App extends Component {
state = {
search: ""
};
const handleClick = (e) => {
e.target.value = '';
};
renderCountry = country => {
const { search } = this.state;
var code = country.code.toLowerCase();
return (
<div className="col-md-3" style={{ marginTop: "20px" }}>
<Card>
<CardBody>
<p className="">
<img
src={blankImg}
className={"flag flag-" + code}
alt={country.name}
/>
</p>
<CardTitle title={country.name}>
{country.name.substring(0, 15)}
{country.name.length > 15 && "..."}
</CardTitle>
</CardBody>
</Card>
</div>
);
};
onchange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const filteredCountries = countriesList.filter(country => {
return country.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
return (
<div className="flyout">
<main style={{ marginTop: "4rem" }}>
<div className="container">
<div className="row">
<div className="col">
<Input
label="Search Country"
icon="search"
onChange={this.onchange}
/>
<button onClick={this.handleClick}> Click to clear</button>
</div>
<div className="col" />
</div>
<div className="row">
{filteredCountries.map(country => {
return this.renderCountry(country);
})}
</div>
</div>
</main>
<Footer color="indigo">
<p className="footer-copyright mb-0">
© {new Date().getFullYear()} Copyright
</p>
</Footer>
</div>
);
}
}
export default App;
useCallback hooks can only be used in functional components OR in a custom Hook NOT in class components.
You have declared your App component in class based approach i.e. Object oriented approach. To use useCallback hook for this, you should change your App component declaration to a functional component or Custom hook.
Something like this
function App() {
return (
<>Hello World!</>
);
}
you cannot useCallBack hook in class Component,we can do a simple thing to clear the button text, by using
a button text state, i.e. { btnTxt: "Clear ButtonText"} than update the state onclick
import React, { Component, useCallback, useState } from "react";
import {
Button,
Input,
Footer,
Card,
CardBody,
CardImage,
CardTitle,
CardText
} from "mdbreact";
import blankImg from "./blank.gif";
import "./style.css";
import "./flags.min.css";
import countriesList from "./countries.json";
class App extends Component {
state = {
search: "",
btnTxt: "Clear ButtonText"
};
renderCountry = country => {
const { search } = this.state;
var code = country.code.toLowerCase();
return (
<div className="col-md-3" style={{ marginTop: "20px" }}>
<Card>
<CardBody>
<p className="">
<img
src={blankImg}
className={"flag flag-" + code}
alt={country.name}
/>
</p>
<CardTitle title={country.name}>
{country.name.substring(0, 15)}
{country.name.length > 15 && "..."}
</CardTitle>
</CardBody>
</Card>
</div>
);
};
onchange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const filteredCountries = countriesList.filter(country => {
return country.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
return (
<div className="flyout">
<main style={{ marginTop: "4rem" }}>
<div className="container">
<div className="row">
<div className="col">
<Input
label="Search Country"
icon="search"
onChange={this.onchange}
/>
<button onClick={()=> this.setState({btnText:""})}> {this.state.btnTExt}</button>
</div>
<div className="col" />
</div>
<div className="row">
{filteredCountries.map(country => {
return this.renderCountry(country);
})}
</div>
</div>
</main>
<Footer color="indigo">
<p className="footer-copyright mb-0">
© {new Date().getFullYear()} Copyright
</p>
</Footer>
</div>
);
}
}
export default App;
Instead of% username%, I would like the current name and name to be displayed in the modal title
user's surname. I can't make this task. I
after solving the problem, give ideas how I can optimize my code.
Code:
class App extends React.Component {
state= {
show: false,
};
showModal = () =>{
this.setState({
show: !this.state.show
});
};
render() {
const users = this.props.data.users;
const userList = users.map(user => <User key={user.id} user={user} onOpen={this.showModal} name={user.name} surname={user.surname}/>)
return (
<div className="container">
{userList}
{this.state.show ? < Modal onClose={this.showModal} show={this.state.show}/> : null}
</div>
)
}
}
class User extends React.Component{
onOpen = () => {
this.props.onOpen && this.props.onOpen();
};
render(){
const {avatar, name, surname, city, country} = this.props.user;
return(
<div className="box">
<img src={avatar} alt="" className="avatar" />
<h3 className="box-title">{`${name} ${surname}`}</h3>
<p className="box-description">{`${city}, ${country}`}</p>
<div className="button-wrap">
<a href="#" className="button" onClick={()=> this.onOpen()} >Report user</a>
</div>
</div>
)
}
}
class Modal extends React.Component {
onClose = () => {
this.props.onClose && this.props.onClose();
};
render() {
if(!this.props.show){
return null;
}
// tak wygląda struktura HTML dla modal boxa
return (
<div className="modal">
<div className="modal-background"></div>
<div className="modal-content">
<div className="box">
<h3 className="modal-title">Report user</h3>
<textarea rows="6"></textarea>
<div className="button-wrap">
<a href="#" className="button button-link" onClick={() => {
this.onClose()}}>Cancel</a>
<a href="#" className="button ml-auto" onClick={()=> alert("ok")}>Report</a>
</div>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<App data={data} />, document.querySelector("#app"))
using state ReportUsr to store the user you want to report changed by function this.ReportUsr in App class then pass function as prop Report to User class to call it OnClick with the value surname for that instance of User component
then Modal component created from App class has CONTENT which is the App.state.ReportUsr
< Modal onClose={this.showModal} show={this.state.show} >{this.state.ReportUsr}</ Modal>
LiveExample
https://h4i1e.csb.app/
Code https://codesandbox.io/s/modern-browser-h4i1e
My component is being rendered in a single column equal to the photo I attached, but it is wrong, it was to be a 5x4 array.
edi1: In an old version of the code I did not have this problem, however I received some props, and since I have to constantly change the contents of the Component, I thought it was good to use state.
render() {
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={this.state.searchString} />}
</div>
</div>
</div>
);
}
}
File RecipeItem.js
const RecipeList = ({ searchString }) => {
return(
<div>
<img className="card-img-top img-fluid" src={searchString.thumbnail} alt={searchString.title} />
<div className="card-body">
<h5 className="card-title">{searchString.title}</h5>
<p className="card-text">
<strong>Ingredients: </strong>{searchString.ingredients}
</p>
</div>
</div>
)
}
const RecipeItem = (props) => {
return (
<div className="col-sm-3 mt-4">
<div className="card">
{props.list && props.list.map((searchString, index) =>
<RecipeList searchString={searchString} key={index} />
)}
</div>
</div>
)
}
You're applying col-sm-3 before iterating on each element, you should apply the class on each iteration like this :
const RecipeItem = (props) => {
return (
props.list && props.list.map((searchString, index) =>
<div className="card col-sm-3 mt-4">
<RecipeList searchString={searchString} key={index} />
</div>
)
)
}
I have to render a component from an .json file, until then okay, to be able to read and pass the api values to my component ('RecipeItem'). The problem lies in the part of rendering, because the correct one would be the components being in 5 columns instead of only one.
enter image description here
updated codes below !!!
File RecipeItem.js
const RecipeList = ({ searchString }) => {
return(
<div>
{console.log('to aqui')}
<img className="card-img-top img-fluid" src={searchString.thumbnail} alt={searchString.title} />
<div className="card-body">
<h5 className="card-title">{searchString.title}</h5>
<p className="card-text">
<strong>Ingredients: </strong>{searchString.ingredients}
</p>
</div>
</div>
)
}
const RecipeItem = (props) => {
return (
<div className="col-sm-3 mt-4">
<div className="card">
{props.list && props.list.map((searchString, index) =>
<RecipeList searchString={searchString} key={index} />
)}
</div>
</div>
)
}
File App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchString: []
};
}
componentDidMount() {
this.setState({ searchString : data.results })
}
render() {
return (
<div className="App">
<Navbar />
<div className="container mt-10">
<div className="row">
<RecipeItem list={this.state.searchString}/>
</div>
</div>
</div>
);
}
}
Is this working ?
class App extends Component {
render() {
return (
<div className="App">
<Navbar />
<div className="container mt-10">
<div className="row">
{RecipesData.results.map(recipe =>
<RecipeItem
title={recipe.title}
ingredients={recipe.ingredients}
source={recipe.href}
thumbnail={recipe.thumbnail} />
)}
</div>
</div>
</div>
);
}
}
I am kinda new to React, what I am trying to do is this, I have a parent component that carries the state, and two children that are considered to be the so-called presentational components lets say (X,Y).
So, Component X receives an array of Items as a prop from the parent and render it, Now, the event should be fired on one of the Items in the X component, then the onClick function will be fired, returning the Item.id to the parent, the parent then will filter the items in the state and pass the selected Item to component Y, Now I want Y to be rendered if and only if it successfully received the selected Item from the parent, here is my code.
// PARENT COMPONENT
class Main extends Component {
constructor(props) {
super(props);
this.state = {
dishes: DISHES,
selectedDish: null
}
}
onDishSelect(dishId){
this.setState({ selectedDish : dishId })
}
render() {
return (
<div >
<Navbar dark color="secondary" >
<div className="container">
<NavbarBrand href="/">My Brand</NavbarBrand>
</div>
</Navbar>
<Menu dishes={this.state.dishes}
onClick={(dishId) => this.onDishSelect(dishId)} />
<DishDetails dish={this.state.dishes.filter(dish => dish.id === this.state.selectedDish)[0]} />
</div>
);
}
}
Component X
render() {
const menu = this.props.dishes.map(item => {
return (
<div key={item.id} className="col-12 col-md-5 offset-md-1 mt-3">
<Card onClick={() => this.props.onClick(item.id)}>
<CardImg width="100%" src={item.image} alt={item.name} />
<CardImgOverlay className="ml-5">
<CardTitle >{item.name}</CardTitle>
</CardImgOverlay>
</Card>
</div>
)
});
return (
<div className="container">
<div className="row">
{ menu }
</div>
</div>
);
}
Finally component Y
class DishDetails extends Component {
constructor(props) {
super(props)
}
renderCard(dish) {
if(this.props.dish){
return (
<div className="col-12 col-md-5 offset-md-1 mt-5 ">
<Card>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardBody>
<CardTitle>{ dish.name }</CardTitle>
<CardText>{ dish.description }</CardText>
</CardBody>
</Card>
</div>
)
} else {
return(<div></div>)
}
}
renderViews(review){
if(this.props.dish){
return(
<div key={review.id} className="col-12 col-md-5 mt-5">
<Media body className="mt-2">
<p>{review.comment}</p>
<p>{ review.author }, {new Intl.DateTimeFormat('en-US',{year: 'numeric', month:'short', day: '2-digit'}).format(new Date(Date.parse(review.date)))}</p>
</Media>
</div>
)
} else {
return(<div></div>)
}
}
render(){
return(
<div className="row">
{ this.renderCard(this.props.dish) }
{ this.renderViews(this.props.dish.comments) }
</div>
)
}
}
I know the code structure is very messed up, I am just more concerned abou the functionality for now, thanks in advance.
You can find documentation about conditional rendering at https://reactjs.org/docs/conditional-rendering.html
This applies to your code like this
// PARENT COMPONENT
class Main extends Component {
constructor(props) {
super(props);
this.state = {
dishes: DISHES,
selectedDish: null
}
}
onDishSelect(dishId){
this.setState({ selectedDish : dishId })
}
render() {
return (
<div >
<Navbar dark color="secondary" >
<div className="container">
<NavbarBrand href="/">My Brand</NavbarBrand>
</div>
</Navbar>
<Menu dishes={this.state.dishes}
onClick={(dishId) => this.onDishSelect(dishId)} />
{this.state.selectedDish !== null &&
<DishDetails dish={this.state.dishes.filter(dish => dish.id === this.state.selectedDish)[0]} />
}
</div>
);
}
}
In this way, DishDetails is evaluated only when this.state.selectedDish is different from null, e.g. when the event from X has been processed