Console Error: Objects are not valid as a React child - javascript

I'm building a movie react app, basically Netflix, and I'm using bootstrap for most of my UI. I've managed to create a quick model of a login form that for now accepts any key and takes you to the movie list.
I've done my research but most of the question doesn't solve the issue or is unique to them.
Now here is where the problem starts: when I click on the movie card I get an error:
Objects are not valid as a React child (found: object with keys {Name,
Description}). If you meant to render a collection of children, use an
array instead.
Expected Output: Be able to load my movie once I click on the movie card
Question: How do I go about solving this issue?
Where I think the problem may be:
In the movie-view.jsx file
Source Code
movie-card.jsx
import React from 'react';
import propTypes from 'prop-types';
import Button from 'react-bootstrap/Button';
import Card from 'react-bootstrap/Card';
export class MovieCard extends React.Component {
render() {
const {
movieData,
onMovieClick
} = this.props;
return ( <
Card >
<
Card.Img variant = 'top'
className = 'thumbnail'
src = {
movieData.ImageURL
}
/> <
Card.Body >
<
Card.Title > {
movieData.Title
} < /Card.Title> <
Card.Text > {
movieData.Description
} < /Card.Text> <
Button onClick = {
() => onMovieClick(movieData)
}
variant = 'link' >
Open <
/Button> <
/Card.Body> <
/Card>
);
}
}
MovieCard.propTypes = {
movieData: propTypes.shape({
Title: propTypes.string.isRequired,
Description: propTypes.string.isRequired,
ImageURL: propTypes.string,
}).isRequired,
onMovieClick: propTypes.func.isRequired,
};
movie-view.jsx
import React from 'react';
export class MovieView extends React.Component {
keypressCallback(event) {
console.log(event.key);
}
componentDidMount() {
document.addEventListener('keypress', this.keypressCallback);
}
componentWillUnmount() {
document.removeEventListener('keypress', this.keypressCallback);
}
render() {
const { movieData, onBackClick } = this.props;
return (
<div className='movie-view'>
<div className='movie-poster'>
<img src={movieData.ImageURL} />
</div>
<div className='movie-title'>
<span className='label'>Title: </span>
<span>{movieData.Title}</span>
</div>
<div className='movie-description'>
<span className='label'> Description: </span>
<span className='value'> {movieData.Description}</span>
</div>
<div className='movie-director'>
<span className='label'>Director: </span>
<span>{movieData.Director}</span>
</div>
<div className='movie-genre'>
<span className='label'>Genre: </span>
<span className='value'>{movieData.Genre}</span>
</div>
<button
onClick={() => {
onBackClick(null);
}}
>
Back
</button>
</div>
);
}
}
Console

You're not accessing the movie data properly.
Your code:
<div className='movie-director'>
<span className='label'>Director: </span>
<span>{movieData.Director}</span>
</div>
<div className='movie-genre'>
<span className='label'>Genre: </span>
<span className='value'>{movieData.Genre}</span>
</div>
Solution:
<div className='movie-director'>
<span className='label'>Director: </span>
<span>{movieData.Director.Name}</span>
</div>
<div className='movie-genre'>
<span className='label'>Genre: </span>
<span className='value'>{movieData.Genre.Name}</span>
</div>
Here is a good article to read regarding this issue

Related

SPFX web part to add Text and show it inside a Popup when clicking on a button

