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.
Related
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 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 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>
)
}
}
im using react with react-redux and react-router. im working on my blog, in which i have a component with shows list of posts. so everything is working fine but when i get post.id in component it gives me undefined. on the other hand posts are passing to component from container.
please look into my code.
//home_container.js
import { connect } from 'react-redux'
import { show } from './actions'
import HomeComponent from './home_component'
const mapStateToProps = (state) => {
return {
posts: state.posts.data
}
}
const mapDispatchToProps = (dispatch) => {
return {
actions:{
showPosts: (page,limit) => {
show(dispatch,page,limit)
}
}
}
}
const HomeContainer = connect(
mapStateToProps,
mapDispatchToProps
)(HomeComponent)
export default HomeContainer
//home_component.js file
import React, {Component} from 'react';
import Paper from 'material-ui/Paper';
import Style from './styles.css'
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import FlatButton from 'material-ui/FlatButton';
import { Link } from 'react-router'
require('rc-pagination/assets/index.css');
const Pagination = require('rc-pagination');
const style = {
height: "100%",
margin: 10,
padding: 10,
display: 'inline-block',
};
var Detail = React.createClass({
render: function() {
return (
<div >
<div className="row">
<div >
<p>{this.props.post?this.props.post.body.substr(1,600):''}</p>
{this.props.post?
<span style={{"float":"right"}}>
<Link to={`/posts/${this.props.post.id}`}>
<FlatButton
label="Ready more"
labelPosition="before"
primary={true}
/>
</Link>
</span>
:''}
</div>
</div>
</div>
)
}
});
class Index extends Component {
getChildContext() {
return { muiTheme: getMuiTheme(baseTheme) };
}
componentDidMount(){
this.props.actions.showPosts(1,5)
}
render() {
return (
<div >
{this.props.posts.map((post,i) =>
<div className="row" key={i}>
<Paper
style={style}
zDepth={0}
children={<Detail post={post}/>}
/>
)}
</div>
);
}
}
Index.childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
};
export default Index;
i already check through react-redux inspector. i have posts in posts props of this component, which are sets by container through react redux.
so problem is in the following part of above code.
<Link to={`/posts/${this.props.post.id}`}>
<FlatButton
label="Ready more"
labelPosition="before"
primary={true}
/>
</Link>
Link to tag of react router generate url in this form posts/undefined. because it is considering that post.id is undefined. on the other hand each post have id property and i also checked it through inspection of posts objects.
so problem is in this line this.props.post.id
I have a TodoList component which is a child of the App component. I wish to change the state of the App component's todos list. I am attempting to pass a toggleComplete function from the TodoList component to the Todo component so at onClick event it would fire and work its way up to the App component so I could update the state.
I get a "Uncaught TypeError: Cannot read property 'toggleComplete' of undefined" in the TodoList.js
~/src/component/Todo.js
import React, {PropTypes} from 'react';
export default class Todo extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<li className={this.props.todo.done ? 'completed' : 'view'}>
<div className="view">
<input onClick={this.props.toggleComplete(this.props.id)} className="toggle" type="checkbox" checked={this.props.todo.done} />
<label>{this.props.id}: {this.props.todo.title}</label>
<button className="destroy"></button>
</div>
</li>
);
}
}
~/src/component/TodoList.js
import React, {PropTypes} from 'react';
import Todo from './Todo'
export default class TodoList extends React.Component {
constructor(props) {
super(props);
}
toggleComplete(todoID){
console.log('hit toggleComplete TodoList');
}
render() {
return (
<section className="main">
<ul className="todo-list">
{this.props.todos.map(function(todo, index){
return <Todo todo={todo} toggleComplete={this.toggleComplete} id={index + 1} key={index+1}/>;
})}
</ul>
</section>
);
}
}
~/src/App.js
import React, { Component } from 'react';
import Header from './component/Header'
import TodoList from './component/TodoList'
import TodoFooter from './component/TodoFooter'
import Footer from './component/Footer'
export default class App extends Component {
constructor(){
super();
this.state = {
todos: [
{title: 'Taste JavaScript', done: true},
{title: 'Buy Unicorn', done: false},
{title: 'eat all day', done: false},
{title: 'sleep all night', done: true}
]
}
}
render() {
return (
<div>
<section className="todoapp">
<Header />
<TodoList todos={this.state.todos} />
<TodoFooter />
</section>
<Footer />
</div>
);
}
}
Your problem seems to occur before the function makes it to your child component, as the error is coming from your parent component. Your map function does not have access to the correct this, so it is treated as undefined -- try this instead:
{this.props.todos.map(function(todo, index){
return <Todo todo={todo} toggleComplete={this.toggleComplete} id={index + 1} key={index+1}/>;
}, this)} // use the optional "this" argument to the map function
And here's a fiddle to play with showing a simple example of parents rendering their many children with the same reference to the parent's scope: https://jsfiddle.net/v5wd6Lrg/1/
The above answer can also be done using arrow function without binding
{this.props.todos.map((todo, index) => {
return <Todo todo={todo} toggleComplete={this.toggleComplete} id={index + 1} key={index+1}/>;
});