Component is not updating on Button Click, React - javascript

I have a parent component 'Menu' and a child component 'DishDetails'. The child component returns a Card on Button Click. It works for the first time when i click on the button. The component is not updated while i again click on button. The Component should update after clicking on the Button.
Parent Component
import React,{Component} from 'react';
import DishDetails from './DishDetails'
import { Card,CardImg,CardImgOverlay,CardTitle,CardBody,CardText} from 'reactstrap'
class Menu extends Component{
//The class base component must have a constructor with super statment
constructor(props){
super(props);
this.state ={
dishSelection:null
}
}
//Method when the user click on the image
onDish(dish){
this.setState({dishSelection:dish})
}
renderDish(dish){
if(dish!=null){
return(
<DishDetails dish = {dish}></DishDetails>
)
}
else{
return(
<div></div>
)
}
}
//The component should implement the render method
render(){
//Declare a variable for the defining the list
const media = this.props.dishes.map((dish)=>{
return(
<div className='col-12 col-md-5 m-2'>
<Card onClick={()=>this.onDish(dish)} key={dish.id}>
<CardImg src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle>{dish.name}</CardTitle>
</CardImgOverlay>
</Card>
</div>
)
})
return(
<div className='container'>
<div className='row'>
{media}
</div>
<div className='row'>
<div className = 'col-12'>
{this.renderDish(this.state.dishSelection)}
</div>
</div>
</div>
)
}
}
export default Menu;
Child Component
import React , {Component} from 'react'
import {Card,CardImg,CardImgOverlay,CardTitle} from 'reactstrap'
class DishDetails extends Component{
constructor(props){
super(props)
this.state = {
dish : this.props.dish
}
}
render(){
//const dish = this.props.dish;
console.log(this.state.dish.name)
return(
<div>
<div className = 'col-12 col-md-5'>
<Card key={this.state.dish.id}>
<CardImg src={this.state.dish.image} alt={this.state.dish.name} />
<CardImgOverlay>
<CardTitle>{this.state.dish.name}</CardTitle>
</CardImgOverlay>
</Card>
</div>
<div>
</div>
</div>
)
}
}
export default DishDetails;

Constructor fires for the first time only. Don't make copy of props in state if you don't need to change the value inside child component. Change this.state.dish to this.props.dish in child component.
class DishDetails extends Component{
render(){
return(
<div>
<div className = 'col-12 col-md-5'>
<Card key={this.props.dish.id}>
<CardImg src={this.props.dish.image} alt={this.state.dish.name} />
<CardImgOverlay>
<CardTitle>{this.props.dish.name}</CardTitle>
</CardImgOverlay>
</Card>
</div>
<div>
</div>
</div>
)
}
}
export default DishDetails;
Or if you still want to update the state on any change in parent component then you should have a look at this link :-
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

Related

React: Changing prop type value for nested component on page level

