i got a problem when i try to pass a props to child component inside map iteration in my parent component. it always show a message notify that
TypeError: Cannot read property 'props' of undefined
can someone help me figure out what wrong in my code? i already try to pass it through a local state too, then still got an error.
here is my code :
My Parent Component
import React from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Button, CardTitle, CardText, Row, Col } from 'reactstrap';
import classnames from 'classnames';
import PeriodicSetup from './PeriodicSetup';
import PeriodicDataTable from './PeriodicDataTable';
import {connect} from 'react-redux';
import store from '../../store/store';
class SetupPage extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
activeTab: 0,
};
}
toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render() {
return (
<div>
<Nav tabs>
{this.props.SetupTabTitles.map((data, i)=>
<NavItem>
<NavLink className={classnames({ active: this.state.activeTab === i})} onClick={() => {this.toggle(i); }}>
{data.tabTitle}
</NavLink>
</NavItem>
)}
</Nav>
<TabContent activeTab={this.state.activeTab}>
{this.props.SetupTabTitles.map(function(data, i) {
if(data.tabTitle == 'Tasks'){
return (
<TabPane tabId={i}>
test
</TabPane>
)
}else if(data.tabTitle == 'Periodic'){
return (
<TabPane tabId={i}>
<PeriodicSetup />
<PeriodicDataTable periodicData = {this.props.periodicList}/>
</TabPane>
)
}
})}
</TabContent>
</div>
);
}
}
function mapStateToProps(state){
return {
SetupTabTitles : state.component.SetupTabTitles,
periodicList : state.setup.periodicList
};
}
export default connect(mapStateToProps)(SetupPage);
My Child Component :
import React from 'react';
const {Table, Column, Cell} = require('fixed-data-table');
export default class PeriodicDataTable extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
test
</div>
)
}
}
The Condition in my code,
this.props.periodicList
already have an array value, and everything is working well if i commend out my
<PeriodicDataTable periodicData = {this.props.periodicList}/>
or i move it out from the iteration, it works. but i still don't know why it got an error if i put it inside an iteration.
You need pass your map callback function with explicit passing to this that refer to the React component, not the callback function that has no property called props, to be like this:
{this.props.SetupTabTitles.map(this.renderTabTitles.bind(this))}
then add a method in your class as follow:
renderTabTitles(data, i) {
if(data.tabTitle == 'Tasks') {
return (
<TabPane tabId={i}>
test
</TabPane>
)
} else if(data.tabTitle == 'Periodic') {
return (
<TabPane tabId={i}>
<PeriodicSetup />
<PeriodicDataTable periodicData = {this.props.periodicList}/>
</TabPane>
)
}
}
Related
I have a NavMenu component class like this:
import React, { Component } from "react";
import { Navbar, NavItem, NavLink, OffcanvasBody } from "reactstrap";
import "./styles/NavMenu.css";
import Offcanvas from "react-bootstrap/Offcanvas";
export class NavMenu extends Component {
static displayName = NavMenu.name;
constructor(props) {
super(props);
this.state = {
showOffcanvas: false,
};
this.setShowOffcanvas = this.setShowOffcanvas.bind(this);
this.setHideOffcanvas = this.setHideOffcanvas.bind(this);
}
setShowOffcanvas() {
this.setState({ showOffcanvas: true });
}
setHideOffcanvas() {
this.setState({ showOffcanvas: false });
}
render() {
return (
<>
<Navbar className="sticky-top">
<ul className="left-navbar">
<NavItem>
<NavLink onClick={this.setShowOffcanvas} className="original">
Open Offcanvas
</NavLink>
</NavItem>
</ul>
</Navbar>
<Offcanvas
className="offcanvasPlayer"
show={this.state.showOffcanvas}
onHide={this.setHideOffcanvas}
>
<NavLink
className="text-white card-link close-btn"
data-bs-dismiss="offcanvas"
onClick={this.setHideOffcanvas}
></NavLink>
<OffcanvasBody>{/* offcanvas body here */}</OffcanvasBody>
</Offcanvas>
</>
);
}
}
then my navbar button to open offcanvas in another component like this :
import React, { Component } from "react";
import { NavLink } from "reactstrap";
import "./styles/TopNavbar.css";
export class TopNavbar extends Component {
static displayName = TopNavbar.name;
constructor(props) {
super(props);
this.state = {
showOffcanvas: false,
showPlayer: false,
};
this.setShowOffcanvas = this.setShowOffcanvas.bind(this);
this.setHideOffcanvas = this.setHideOffcanvas.bind(this);
}
setShowOffcanvas() {
this.setState({ showOffcanvas: true });
}
setHideOffcanvas() {
this.setState({ showOffcanvas: false });
}
render() {
return (
<>
<NavLink className="item" onClick={this.setShowOffcanvas}>Opening Offcanvas NavMenu component</NavLink>
</>
);
}
}
but when i'm clicking on the (this.setShowOffcanvas), project show this error:
Uncaught TypeError: Cannot read properties of null (reading 'hide')
at HTMLAnchorElement.<anonymous> (offcanvas.js:254:1)
at HTMLDocument.handler (event-handler.js:118:1)
I swear to God, whatever I did didn't work
Learning react and using semantic-ui-react Menu to build a SideBarMenu , and have below Questions
How does clicking on a Menu.Item trigger parent component (SBMenu) render ?
like render on component is triggered either due to change in state or props , but what is changing for SBMenu ?
Why is SBMenu render function called twice ?
Rendering .... future
Rendering .... future
SidebarMenu
import React from 'react';
import { Sidebar, Menu, Segment, Icon } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { withRouter } from 'react-router-dom';
import SBMenu from './SBMenu'
import './SidebarMenu.css';
import SBRoutes from './SBRoutes'
const menuItems = [
{ icon: 'dashboard', label: 'Future ITEM', name: 'future', url: '/future' },
{ icon: 'dashboard', label: 'Future ITEM1', name: 'future', url: '/future1' }
];
class SidebarMenu extends React.Component {
constructor(props) {
super(props);
this.handleItemClick = this.handleItemClick.bind(this);
}
handleItemClick () {
console.log('item clicked')
}
/*shouldComponentUpdate() {
return true;
}*/
render() {
console.log("SidebarMenu called....")
return (
<Sidebar.Pushable as={Segment} className="SidebarMenu">
<Sidebar
as={Menu}
borderless
animation="push"
icon="labeled"
inverted
onHide={this.handleSidebarHide}
vertical
visible={true}
width="thin"
>
<SBMenu menuItems={menuItems} />
</Sidebar>
<Sidebar.Pusher>
<div className="container">
<SBRoutes />
</div>
</Sidebar.Pusher>
</Sidebar.Pushable>
);
}
}
export default SidebarMenu;
SBMenu
class SBMenu extends React.Component {
constructor(props) {
super(props);
}
render() {
const {location,menuItems} = this.props;
console.log("Rendering .... " + location.pathname)
return (menuItems.map((item, index) => (
<Menu.Item
name={item.name}
as={Link}
to={item.url}
active={location.pathname === item.url}
data-menu={item.label}
key={`menu_item_${index}`}
>
<Icon name={item.icon} />
{item.label}
</Menu.Item>
))
);
}
}
export default withRouter(SBMenu);
You can change child state from parent using lifting state up. You can simply pass a method to the SBMenu and work with it.
SBMenu render twice because you use the same name in menuItems.
It should be working fine now if you change menuItems name.
I had already found a solution by luck so I don't quite understand how it worked even after trying to read stuff online.
I am simply trying to get the array of comments inside the selectedDish prop. The selectedDish's state is populated inside the Main Component's function, and I am trying to access selectedDish's array of comments inside the DishDetail component.
I was able to get the comments by:
{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}
But I am unable to when I just do the following and receive this error "Cannot read property 'comments' of undefined":
{ this.renderComments(this.props.selectedDish.comments)}
Why does this work? Shouldn't this.props.selectedDish.comments be enough as I am able to successfully render the "renderDish(dish)" function?
Main Component
import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import Menu from './MenuComponent';
import Dishdetail from './DishdetailComponent';
import { DISHES } from '../shared/dishes';
class Main extends React.Component {
constructor() {
super();
this.state = {
dishes: DISHES,
selectedDish: null
};
// this.onDishSelect = this.onDishSelect.bind(this);
}
onDishSelect(dishId) {
this.setState({
selectedDish: dishId
});
}
render() {
return (
<div>
<Navbar dark color="primary">
<div className="container">
<NavbarBrand href="/">Ristorante Con Fusion</NavbarBrand>
</div>
</Navbar>
<Menu dishes={this.state.dishes} onClick={(dishId) => this.onDishSelect(dishId)} />
<Dishdetail selectedDish={this.state.dishes.filter((dish) => dish.id === this.state.selectedDish)[0]} />
</div>
);
}
}
export default Main;
DishDetail Component
import React, { Component } from 'react';
import { Card, CardImg, CardText, CardBody, CardTitle } from 'reactstrap';
export default class Dishdetail extends React.Component {
constructor(props) {
super(props);
}
renderDish(dish) {
if (dish != null)
return (
<Card >
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
);
else
return (
<div></div>
);
}
renderComments(comments) {
let list = (<div></div>);
if (comments != null) {
list = (
<ul className="list-unstyled">
{comments.map(c => {
return (
<li key={c.id}>
<p>{c.comment}</p>
<p>-- {c.author}, {new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: '2-digit' }).format(new Date(Date.parse(c.date)))}</p>
</li>
);
})}
</ul>
);
}
return (
<div>
<h4>Comments</h4>
{list}
</div>
);
}
render() {
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">
{/* This works: */}
{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}
{/* This doesn't: */}
{/* { this.renderComments(this.props.selectedDish.comments)} */}
</div>
</div>
);
}
}
When your main component renders for the first time the props are still undefined as DISHES haven't been loaded, reasons for that might be the DISHES are initiated with some external resource.
So when this.props.selectedDish is undefined you can't access it's key because they don't exist. So react throws this error.
{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}
So this ensures this.props.selectedDish exists and then you are accessing it's comments key.
When your component is loaded the first time the selectedDish state value is null. It only gets set later, hence the error.
Changing the below code
constructor() {
super();
this.state = {
dishes: DISHES,
selectedDish: null
};
// this.onDishSelect = this.onDishSelect.bind(this);
}
To
constructor() {
super();
this.state = {
dishes: DISHES,
selectedDish: {}
};
// this.onDishSelect = this.onDishSelect.bind(this);
}
will make it work. However I feel that what you are doing now i.e. null check and then render is a more robust way of doing it.
Just a few pointers , you don't need to have a class based component since you aren't using lifecycle methods etc.
you can use destructuring to reduce typing
let {selectedDish:{comments}} = this.props .
You could solve your problem with a default value for comments ,
`const Dishdetail = ({selectedDishes:{comments}}=[]}) => {}
and conditional rendering in render
<div>
{ comments.length > 0 &&
//map you comments here if you have some
}
</div>`
I am implementing a scrolling functionality on the same page when the Contact Us button is clicked. The Contact Us is contained in a child component (MyNavbar); when clicked, it will scroll to a fragment contained in another child component (MyContactForm), which is sibling of MyNavbar.
Here's the parent component:
// App.js
import React, { Component } from 'react';
import MyNavbar from './components/MyNavbar';
import MyContactForm from './components/MyContactForm';
export default class App extends Component {
constructor(props) {
super(props);
...
}
scrollToContactForm = () => {
this.refs.contactForm.scrollTo();
}
render() {
return (
<main>
<MyNavbar onClickToContactUs={ () => this.scrollToContactForm() } />
<MyContactForm ref="contactForm" />
</main>
);
}
}
And here are the two child components, MyNavbar
// MyNavbar.js
import React, { useState } from 'react';
import { Navbar, Nav, NavItem, NavLink } from 'reactstrap';
const MyNavbar = (props) => {
return (
<Navbar>
<Nav>
...
<NavItem>
<NavLink href="/products/"> Products </NavLink>
</NavItem>
<NavItem>
<NavLink href="/services/"> Services </NavLink>
</NavItem>
<NavItem>
<NavLink onClick={ () => props.onClickToContactUs() } href="#"> Contact Us </NavLink>
</NavItem>
</Nav>
</Navbar>
);
}
export default MyNavbar;
and MyContactForm:
// MyContactForm.js
import React, { Component } from 'react';
import { Form, ... } from 'reactstrap';
export default class MyContactForm extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
...
inquiry: ''
};
this.setEmail = this.setEmail.bind(this);
...
this.setInquiry = this.setInquiry.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.myRef = React.createRef();
}
setEmail(event) {
this.setState( { email: event.target.email } );
}
...
setInquiry(event) {
this.setState( { question: event.target.inquiry } );
}
handleSubmit(event) {
alert("Thank you for contacting us. We will respond to you shortly");
event.preventDefault();
}
scrollTo = () => window.scrollTo(0, this.myRef.current.offsetTop);
render() {
return (
<React.Fragment ref={this.myRef} >
<Form onSubmit={this.handleSubmit}>
...
</Form>
</React.Fragment>
);
}
}
The app runs, however when I click Contact Us, I get a message saying
this.myRef.current is null
How can I get this to work?
Here's what worked for me:
I replaced <React.Fragment> with a <div>. Putting the ref in <Form> doesn't work either, as it should be on a DOM node (HTML element, not React component). So MyContactForm.js becomes:
render() {
return (
<div ref={this.myRef} >
<Form onSubmit={this.handleSubmit}>
...
</Form>
</div>
);
}
I'd like to set PropTypes validation for my React component to warn if there are no children. But when I follow the PropTypes syntax recommended by the React docs, I get this error thrown:
prop type children is invalid; it must be a function, usually from the prop-types package, but received undefined.
When I step through in the Chrome debugger, I see that children is undefined when the validation is performed. But I specifically set children in defaultProps, so why is it undefined? I've tried several different values for defaultProps.children, including [] and ''. All of them produced the same error.
Note that my app works OK. It's only PropTypes validation that is failing.
Below is a simplified version of the problematic code. Note that I'm using static properties to define defaultProps (as recommended by Dan Abramaov). I'm using create-react-app so I have the Babel transform to enable the default class properties syntax.
import React, { Fragment } from 'react';
import PropTypes from 'prop-types'
import { TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import classnames from 'classnames';
export class Tab extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
children: PropTypes.node.required,
}
static defaultProps = {
name: null,
children: ''
}
render () {
return (
<Fragment>
{this.props.children}
</Fragment>
)
}
}
export default class TabSet extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTab: 0
};
}
// TODO: limit to only Tab child elements.
static propTypes = {
children: PropTypes.node.required,
}
static defaultProps = {
children: ''
}
toggle = (tab) => {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render() {
return (
<Fragment>
<Nav tabs>
{this.props.children.map((tab,i) =>
<NavItem key={i} style={{cursor: 'pointer'}}>
<NavLink
className={classnames({ active: this.state.activeTab === i })}
onClick={() => { this.toggle(i); }}
>
{ tab.props.name }
</NavLink>
</NavItem>
)}
</Nav>
<TabContent activeTab={this.state.activeTab}>
{this.props.children.map((tab,i) =>
<TabPane key={i} tabId={i}>
{tab}
</TabPane>
)}
</TabContent>
</Fragment>
);
}
}
If it matters, here's a simplified example of how these components are used:
import React from 'react';
import TabSet, {Tab} from './TabSet';
import HomeTab from './HomeTab';
import FriendsTab from './FriendsTab';
import HangTimesTab from './HangTimesTab';
export default class MainContainer extends React.Component {
render() {
return (
<div>
<TabSet>
<Tab name="Home">
<HomeTab />
</Tab>
<Tab name="Hang Times">
<HangTimesTab />
</Tab>
<Tab name="Friends">
<FriendsTab />
</Tab>
</TabSet>
</div>
);
}
}
The error message is a little confusing, but it refers to the prop-type declaration for children (i.e. PropTypes.node.required), which is undefined because of a typo. It should be PropTypes.node.isRequired.