I am trying to achieve this using SPFX web part:-
The web part will render a Button and has a text field inside its settings.
The user add the Web Part >> edit it >> enter the text inside the text field (inside the setting page) >> save the web part>> then the web part will render a button >> if the user clicks on the button a popup will be shown with the entered text.
Now I found this link # https://www.c-sharpcorner.com/article/conecpt-of-react-portal-in-spfx/ which almost achieves what I am looking for, except that the Popup text inside the example is been hard-coded inside the .tsx file.. so what are the steps to make the Popup text configurable inside the web part settings instead of been hard-coded?
Thanks
Here is my ReactPortalWebPart.ts file:-
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '#microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '#microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '#microsoft/sp-webpart-base';
import * as strings from 'ReactPortalWebPartStrings';
import ReactPortal from './components/ReactPortal';
import { IReactPortalProps } from './components/IReactPortalProps';
export interface IReactPortalWebPartProps {
description: string;
}
export default class ReactPortalWebPart extends BaseClientSideWebPart<IReactPortalWebPartProps> {
public render(): void {
const element: React.ReactElement<IReactPortalProps> = React.createElement(
ReactPortal,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}
and here is the ReactPortal.tsx:-
import * as React from 'react';
import { IReactPortalProps } from './IReactPortalProps';
import Myportal from "./Myportal";
export default class ReactPortal extends React.Component<IReactPortalProps, {}> {
public render(): React.ReactElement<IReactPortalProps> {
return (
<div >
<Myportal/>
</div>
);
}
}
Here is the Myportal.tsx:-
import* as React from "react";
import usePortal from "react-cool-portal";
import "./mystyle.scss";
const Myportal = () => {
// const { Portal } = usePortal({ containerId: "my-portal-root" });
const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });
const handleClickBackdrop = (e: React.MouseEvent) => {
const { id } = e.target as HTMLDivElement;
if (id === "modal") hide();
};
return (
<div className="App">
<button className="btn" onClick={show} type="button">
Who we are
</button>
<button className="btn" onClick={show} type="button">
Our value
</button>
<Portal>
<div
id="modal"
className="modal"
onClick={handleClickBackdrop}
tabIndex={-1}
>
<div
className="modal-dialog"
role="dialog"
aria-labelledby="modal-label"
aria-modal="true"
>
<div className="modal-header">
<button
className="modal-close"
onClick={hide}
type="button"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<h1> Who we are</h1>
<h3>.................................................</h3>
Our overriding purpose is to dramatically improve the reliability, efficiency........
</div>
</div>
</div>
</Portal>
</div>
);
};
export default Myportal;
I think you need to re-write MyPortal as a React Component; bellow I'm tried written some example about this change that needed on MyPortal.tsx and ReactPortal.tsx, but I do not test then.
ReactPortal.tsx:
import * as React from 'react';
import { IReactPortalProps } from './IReactPortalProps';
import { IMyportalProps } from './IMyportalProps';
import Myportal from "./Myportal";
export default class ReactPortal extends React.Component<IReactPortalProps, {}> {
public render(): React.ReactElement<IReactPortalProps, IMyportalProps> {
return (
<div >
<Myportal title="Who we are" body="Our overriding purpose is to dramatically improve the reliability, efficiency........"/>
</div>
);
}
}
MyPortal.tsx
import* as React from "react";
import usePortal from "react-cool-portal";
import "./mystyle.scss";
export interface IMyportalProps {
title: string;
body: string
}
class Myportal extends React.Component
<IMyportalProps> {
constructor(props: IUnderstandStateComponentProps) {
super(props);
}
public render(): React.ReactElement
<IMyportalProps> {
const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });
const handleClickBackdrop = (e: React.MouseEvent) => {
const { id } = e.target as HTMLDivElement;
if (id === "modal") hide();
};
return (
<div className="App">
<button className="btn" onClick={show} type="button">
Who we are
</button>
<button className="btn" onClick={show} type="button">
Our value
</button>
<Portal>
<div
id="modal"
className="modal"
onClick={handleClickBackdrop}
tabIndex={-1}
>
<div
className="modal-dialog"
role="dialog"
aria-labelledby="modal-label"
aria-modal="true"
>
<div className="modal-header">
<button
className="modal-close"
onClick={hide}
type="button"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<h1>{this.props.title}</h1>
<h3>.................................................</h3>
{this.props.body}
</div>
</div>
</div>
</Portal>
</div>
);
};
My attempt :)
The idea is, you pass the "description" from the web part down to the dialog.
Please read the link above, how to pass properties in React from parent to the child components (or some basic React tutorial), it is highly recommended if you have chosen to work with SPFx using React.
Please note that an easier approach to create dialogs in SPFx coulod be, to use the Microsoft Fluent UI library, it works great with SPFx, has many tutorials, supports SharePoint themes, etc. In particular, it has the "Dialog" component built-in, among dozens of other useful components and controls.
Anyway, here we go with 'react-cool-portal'. Check out the comments in the code below.
ReactPortal.tsx
import * as React from 'react';
import { IReactPortalProps } from './IReactPortalProps';
import Myportal from "./Myportal";
export default class ReactPortal extends React.Component<IReactPortalProps, {}> {
// pass further the parameter that was received from the web part.
// The "description was passed to "ReactPortal" from WebPart with:
// React.createElement(
// ReactPortal,
// {
// description: this.properties.description
// }
public render(): React.ReactElement<IReactPortalProps> {
return (
<div>
<!-- pass the parameter down to the child component -->
<Myportal description={this.props.description} />
</div>
);
}
}
Myportal.tsx
import* as React from "react";
import usePortal from "react-cool-portal";
import "./mystyle.scss";
// accept properties (added "props" argument)
// and use it below in HTML {props.description}
const Myportal = (props) => {
// const { Portal } = usePortal({ containerId: "my-portal-root" });
const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });
const handleClickBackdrop = (e: React.MouseEvent) => {
const { id } = e.target as HTMLDivElement;
if (id === "modal") hide();
};
return (
<div className="App">
<button className="btn" onClick={show} type="button">
Who we are
</button>
<button className="btn" onClick={show} type="button">
Our value
</button>
<Portal>
<div
id="modal"
className="modal"
onClick={handleClickBackdrop}
tabIndex={-1}
>
<div
className="modal-dialog"
role="dialog"
aria-labelledby="modal-label"
aria-modal="true"
>
<div className="modal-header">
<button
className="modal-close"
onClick={hide}
type="button"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<h1> Who we are</h1>
<!-- use the property value somehow -->
<h3>{props.description}</h3>
</div>
</div>
</div>
</Portal>
</div>
);
};
export default Myportal;
You can achieve this in two ways.
Storing the popup content as part of the page property that is persisted once you edit the page
Storing the popup content in a list and fetching the data whenever the page is render. FYI, haven't fully thought about how to achieve it via this way.
I have updated relevant sections of the code you provided to show how to achieve the expected by storing the popup content as part of the page property.
Added another property to the props to store the popup content
export interface IReactPortalWebPartProps {
description: string;
content?: string;
}
Updated the render method of the component ReactPortalWebPart.ts
public render(): void {
const element: React.ReactElement<any> = React.createElement(
ReactPortal,
{
mode: this.displayMode, //used to determine if to display the text input or not based on the current state of the webpart.
content: this.properties.content, //the contents property of the webpart which is persisted in the page properties.
description: this.properties.description,
updateWebPartContent: this.updateWebPartContentProperty.bind(this) // the function that would be called from the portal component when the content is saved by the user
}
);
private updateWebPartContentProperty(htmlcontent) {
//console.log( htmlcontent )
//console.log( this.properties.content )
this.properties.content = htmlcontent;
this.onPropertyPaneFieldChanged( 'content', this.properties.content, htmlcontent );
//console.log( this.properties.content )
}
Passing the properties from ReactPortal to the Myportal functional component.
<Myportal
_mode={ this.props.mode }
_content={ this.props.content }
_updateWebPartContent={ this.props.updateWebPartContent }
/>
Updated the functional component with the props object, added the hook to save the input from the user and also using the current mode of the page to determine if the text area should be displayed or not.
const Myportal = (props) => {
const [ content, setContent ] = React.useState(props._content);
//abbreviated
return (
<div className="App">
{ props._mode == 2 && <div>
<textarea name="conent" id="conent" cols={30} rows={10} value={ props._content } onChange={ (evt) => { setContent(evt.target.value); } }
</textarea> <br />
<button className="btn" onClick={ () => { props._updateWebPartContent(content) } } type="button" >Save Content</button>
</div> }
<div className="modal-body">
{ props._content }
</div>

Syntax error with creating a popup in React

I am trying to create a simple pop up using React. In other words, when a user clicks on a button, a simple pop up should appear. However, with my current implementation I am getting a syntax error and I am unsure why. Any help would be much appreciated!
Here is my main file, where my mock_modal is called, e.g. where my popup is called
import PropTypes from 'prop-types';
import React from 'react';
...
class Calendar extends React.Component {
...
mockModalClick () {
}
hoverCustomSlot() {
this.setState({ hoverCustomSlot: !this.state.hoverCustomSlot });
}
render() {
const description = this.state.hoverCustomSlot ?
(<h4 className="custom-instructions">
Click, drag, and release to create your custom event
</h4>)
: null;
const addMockModal = this.props.registrarSupported ? (
<div className="cal-btn-wrapper">
<button
type="submit"
form="form1"
className="save-timetable add-button"
data-for="sis-btn-tooltip"
data-tip
onClick={this.state.seen ? <MockModal toggle={this.togglePop} /> : null}
>
<img src="/static/img/star.png" alt="SIS" style={{ marginTop: '2px' }} />
</button>
<ReactTooltip
id="sis-btn-tooltip"
class="tooltip"
type="dark"
place="bottom"
effect="solid"
>
<span>See my Mock Modal!</span>
</ReactTooltip>
</div>
) : null;
Here is the definition of my pop up
import React, { Component } from "react";
export default class PopUp extends Component {
handleClick = () => {
this.props.toggle();
};
render() {
return (
<div className="modal">
<div className="modal_content">
<span className="close" onClick={this.handleClick}>
×
</span>
<form>
<h3>Register!</h3>
<label>
Name:
<input type="text" name="name" />
</label>
<br />
<input type="submit" />
</form>
</div>
</div>
);
}
}
I have added ellipses and did not include other unnecessary code. The exact error that I am getting is:
ERROR in ./static/js/redux/ui/modals/mock_modal.jsx
web_1 | Module build failed: SyntaxError: Unexpected token (4:14)
export default class PopUp extends Component {
handleClick = () => {
this.props.toggle();
Try adding a const in front of handleClick.

ReactJs Hiding Some HTML depend on situation

I want to make the price tag also some other HTML contents hide/show depending on some data entry.
for example, if I get True it should be visible prices if it's gonna be False it must hide.
I'm sharing some code of my pages please give me ideas.
Thank you.
// react
import React from 'react';
// third-party
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
// application
import AsyncAction from './AsyncAction';
import Points from './Points';
import { cartAddItem } from '../../store/cart';
import { Quickview16Svg } from '../../svg';
import { quickviewOpen } from '../../store/quickview';
import { url } from '../../services/utils';
function ProductCard(props) {
const {
product,
layout,
quickviewOpen,
cartAddItem,
} = props;
const containerClasses = classNames('product-card', {
'product-card--layout--grid product-card--size--sm': layout === 'grid-sm',
'product-card--layout--grid product-card--size--nl': layout === 'grid-nl',
'product-card--layout--grid product-card--size--lg': layout === 'grid-lg',
'product-card--layout--list': layout === 'list',
'product-card--layout--horizontal': layout === 'horizontal',
});
let badges = [];
let image;
let price;
let features;
if (product.badges.includes('sale')) {
badges.push(<div key="sale" className="product-card__badge product-card__badge--sale">Sale</div>);
}
if (product.badges.includes('hot')) {
badges.push(<div key="hot" className="product-card__badge product-card__badge--hot">Hot</div>);
}
if (product.badges.includes('new')) {
badges.push(<div key="new" className="product-card__badge product-card__badge--new">New</div>);
}
badges = badges.length ? <div className="product-card__badges-list">{badges}</div> : null;
if (product.images && product.images.length > 0) {
image = (
<div className="product-card__image product-image">
<Link to={url.product(product)} className="product-image__body">
<img className="product-image__img" src={product.images[0]} alt="" />
</Link>
</div>
);
}
if (product.discountPrice) {
price = (
<div className="product-card__prices">
<span className="product-card__new-price"><Points value={product.price} /></span>
{' '}
<span className="product-card__old-price"><Points value={product.discountPrice} /></span>
</div>
);
} else {
price = (
<div className="product-card__prices">
<Points value={product.price} />
</div>
);
}
if (product.attributes && product.attributes.length) {
features = (
<ul className="product-card__features-list">
{product.attributes.filter((x) => x.featured).map((attribute, index) => (
<li key={index}>{`${attribute.name}: ${attribute.values.map((x) => x.name).join(', ')}`}</li>
))}
</ul>
);
}
return (
<div className={containerClasses}>
<AsyncAction
action={() => quickviewOpen(product.slug)}
render={({ run, loading }) => (
<button
type="button"
onClick={run}
className={classNames('product-card__quickview', {
'product-card__quickview--preload': loading,
})}
>
<Quickview16Svg />
</button>
)}
/>
{badges}
{image}
<div className="product-card__info">
<div className="product-card__name">
<Link to={url.product(product)}>{product.name}</Link>
<br />
<br />
</div>
{features}
</div>
<div className="product-card__actions">
<div className="product-card__availability">
Availability:
<span className="text-success">In Stock</span>
</div>
{price}
<div className="product-card__buttons">
<AsyncAction
action={() => cartAddItem(product)}
render={({ run, loading }) => (
<React.Fragment>
<button
type="button"
onClick={run}
className={classNames('btn btn-primary product-card__addtocart', {
'btn-loading': loading,
})}
>
Add To Cart
</button>
<button
type="button"
onClick={run}
className={classNames('btn btn-secondary product-card__addtocart product-card__addtocart--list', {
'btn-loading': loading,
})}
>
Add To Cart
</button>
</React.Fragment>
)}
/>
</div>
</div>
</div>
);
}
ProductCard.propTypes = {
/**
* product object
*/
product: PropTypes.object.isRequired,
/**
* product card layout
* one of ['grid-sm', 'grid-nl', 'grid-lg', 'list', 'horizontal']
*/
layout: PropTypes.oneOf(['grid-sm', 'grid-nl', 'grid-lg', 'list', 'horizontal']),
};
const mapStateToProps = () => ({});
const mapDispatchToProps = {
cartAddItem,
quickviewOpen,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ProductCard);
Here I want to hide prices in some onload situations. This is my homepage Carousel.
You can use code like this. It will only render the component if the boolean evaluates to a truthy value.
const { isVisible } = this.props; // Or wherever you want to get your boolean from
return (
<div>
{isVisible && <MyComponent />}
</div>
You refer to Conditional rendering, there are a couple ways to do that:
<div>
{someCondition && <p>The condition is true</p>}
</div>
Or if you want a if else rendering:
<div>
{someCondition ? <p>The condition is true</p> : <p>The condition is false</p>}
</div>
You can find more info in react docs

Accessing state from another component in react

Just learning React and ran into a problem. I have a selector allowing me to choose year/make/model of a vehicle. However, I want it to reset make and model if the year is changed, or just reset the model if make is changed, so that those options state is set back to null and thus in the ui deselected.
However, the problem is I don't know how to make the state of a component available to the others. I figured the solution could be as simple as using an onChange function for year, that will then take the state of make/model and reset to null, though that's not possible without year.js knowing the state of the other 2...
Hopefully you understand what I'm talking about. Here's the code.
Year.js
import React, { Component } from 'react';
import '../CSS/App.css';
const vehicleYear = [
{id:1,label:"2019",href:"#"},
{id:2,label:"2018",href:"#"},
{id:3,label:"2017",href:"#"},
{id:4,label:"2016",href:"#"},
{id:5,label:"2015",href:"#"},
{id:6,label:"2014",href:"#"}
];
class Year extends Component {
constructor(props){
super(props);
this.state = {
year: null,
}
}
createYearList = () => {
let listItems = [];
for (let i = 0; i < vehicleYear.length; i++) {
listItems.push(
<li className={`list ${this.state.year === vehicleYear[i].id ? "active" : ""}`} onClick={(e) => {
this.yearClick(e, vehicleYear[i].id, vehicleYear[i].label)
}}>
<a href={vehicleYear[i].href}>{vehicleYear[i].label}</a>
</li>
);
}
return listItems;
};
yearClick = (e, id, label) => {
let state = this.state;
state.year = id;
this.setState(state);
console.log(this.state);
console.log(this.props.year);
};
render() {
return (
<div>
{this.createYearList()}
</div>
)
}
}
export default Year;
Make.js
import React, { Component } from 'react';
import '../CSS/App.css';
const vehicleMake = [
{id:1,label:"POLARIS",href:"#"},
{id:2,label:"CAN_AM",href:"#"},
{id:3,label:"YAMAHA",href:"#"},
{id:4,label:"SUZUKI",href:"#"},
{id:5,label:"ARCTIC-CAT",href:"#"}
];
class Make extends Component {
constructor(props){
super(props);
this.state = {
make: null
}
}
createMakeList = () => {
let listItems = [];
for(let i = 0; i < vehicleMake.length; i++){
listItems.push(
<li className={`list ${this.state.make === vehicleMake[i].id ? "active" : ""}`} onClick={(e)=>{this.makeClick(e, vehicleMake[i].id, vehicleMake[i].label)}}>
<a href={vehicleMake[i].href}>{vehicleMake[i].label}</a>
</li>
);
}
return listItems;
};
makeClick = (e, id, label) => {
console.log(id, label);
let state = this.state;
state.make = id;
this.setState(state);
console.log(state.make);
};
render() {
return (
<div>
{this.createMakeList()}
</div>
)
}
}
export default Make;
Model.js
import React, { Component } from 'react';
import '../CSS/App.css';
const vehicleModel = [
{id:1,label:"RZR 570",href:"#"},
{id:2,label:"RZR 900",href:"#"},
{id:3,label:"RZR S 900",href:"#"},
{id:4,label:"RZR S 1000",href:"#"},
{id:5,label:"RZR S 1000 TURBO",href:"#"}
];
class Model extends Component {
constructor(props){
super(props);
this.state = {
model: null
}
}
createModelList = () => {
let listItems = [];
for(let i = 0; i < vehicleModel.length; i++){
listItems.push(
<li className={`list ${this.state.model === vehicleModel[i].id ? "active" : ""}`} onClick={(e)=>{this.modelClick(e, vehicleModel[i].id, vehicleModel[i].label)}}>
<a href={vehicleModel[i].href}>{vehicleModel[i].label}</a>
</li>
);
}
return listItems;
};
modelClick = (e, id, label) => {
console.log(id, label);
let state = this.state;
state.model = id;
this.setState(state);
};
render() {
return (
<div>
{this.createModelList()}
</div>
)
}
}
export default Model;
And here's the main App.js
import React, { Component } from 'react';
import './CSS/App.css';
import { Container, Row, Col } from 'reactstrap';
import rzrPic from './Media/rzr-xp-1000-eps-trails-rocks-media-location-1-xxs.jpg';
import camsoT4S from './Media/camso-atv-t4s.jpg';
import Year from './Components/Year';
import Make from './Components/Make';
import Model from './Components/Model';
class App extends Component {
render() {
return (
<div className="App">
<Container fluid="true">
<Row>
<Col xs="3" className="categories">
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE YEAR
</h2>
</span>
<div className="categoryList">
<ul>
<Year/>
</ul>
</div>
</div>
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE MAKE
</h2>
</span>
<div className="categoryList">
<ul>
<Make/>
</ul>
</div>
</div>
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE MODEL
</h2>
</span>
<div className="categoryList">
<ul>
<Model/>
</ul>
</div>
</div>
</Col>
<Col xs="6" className="fill">
<img src={rzrPic} alt="rzr xp 1000"/>
</Col>
<Col xs="3" className="categories">
<span className="categoryHeader2">
<h2 className="categoryHeading">
AVAILABLE TRACKS
</h2>
</span>
<div className="Track">
<img src={camsoT4S} alt="Camso T4S Tracks"/>
<div className="TrackInfo">
<h3>CAMSO T4S - 4 SEASON</h3>
<p>Starting at $3,999.00</p>
<span>
ADD TO CART
</span>
</div>
</div>
<div className="Track">
<div className="TrackInfo">
<h3>CAMSO T4S - 4 SEASON</h3>
<p>Starting at $3,999.00</p>
<p className="select">SELECT</p>
</div>
</div>
</Col>
</Row>
</Container>
</div>
);
}
}
export default App;
Thanks in advance for your help!
Instead of storing the selected year / make / model in each component, store them in the parent App. You will then handle the reset logic in the App component.
Here is how to refactor your code:
import React, { Component } from "react";
import "../CSS/App.css";
// The list of years is now passed as props
//
// const vehicleYear = [];
class Year extends Component {
// You dont need the constructor anymore as the component
// doesn't have a state to initialize
//
// constructor(props) {}
createYearList = () => {
// Use the year list passed as a prop from the parent
const { vehicleYear } = this.props;
let listItems = [];
for (let i = 0; i < vehicleYear.length; i++) {
listItems.push(
<li
className={`list ${
this.state.year === vehicleYear[i].id ? "active" : ""
}`}
onClick={e => {
this.yearClick(e, vehicleYear[i].id, vehicleYear[i].label);
}}
>
<a href={vehicleYear[i].href}>{vehicleYear[i].label}</a>
</li>
);
}
return listItems;
};
yearClick = (e, id, label) => {
// Call the onClick function passed as a prop from the parent
this.props.onClick(e, id, label);
};
render() {
return <div>{this.createYearList()}</div>;
}
}
export default Year;
I only modified the Year component since the Make and Model components have the same structure. I'll come back to this later.
And here is how to use Year in App:
import React, { Component } from 'react';
// import ...
// Define the list of years
const vehicleYear = [
{id:1,label:"2019",href:"#"},
{id:2,label:"2018",href:"#"},
{id:3,label:"2017",href:"#"},
{id:4,label:"2016",href:"#"},
{id:5,label:"2015",href:"#"},
{id:6,label:"2014",href:"#"}
];
class App extends Component {
constructor(props){
super(props);
// Initialise the state of the App component
this.state = {
year: null,
}
}
// Move the click handler from the Year component to its parent component
yearClick = (e, id, label) => {
this.setState({
year: id
});
};
render() {
return (
<div className="App">
<Container fluid="true">
<Row>
<Col xs="3" className="categories">
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE YEAR
</h2>
</span>
<div className="categoryList">
<ul>
{/* Pass the list of years, the selected year and the handler to
select a year to the Year component */}
<Year vehicleYear={vehicleYear} selectedYear={this.state.year} onClick={this.yearClick} />
</ul>
</div>
</div>
...
</Row>
</Container>
</div>
);
}
}
export default App;
Now you have a fully controled Year and the logic handled by the App component. If you want to reset the selected year, you only have to create and call such a function in the App component:
resetYear = () => {
this.setState({
year: null
});
};
Bonus: Refactoring
You can refacto your Year, Make and Model components to one reusable component because they have exactly the same structure. Here is a ListComponent extracted from them:
// The list components takes three arguments:
// - itemsList: items to be displayed
// - selectedItemId: the id of the selected item
// - onSelect: a function to call when an item is selected
class ListComponent extends Component {
render() {
const { itemsList, selectedItemId, onSelect } = this.props;
return (
<div>
{itemsList.map(item => (
<li
className={`list ${selectedItemId === item.id ? "active" : ""}`}
onClick={e => {
onSelect(e, item.id, item.label);
}}
>
<a href={item.href}>{item.label}</a>
</li>
))}
</div>
);
}
}
export default ListComponent;
And you can use it like this:
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE YEAR
</h2>
</span>
<div className="categoryList">
<ul>
<ListComponent onSelect={this.selectYear} itemsList={vehicleYear} selectedItemId={this.state.year}/>
</ul>
</div>
</div>
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE MAKE
</h2>
</span>
<div className="categoryList">
<ul>
<ListComponent onSelect={this.selectMake} itemsList={vehicleMake} selectedItemId={this.state.make}/>
</ul>
</div>
</div>
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE MODEL
</h2>
</span>
<div className="categoryList">
<ul>
<ListComponent onSelect={this.selectModel} itemsList={vehicleModel} selectedItemId={this.state.model}/>
</ul>
</div>
</div>
The easiest solution will be to keep your state in the parent in this case in the App.js or create a component ad hoc to be the parent of your components, and simply pass a prop onChangeYear for example to change the state of your parent
import React, { Component } from 'react';
import './CSS/App.css';
import { Container, Row, Col } from 'reactstrap';
import rzrPic from './Media/rzr-xp-1000-eps-trails-rocks-media-location-1-xxs.jpg';
import camsoT4S from './Media/camso-atv-t4s.jpg';
import Year from './Components/Year';
import Make from './Components/Make';
import Model from './Components/Model';
class App extends Component {
contructor(props) {
super(props);
this.state = { year: "" }
}
render() {
return (
<div className="App">
<Container fluid="true">
<Row>
<Col xs="3" className="categories">
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE YEAR
</h2>
</span>
<div className="categoryList">
<ul>
<Year onChangeYear={(year) => {this.setState({year})/>
</ul>
</div>
</div>
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE MAKE
</h2>
</span>
<div className="categoryList">
<ul>
<Make/>
</ul>
</div>
</div>
<div>
<span className="categoryHeader">
<h2 className="categoryHeading">
VEHICLE MODEL
</h2>
</span>
<div className="categoryList">
<ul>
<Model/>
</ul>
</div>
</div>
</Col>
<Col xs="6" className="fill">
<img src={rzrPic} alt="rzr xp 1000"/>
</Col>
<Col xs="3" className="categories">
<span className="categoryHeader2">
<h2 className="categoryHeading">
AVAILABLE TRACKS
</h2>
</span>
<div className="Track">
<img src={camsoT4S} alt="Camso T4S Tracks"/>
<div className="TrackInfo">
<h3>CAMSO T4S - 4 SEASON</h3>
<p>Starting at $3,999.00</p>
<span>
ADD TO CART
</span>
</div>
</div>
<div className="Track">
<div className="TrackInfo">
<h3>CAMSO T4S - 4 SEASON</h3>
<p>Starting at $3,999.00</p>
<p className="select">SELECT</p>
</div>
</div>
</Col>
</Row>
</Container>
</div>
);
}
}
export default App;
If you find yourself having a lot of components in your web app I would think to integrate Redux to handle the state globally https://redux.js.org/introduction/getting-started.

Passing data to parent - recipebook React

I am working on the recipe book app and trying to implement the edit entry function.
How it works is to input recipe name (e.g Pasta), followed by ingredients (e.g egg, flour, salt). The ingredients have to be input with commas and will be shown as a list.
Pasta
-Egg
-Flour
i can see that it is somewhat working, because i can see the new entries in the input text (e.g initially was egg,flour,salt -> egg,flour,salt,water) when i tried to edit it again.
However, the extra ingredients (in the above example: water) is not showing up in the list. Do i have to figure a way to re-render the list?
updates:
I think i know where the error might be. There is some issue passing the data and setting the state.
<EditRecipe recipe={this.props.recipe} editRecipe={this.editRecipe.bind(this, this.props.recipe.id, recipe)}/>
App.js
import React, { Component } from 'react';
// import logo from './logo.svg';
import './App.css';
import uuid from 'uuid';
import Modal from 'react-modal';
import RecipeList from './components/RecipeList/RecipeList';
import AddRecipe from './components/AddRecipe/AddRecipe';
class App extends Component {
constructor(props){
super(props);
this.state = {
recipes:[]
};
}
getRecipes(){
this.setState({recipes:[
{
id: uuid.v4(),
food: "pumpkin pie",
ingredients: ["pumpkin puree", "sweetened condensed milk", "eggs", "pumpkin pie spice", "pie crust"]
},
{
id: uuid.v4(),
food: "spaghetti",
ingredients: ["noodles", "tomato sauce", "meatballs"]
},
{
id: uuid.v4(),
food: "onion pie",
ingredients: ["onion", "pie crust"]
},
]});
}
componentWillMount(){
this.getRecipes();
}
handleAddRecipe(recipe){
let recipes = this.state.recipes;
recipes.push(recipe);
this.setState({recipes: recipes});
}
handleDeleteRecipe(id){
let recipes = this.state.recipes;
let index = recipes.findIndex(x => x.id === id);
recipes.splice(index,1);
this.setState({recipes: recipes});
}
handleEditRecipe(id, recipe){
let recipes = this.state.recipes;
let index = recipes.findIndex(x => x.id === id);
recipes.splice(index,1,recipe);
this.setState({recipes: recipes});
}
render() {
return (
<div className="App">
<RecipeList recipes={this.state.recipes} onDelete={this.handleDeleteRecipe.bind(this)} onEdit={this.handleEditRecipe.bind(this)}/>
<AddRecipe addRecipe={this.handleAddRecipe.bind(this)}/>
</div>
);
}
}
export default App;
RecipeList.js
import React, { Component } from 'react';
import Collapsible from 'react-collapsible';
import RecipeItem from '../RecipeItem/RecipeItem'
import './RecipeList.css';
class RecipeList extends Component{
deleteRecipe(id){
this.props.onDelete(id);
}
editRecipe(id, recipe){
this.props.onEdit(id, recipe);
}
render(){
let recipeItem;
if(this.props.recipes){
recipeItem=this.props.recipes.map(recipe => {
return(
<RecipeItem onEdit={this.editRecipe.bind(this)} onDelete={this.deleteRecipe.bind(this)} key={recipe.id} recipe={recipe} />
)
});
}
return(
<div className="recipeList box">
{recipeItem}
</div>
)
}
}
export default RecipeList;
RecipeItem.js
import React, { Component } from 'react';
import Collapsible from 'react-collapsible';
import EditRecipe from '../EditRecipe/EditRecipe';
class RecipeItem extends Component{
deleteRecipe(id){
this.props.onDelete(id);
}
editRecipe(id, recipe){
this.props.onEdit(id, recipe);
}
render(){
let recipe=this.props.recipe
let foodName=recipe.food;
let ingredientItem;
if(recipe.ingredients){
ingredientItem=recipe.ingredients.map(ingredient=>{
return(
<a className="panel-block">
{ingredient}
</a>
)
})
}
return(
<ul>
<li className="Recipe">
<Collapsible trigger={foodName} transitionTime="200" easing="ease-in-out">
<nav className="panel">
<p className="panel-heading">
Ingredients
</p>
{ingredientItem}
<div className="panel-block">
<button className="button is-warning is-outlined" onClick={this.deleteRecipe.bind(this, this.props.recipe.id)}>
Delete
</button>
<EditRecipe recipe={this.props.recipe} editRecipe={this.editRecipe.bind(this, this.props.recipe.id, recipe)}/>
</div>
</nav>
</Collapsible>
</li>
</ul>
);
}
}
export default RecipeItem;
EditRecipe.js
import React, { Component } from 'react';
import RecipeForm from '../RecipeForm/RecipeForm';
// import './EditRecipe.css';
import Modal from 'react-modal';
import uuid from 'uuid';
// import Modal from 'boron/DropModal';
// import './RecipeList.css';
class RecipeEdit extends Component{
constructor(props){
super(props);
this.state = {
revisedRecipe:{
id: this.props.recipe.id,
food: this.props.recipe.food,
ingredients: this.props.recipe.ingredients
},
modalIsOpen: false,
speed: 100
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal(){
this.setState({modalIsOpen: true});
}
closeModal(){
this.setState({modalIsOpen: false});
}
handleSubmit(e){
const revised = this.state.revisedRecipe;
this.props.editRecipe(revised);
e.preventDefault();
}
handleNameChange(e){
this.setState({revisedRecipe:{
food: e.target.value
}
});
}
handleIndChange(e){
this.setState({revisedRecipe:{
ingredients: e.target.value
}
});
}
render(){
const speed = this.state.speed;
let recipe=this.props.recipe;
let foodName=this.state.revisedRecipe.food;
let ingredients=recipe.ingredients;
return(
<div>
<button className="button is-primary" onClick={this.openModal}>Edit Recipe</button>
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
closeTimeoutMS={speed}
contentLabel="Example Modal"
>
<div className="field">
<h2 className="title is-2">Edit Recipe</h2>
<form>
<label className="label">Recipe</label>
<div className="control">
<input className="input" type="text" placeholder="Recipe Name" ref="recipeName" value={this.state.revisedRecipe.food} onChange={this.handleNameChange.bind(this)}/>
</div>
<div className="field">
<label className="label">Ingredients</label>
<div className="control has-icons-left has-icons-right">
<input className="input" type="text" placeholder="Enter ingredients. (if more than 1 ingredient, separate them with commas)" ref="ingredients" value={this.state.revisedRecipe.ingredients} onChange={this.handleIndChange.bind(this)}/>
<span className="icon is-small is-left">
<i className="fa fa-flask"></i>
</span>
</div>
</div>
<div className="field is-grouped">
<div className="control">
<button className="button is-primary" onClick={this.closeModal}>Edit Recipe</button>
</div>
<div className="control">
<button className="button" onClick={this.closeModal}>Cancel</button>
</div>
</div>
</form>
</div>
</Modal>
</div>
);
}
}
export default RecipeEdit;
I believe you're actually getting an error when trying to re-render after updating a list. The ingredients property in the recipes are an array (as shown in getRecipes()) but you're setting the new state of ingredients (in EditRecipe) as a string: "egg,flour,salt,water" and then trying to render the ingredients as an array: ingredients.map().
When you render an input field with an array <input value={["egg", "flour"]} /> it does show the values separated by comma, but the event.target.value in onChange is actually a string.
In EditRecipe's, handleIndChange could be fixed with:
this.setState({revisedRecipe: {ingredients: e.target.value.split(',')}});
This does have another problem, though in that you are overriding the revisedRecipe completely. So all of the setState calls should be something like:
const recipe = this.state.revisedRecipe;
recipe.ingredients = e.target.value.split(',');
this.setState({revisedRecipe: recipe);

Categories

Resources