I'm fairly new to React and what to understand how I can (if it's possible) change prop values for nested components at page level.
Here's an example of what I mean:
I have a component called Text
I have a component called Image
I have a component called TextImage
In TextImage, I am using both Text and Image components. Both of these nested components have their own props.
Now, I cannot define these prop values in the TextImage component itself, because I may need to use the TextImage component multiple times on my page.
Here is Text.js:
import React from 'react';
class Text extends React.Component{
render() {
const header = this.props.header;
const copy = this.props.copy;
return(
<section className="text">
<h2 className="text__header">{ header }</h2>
<div className="text__copy">{ copy }</div>
</section>
)
}
}
export default Text;
Here is Image.js:
import React from 'react';
class Image extends React.Component{
render() {
const image_src = this.props.image_src;
const image_alt = this.props.image_alt;
return(
<section className="image">
<img loading="lazy" src={ image_src } alt={ image_alt } />
</section>
)
}
}
export default Image;
And here is TextImage.js (in its current form):
import React from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import Text from '../Text/Text';
import Image from '../Image/Image';
import "./TextImage.scss";
class TextImage extends React.Component{
render(){
return(
<section className="textImage">
<Container>
<Row>
<Col md={6}>
<Text />
</Col>
<Col md={6}>
<Image/>
</Col>
</Row>
</Container>
</section>
)
}
}
export default TextImage;
I cannot do something like the below, because then those props will be consistent for whenever I use the TextImage component.
<section className="textImage">
<Container>
<Row>
<Col md={6}>
<Text header="This is header" copy="this is copy" />
</Col>
<Col md={6}>
<Image image_src="" image_alt="image" />
</Col>
</Row>
</Container>
</section>
In my Homepage.js file, I have tried to do something like this:
<TextImage header="test" /> which I kind of knew beforehand wouldn't work as the props are assigned to the Text and Image components within TextImage.
I can easily output my TextImage markup in Homepage.js (like below), but I'm looking for a cleaner approach.
import React from "react";
import Text from "../components/Text/Text";
import Image from "../components/Image/Image";
function Homepage() {
return (
<>
<section className="textImage">
<Container>
<Row>
<Col md={6}>
<Text header="This is header" copy="this is copy" />
</Col>
<Col md={6}>
<Image image_src="" image_alt="image" />
</Col>
</Row>
</Container>
</section>
</>
);
}
export default Homepage;
You just need to pass the props which are used in Text and Image to TextImage component as below.
Demo at : https://codesandbox.io/s/gallant-golick-zkmdi?file=/src/App.js
import "./styles.css";
import React from "react";
class Text extends React.Component {
render() {
const header = this.props.header;
const copy = this.props.copy;
return (
<section className="text">
<h2 className="text__header">{header}</h2>
<div className="text__copy">{copy}</div>
</section>
);
}
}
class Image extends React.Component {
render() {
const image_src = this.props.image_src;
const image_alt = this.props.image_alt;
return (
<section className="image">
<img loading="lazy" src={image_src} alt={image_alt} />
</section>
);
}
}
class TextImage extends React.Component {
render() {
const image_src = this.props.image_src;
const image_alt = this.props.image_alt;
const header = this.props.header;
const copy = this.props.copy;
return (
<section className="textImage">
<div>
<div>
<div md={6}>
<Text header={header} copy={copy} />
</div>
<div md={6}>
<Image image_alt={image_alt} image_src={image_src} />
</div>
</div>
</div>
</section>
);
}
}
export default function App() {
return (
<div className="App">
<TextImage header='Random Header' copy='RandomCopy' image_src='https://picsum.photos/200/300' image_alt='randomImage' />
<TextImage header='Random Header2' copy='RandomCopy2' image_src='https://picsum.photos/300/300' image_alt='randomImage2' />
</div>
);
}

passing the props from menucomponent to dishdetails

Im new to react.js and have been facing this issue.Im working on this app dishes where the user clicks one dish and then it is rendered in form a card.The value of dish is passed as a props from menucomponent to DishDetail but in doing so the dish object is always receiving null value for which there is nothing on the screen . please help me with it.im attaching both MenuComponents.js and DishDetail.js
the MenuComponent.js is:
import React, { Component } from 'react';
// import { Media } from 'reactstrap';
import { Card, CardImg, CardImgOverlay, CardText, CardBody,CardTitle } from 'reactstrap';
import DishDetail from './DishdetailComponent';
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
selectedDish: null
};
}
onDishSelect(dish) {
this.setState({ selectedDish: dish});
}
render() {
const menu = this.props.dishes.map((dish) => {
return(
<div key={dish.id} className="col-12 col-md-5 m-1">
{/* <Media tag="li">
<Media left middle>
<Media object src={dish.image} alt={dish.name} />
</Media>
<Media body className="ml-5">
<Media heading>{dish.name}</Media>
<p>{dish.description}</p>
</Media>
</Media> */}
<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">
{/* <Media list>
{menu}
</Media> */}
{menu}
</div>
<DishDetail dish={this.state.selectedDish} />
</div>
);
}
}
export default Menu;
the DishDetail.js is:
import React, { Component } from 'react';
import { Card, CardImg, CardImgOverlay, CardText, CardBody,CardTitle } from 'reactstrap';
class DishDetail extends Component{
constructor(props){
super(props);
}
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() {
const dish = this.props.selectedDish;
console.log(dish);
return(
<div className="row">
<div className="col-12 col-md-5 m-1">
{this.renderDish(this.props.selectedDish)}
</div>
<div className="col-12 col-md-5 m-1">
</div>
</div>
);
}
}
export default DishDetail;
You're passing a property named dish to your component but using selectedDish.
<DishDetail dish={this.state.selectedDish} />
const dish = this.props.selectedDish;
The prop passed to the DishDetail component is called dish:
// MenuComponent.js
<DishDetail dish={this.state.selectedDish} />
However DishDetail is expecting a prop with the name selectedDish:
// DishDetail.js
const dish = this.props.selectedDish;
{this.renderDish(this.props.selectedDish)}
Updating DishDetail to use this.props.dish will resolve your issue:
const dish = this.props.dish;
{this.renderDish(this.props.dish)}

How to make a button click another button from different components in React

I'm new to react and it is kinda hard to understand the one way data flow on it, i was making a simple app and i'm using mdbootstrap for some ready bootstrap components, I imported the component of a modal (which has a button when clicked it toggles a modal) so in my app i have cards, each one has a button that's supposed to toggle the button, but i couldn't figure out how to link the card's button with the mdbootstrap component's button.
The Card component:
import React, { Component } from 'react';
import ModalPage from './modal.jsx'
class Card extends Component {
render() {
return (
<div>
<div className="card m-5" style={{ width: '18rem' }}>
<img src={this.props.img} className="card-img-top" />
<div className="card-body">
<h5 className="card-title">{this.props.title}</h5>
<p className="card-text">{this.props.desc}</p>
<button onClick={/*I don't know what exactly i should put here */}></button>
</div>
</div>
</div>
)
}
}
export default Card;
The modal componant:
import React, { Component } from 'react';
import { MDBContainer, MDBBtn, MDBModal, MDBModalBody, MDBModalHeader, MDBModalFooter } from 'mdbreact';
class ModalPage extends Component {
state = {
modal13: false
}
toggle = nr => () => {
let modalNumber = 'modal' + nr
this.setState({
[modalNumber]: !this.state[modalNumber]
});
}
render() {
return (
<MDBContainer>
{/* This is the button I want to click when clicking the card's button */}
<MDBBtn color="primary" onClick={this.toggle(13)}>
Modal
</MDBBtn>
<MDBModal isOpen={this.state.modal13} toggle={this.toggle(13)}>
<MDBModalHeader toggle={this.toggle(13)}>
{this.props.title}
</MDBModalHeader>
<MDBModalBody>
{/* edit here */}
{this.props.content}
</MDBModalBody>
<MDBModalFooter>
<MDBBtn color="secondary" onClick={this.toggle(13)}>
Close
</MDBBtn>
<MDBBtn color="primary">Save changes</MDBBtn>
</MDBModalFooter>
</MDBModal>
</MDBContainer>
);
}
}
export default ModalPage;
Rather than having 2 click events you only need one on the child component. Instead of trying to send a click to the parent button in order to call toggle() just pass the toggle function to the child to be called:
Card:
import React, { Component } from 'react';
import ModalPage from './modal.jsx'
class Card extends Component {
render() {
return (
<div>
<div className="card m-5" style={{ width: '18rem' }}>
<img src={this.props.img} className="card-img-top" />
<div className="card-body">
<h5 className="card-title">{this.props.title}</h5>
<p className="card-text">{this.props.desc}</p>
//*****************************************
<button onClick={this.props.click}></button>
//*****************************************
</div>
</div>
</div>
)
}
}
export default Card;
Modal:
import React, { Component } from 'react';
import { MDBContainer, MDBBtn, MDBModal, MDBModalBody, MDBModalHeader, MDBModalFooter } from 'mdbreact';
class ModalPage extends Component {
state = {
modal13: false
}
toggle = nr => () => {
let modalNumber = 'modal' + nr
this.setState({
[modalNumber]: !this.state[modalNumber]
});
}
render() {
return (
<MDBContainer>
{/* I am assuming that this is a reference to <Card /> - simply pass in the onClick function as a parameter. You can even use onClick here and this.props.onClick in the child element */}
<MDBBtn color="primary" click={this.toggle(13)}>
Modal
</MDBBtn>
<MDBModal isOpen={this.state.modal13} toggle={this.toggle(13)}>
<MDBModalHeader toggle={this.toggle(13)}>
{this.props.title}
</MDBModalHeader>
<MDBModalBody>
{/* edit here */}
{this.props.content}
</MDBModalBody>
<MDBModalFooter>
<MDBBtn color="secondary" onClick={this.toggle(13)}>
Close
</MDBBtn>
<MDBBtn color="primary">Save changes</MDBBtn>
</MDBModalFooter>
</MDBModal>
</MDBContainer>
);
}
}
export default ModalPage;

Dynamically expand/collapse on click of header

I have a set of items that needs to be shown in the UI, like a header and list of items under it. There is a parent component where I am passing this data to a file that is shown below. Based on this the parent-child layout is shown. Now I need to expand/collapse based on the click of the header.
There is a class "open" and "close " that can be attached to the div. Based on it the it gets collapse/expands. The point is how do it item wise
Can someone help
import React from "react";
import Child from "./Child";
import Parent from "./Parent";
export default class Helper extends React.Component{
constructor(props: any) {
super(props);
this.state = {
parent:{},
children:{},
};
}
componentDidMount() {
this.setParentValue();
this.setChildValue();
}
render() {
const { parent, children } = this.state;
const { name } = this.props;
return (
<>
<div className="an-panel expand-panel expand-close">
<div className="an-panel-header">
<div className="title-holder">
<span className="toggle-icon far fa-plus-square" />
<span className="toggle-icon far fa-minus-square" />
<h5>{name}</h5>
</div>
<div className="action-holder">
<div className="status-holder">
<Parent
parent = {parent}
onSelect={this.handleParentClick}
/>
</div>
</div>
</div>
{children.map(({ id, name },id) => (
<div className="an-panel-body" key={id}>
<ul className="applications-list-holder">
<li>
<div className="name">{name}</div>
<div className="status">
<Child
children={children}
onSelect={this.setChildSwitchValue}
/>
</div>
</li>
</ul>
</div>
))}
</div>
</>
);
}
}
Ok let me explain it to you, here is your code
import React from "react";
import Child from "./Child";
import Parent from "./Parent";
export default class Helper extends React.Component{
constructor(props: any) {
super(props);
this.state = {
parent:{},
children:{},
navBarStatus: false,
};
}
componentDidMount() {
this.setParentValue();
this.setChildValue();
}
changeNavBar = (e, status)=>{
this.setState({navBarStatus: !status});
}
render() {
const { parent, children } = this.state;
const { name } = this.props;
return (
<>
<div className={`an-panel expand-panel ${this.state.navBarStatus ? "expand-open" : "expand-close"}`}>
<div className="an-panel-header" onClick={(e)=>this.changeNavBar(e, this.state.navBarStatus)}>
<div className="title-holder">
<span className="toggle-icon far fa-plus-square" />
<span className="toggle-icon far fa-minus-square" />
<h5>{name}</h5>
</div>
<div className="action-holder">
<div className="status-holder">
<Parent
parent = {parent}
onSelect={this.handleParentClick}
/>
</div>
</div>
</div>
{children.map(({ id, name },id) => (
<div className="an-panel-body" key={id}>
<ul className="applications-list-holder">
<li>
<div className="name">{name}</div>
<div className="status">
<ChildSetting
children={children}
onSelect={this.setChildSwitchValue}
/>
</div>
</li>
</ul>
</div>
))}
</div>
</>
);
}
}
You can see I have taken a new property in state navBarStatus. Based on navBarStatus value I am changing CSS class which will expand/close your attached div

React - How to show component from an image onClick event in another component?

I have 3 React components:
-Home - need to display the component here when image is
clicked from the Header component
-Header- Contains the image tag that will be clicked to
show the AllSites Component.
-AllSites - component that needs displayed in Home component when Image is
clicked in the Header component.
Header
export class Header extends React.Component<any, any> {
private readonly searchServerUrl = "";
private appButtonElement: HTMLElement;
constructor(props: any) {
super(props);
this.state = { showAppMenu: false };
}
render() {
const { showAppMenu } = this.state;
const { className, navItems, singleColumn, appItems } = this.props;
return (
<header className={className}>
<div className="app-icon">
<button className="nav-button" onClick={() => this.toggleAppMenu()} ref={(menuButton: any) => this.appButtonElement = menuButton}><i className="ms-Icon ms-Icon--Waffle" aria-hidden="true"></i></button>
</div>
***When image is clicked, show the <AllSites/> component in the HomeComponent below.***
<img src="/Styles/Images/logo/loop-logo-white.png" className="nav-logo" onClick={} />
{showAppMenu ? <ApplicationMenu navItems={appItems} targetElement={this.appButtonElement} onDismiss={() => this.onDismiss()} /> : null}
<div className="nav-container"><TopNavigation classNam
e={className} navItems={navItems} singleColumn={singleColumn} /></div>
<div className="search-container">
<SearchBox onSearch={(searchTerm: string) => this.executeSearch(searchTerm)} />
</div>
</header>
);
}
Home
export class HomeComponent extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = { navItems: [], appItems: [], singleColumnLayout: false, showAllSites: false };
}
componentDidMount() {
this.checkWidth();
window.addEventListener("resize", this.checkWidth.bind(this));
this.fetchNavigation()
.then(nav => this.setState({ navItems: nav }));
this.fetchAppIcons()
.then(icons => this.setState({ appItems: icons }));
}
componentWillUnmount(): void {
window.addEventListener("resize", this.checkWidth.bind(this));
}
render() {
const { navItems, appItems, singleColumnLayout } = this.state;
return (
<Fabric>
<Header navItems={navItems} appItems={appItems} singleColumn={singleColumnLayout} />
<div className="main-container">
<AlertBar />
<div className="main-content">
<div className="ms-Grid">
When the image tag is clicked, I need to render the <AllSites/> component here
<Hero singleColumn={singleColumnLayout} />
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm12 ms-xl4 webpart-container">
<UpcomingEvents />
</div>
<div className="ms-Grid-col ms-sm12 ms-xl4 webpart-container">
<EmployeeNews />
</div>
<div className="ms-Grid-col ms-sm12 ms-xl4 webpart-container">
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm12 webpart-container">
<IndustryNews />
</div>
<div className="ms-Grid-col ms-sm12 webpart-container">
<Quote />
</div>
</div>
</div>
</div>
</div>
<Footer navItems={navItems} />
</div>
</div>
</Fabric>
);
}
In the simplest approach you will need a common parent component for your Home and Header that will hold some shared state for them and that will pass a callback to update this state as a prop to Header. In the shared state you need a flag that will be responsible for showing/hiding AllSites component, this flag you will pass as a prop to Home.
You can see a basic example here
If you need a more advanced state management solution, you can check redux library

Categories

Resources