I use the code below to set default props on a React component but it doesn't work. In the render() method, I can see the output "undefined props" was printed on the browser console. How can I define a default value for the component props?
export default class AddAddressComponent extends Component {
render() {
let {provinceList,cityList} = this.props
if(cityList === undefined || provinceList === undefined){
console.log('undefined props')
}
...
}
AddAddressComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
}
AddAddressComponent.propTypes = {
userInfo: React.PropTypes.object,
cityList: PropTypes.array.isRequired,
provinceList: PropTypes.array.isRequired,
}
You forgot to close the Class bracket.
class AddAddressComponent extends React.Component {
render() {
let {provinceList,cityList} = this.props
if(cityList === undefined || provinceList === undefined){
console.log('undefined props')
} else {
console.log('defined props')
}
return (
<div>rendered</div>
)
}
}
AddAddressComponent.contextTypes = {
router: React.PropTypes.object.isRequired
};
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
};
AddAddressComponent.propTypes = {
userInfo: React.PropTypes.object,
cityList: React.PropTypes.array.isRequired,
provinceList: React.PropTypes.array.isRequired,
}
ReactDOM.render(
<AddAddressComponent />,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app" />
For those using something like babel stage-2 or transform-class-properties:
import React, { PropTypes, Component } from 'react';
export default class ExampleComponent extends Component {
static contextTypes = {
// some context types
};
static propTypes = {
prop1: PropTypes.object
};
static defaultProps = {
prop1: { foobar: 'foobar' }
};
...
}
Update
As of React v15.5, PropTypes was moved out of the main React Package (link):
import PropTypes from 'prop-types';
Edit
As pointed out by #johndodo, static class properties are actually not a part of the ES7 spec, but rather are currently only supported by babel. Updated to reflect that.
First you need to separate your class from the further extensions ex you cannot extend AddAddressComponent.defaultProps within the class instead move it outside.
I will also recommend you to read about the Constructor and React's lifecycle: see Component Specs and Lifecycle
Here is what you want:
import PropTypes from 'prop-types';
class AddAddressComponent extends React.Component {
render() {
let { provinceList, cityList } = this.props;
if(cityList === undefined || provinceList === undefined){
console.log('undefined props');
}
}
}
AddAddressComponent.contextTypes = {
router: PropTypes.object.isRequired
};
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
};
AddAddressComponent.propTypes = {
userInfo: PropTypes.object,
cityList: PropTypes.array.isRequired,
provinceList: PropTypes.array.isRequired,
}
export default AddAddressComponent;
If you're using a functional component, you can define defaults in the destructuring assignment, like so:
export default ({ children, id="menu", side="left", image={menu} }) => {
...
};
You can also use Destructuring assignment.
class AddAddressComponent extends React.Component {
render() {
const {
province="insertDefaultValueHere1",
city="insertDefaultValueHere2"
} = this.props
return (
<div>{province}</div>
<div>{city}</div>
)
}
}
I like this approach as you don't need to write much code.
use a static defaultProps like:
export default class AddAddressComponent extends Component {
static defaultProps = {
provinceList: [],
cityList: []
}
render() {
let {provinceList,cityList} = this.props
if(cityList === undefined || provinceList === undefined){
console.log('undefined props')
}
...
}
AddAddressComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
}
AddAddressComponent.propTypes = {
userInfo: React.PropTypes.object,
cityList: PropTypes.array.isRequired,
provinceList: PropTypes.array.isRequired,
}
Taken from:
https://github.com/facebook/react-native/issues/1772
If you wish to check the types, see how to use PropTypes in treyhakanson's or Ilan Hasanov's answer, or review the many answers in the above link.
You can set the default props using the class name as shown below.
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// Specifies the default values for props:
Greeting.defaultProps = {
name: 'Stranger'
};
You can use the React's recommended way from this link for more info
For a function type prop you can use the following code:
AddAddressComponent.defaultProps = {
callBackHandler: () => {}
};
AddAddressComponent.propTypes = {
callBackHandler: PropTypes.func,
};
class Example extends React.Component {
render() {
return <h1>{this.props.text}</h1>;
}
}
Example.defaultProps = { text: 'yo' };
Related
When the component renders it has some children components and when i log props in the formgroup it looks like what's below.
{$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
import React from 'react';
class FormGroup extends React.Component {
constructor(props) {
super(props)
this.state = {
value: {}
}
}
setValue() {
//do nothing at the moment
}
render() {
return (<div className="formGroup" >
{
this.props.children.map(child => {
child.props.setValue = this.setValue;
console.log('child', child)
// does not change
return child;
})
}
</div>)
}
};
export default FormGroup;
You can utilize React Children and cloneElement API to manipulate children's props.
import React from 'react';
class FormGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {},
};
}
setValue() {
//do nothing at the moment
}
render() {
const { children } = this.props;
return (
<div className="formGroup">
{React.Children.map(children, (child) =>
React.cloneElement(child, {
...child.props,
setValue: this.setValue,
})
)}
</div>
);
}
}
export default FormGroup;
In this component I get a Promise object in the properties, I try to put it in state, but when the view is rendered, I get the message "TypeError: Cannot read property 'vote' of null", asking for a solution to my problem, I spent two hours on it and I don't see the end. What should I do differently?
import { IVoteDetailsProps } from "./IVoteDetailsProps";
import { IVoteDetailsState } from "./IVoteDetailsState";
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>{
constructor(props: IVoteDetailsProps) {
super();
console.log(props)
}
componentDidMount() {
let data = this.props.voteDetails;
data.then(result => this.setState({
vote: result
}));
};
public render(): React.ReactElement<IVoteDetailsState> {
return (
<table >
<tbody>
{this.state.vote && this.state.vote.map(el => {
<tr id={el.id.toString()}>
<td>{el.title}</td>
<td>{el.voteType}</td>
</tr>
})}
</tbody>
</table>
)
}
}
export interface IVoteDetailsProps {
voteDetails: Promise<IVoteDetailsData[]>;
}
export interface IVoteDetailsData{
id: number;
title: string;
voteType: string;
}
import React = require("react");
import { VoteDetails } from "../VoteDetails/VoteDetails";
import { IVoteListProps } from "./IVoteListProps";
export class VoteList extends React.Component<IVoteListProps, {}> {
constructor(props: IVoteListProps) {
super(props);
console.log(props)
}
public render(): React.ReactElement<IVoteListProps> {
// const { vote } = this.state;
return (
<VoteDetails voteDetails={this.props.adminServicePanel.getVotesInfo()} />
)
};
}
public render(): React.ReactElement<IVoteSecurityAppProps> {
return (
<main className="ui main text container">
<VoteList adminServicePanel={this.props.adminPanelService}/>
</main>
);
import {HttpClient} from '#microsoft/sp-http';
import { reject } from 'lodash';
import {IAdminPanelService} from './IAdminPanelService';
import {IReportData} from './IReportData'
import { IVoteDetailsData } from './IVoteDetailsData';
import {IVoteInfo} from './IVoteInfo'
import {VoteOptions} from './VoteOptions';
export class AdminPanelService implements IAdminPanelService {
//////////////////////////////MOCK////////////////////////////////////////////
private voteInfos : IVoteDetailsData[];
private reportData : IReportData[];
//////////////////////////////MOCK////////////////////////////////////////////
constructor(private httpClient: HttpClient, private serverRelativeSiteUrl: string) {
//MOCK
this.voteInfos = [
{
id : 1,
title : "xxx",
voteType : "xx"
},
{
id : 2,
title : "xxx",
voteType : "xxx"
}
];
}
public getVotesInfo () : Promise<IVoteDetailsData[]> {
return new Promise<IVoteDetailsData[]>((resolve : (voteMiniInfo : IVoteDetailsData[]) => void, reject : (error: any) => void): void =>{
resolve(this.voteInfos);
})
}
}
export interface IAdminPanelService {
getVotesInfo:() => Promise<IVoteDetailsData[]>;
}
import * as React from 'react';
import styles from './VoteSecurityApp.module.scss';
import { IVoteSecurityAppProps } from './IVoteSecurityAppProps';
import { escape } from '#microsoft/sp-lodash-subset';
import { VoteList } from './VoteList/VoteList';
export default class VoteSecurityApp extends React.Component<IVoteSecurityAppProps, {}> {
public render(): React.ReactElement<IVoteSecurityAppProps> {
return (
<main className="ui main text container">
<VoteList adminServicePanel={this.props.adminPanelService}/>
</main>
);
}
}
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>
{
state = {
vote: null,
}
// change this
componentDidMount() {
this.props.voteDetails().then(result => this.setState({
vote: result
}));
};
// rest of your codes here
}
export class VoteList extends React.Component<IVoteListProps, {}> {
constructor(props: IVoteListProps) {
super(props);
console.log(props)
}
public render(): React.ReactElement<IVoteListProps> {
// const { vote } = this.state;
return (
<VoteDetails voteDetails=
{this.props.adminServicePanel.getVotesInfo} /> // change this line
)
};
}
All errors "TypeError: Cannot read property '......' of null" in spfx components, when you call
this.state.{varname}
or
this.props.{varname}
solves one of:
Add binding 'this' in constructor to method where rise error
this.{methodname} = this.{methodname}.bind(this)
You miss initialize state in constructor (for React.Component<props,state>)
this.state = {};
You use value from props or state and miss check it for null
In question I see all of this things. For example, in this peice of code state will not be initialized, otherwise component has state
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>{
constructor(props: IVoteListProps) {
super(props);
console.log(props)
//this.state == null - true
}
}
Second problem is this code
<VoteDetails voteDetails={this.props.adminServicePanel.getVotesInfo()}
getVotesInfo- return promise, not data. This bad practice, use state to hold data, for example
constructor(props: ...){
super(props);
this.state{
data: null
};
this._getData = this._getData.bind(this);
}
componentDidMount(){
this._getData();
}
async _getData(){
if(this.props.adminServicePanel){
let data = await this.props.adminServicePanel.getVotesInfo();
this.setStae({data});
}
}
render():...{
const data = this.state.data;
return(
{data && data.map(...)}
);
}
want to update the title present in the Header
everytime we navigate to a new page.
Is passing the _setTitle method with the props the way to go?
import React, { Component } from 'react';
import PropTypes from 'prop-types';
Main App component
export class App extends Component {
constructor() {
super();
this.state = { pageTitle: 'My app' };
this._setTitle = this._setTitle.bind(this);
}
_setTitle(title) {
this.setState({ pageTitle: title });
}
render() {
const { pageTitle } = this.state.pageTitle;
return (
<div>
<Header title={pageTitle} />
{React.cloneElement(children, { setTitle: this._setTitle })}
<Footer />
</div>
);
}
}
Header and Footer Components
export class Header extends Component {
static propTypes = {
title: PropTypes.string
};
// ...
render() {
const { title } = this.props;
return <h2>{title}</h2>;
}
}
export class Footer extends Component {
// Footer code
}
Following are the different page Components:
export class Profile extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('Profile');
}
}
export class Projects extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('Projects');
}
// ...
}
export class ProjectForm extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('New Project');
}
// ...
}
export class Translators extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('Translators');
}
// ...
}
// ...
How can I improve upon this. I'm new to react so pls suggest If you have any ideas, I'll implement it. Thank you.
You can make use of Context and pass the setTitle method as a Context value, then you can create a Component that has the logic of setting the Title, A simple implementation would look like
const TitleContext = React.createContext();
export class App extends Component {
constructor() {
super();
this.state = { pageTitle: 'My app' };
this._setTitle = this._setTitle.bind(this);
}
_setTitle(title) {
this.setState({ pageTitle: title });
}
render() {
const { pageTitle } = this.state.pageTitle;
return (
<TitleContext.Provider value={{setTitle: this._setTitle}}
<div>
<Header title={pageTitle} />
{this.props.children}
<Footer />
</div>
</TitleContext.Provider>
);
}
}
class TitleSetter extends React.Component {
static propTypes = {
title: PropTypes.string.isRequired
}
componentDidMount() {
this.context.setTitle(this.props.title)
}
}
TitleSetter.contextTypes = TitleContext;
Now in any component you can simply render the TitleSetter like
export class Profile extends Component {
render() {
return (
<div>
<TitleSetter title="Profile" />
{/* other context */}
</div>
)
}
}
Also while looking into context, please look at the this question on how to access context outside of render
For my component, I set a context
ManageCoursePage.contextTypes = {
router: PropTypes.object
};
How does my class method know that I am referencing react router to automatically redirect me to another URL?
this.context.router.push('/courses');
Here is my component code:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as courseActions from '../../actions/courseActions';
import CourseForm from './courseForm';
class ManageCoursePage extends React.Component {
constructor(props, context) {
super(props, context);
// set up local state
this.state = {
errors: {},
course: Object.assign({}, this.props.course)
};
this.updateCourseState = this.updateCourseState.bind(this);
this.saveCourse = this.saveCourse.bind(this);
}
updateCourseState(event) {
const field = event.target.name;
let course = this.state.course;
course[field] = event.target.value;
return this.setState({
course: course
});
}
saveCourse(event) {
event.preventDefault();
this.props.actions.saveCourse(this.state.course);
this.context.router.push('/courses');
}
render() {
return (
<CourseForm
allAuthors={ this.props.authors }
onChange={ this.updateCourseState }
onSave={ this.saveCourse }
course={ this.state.course }
errors={ this.state.errors }
/>
);
}
}
ManageCoursePage.propTypes = {
// myProp: PropTypes.string.isRequired
course: PropTypes.object.isRequired,
authors: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
// Pull in the React Router context so router is available on this.context.router
// basically a global variable to make it easy for other components to get data easily
ManageCoursePage.contextTypes = {
router: PropTypes.object
};
function mapStateToProps(state, ownProps) {
// empty course
let course = {
id: "",
watchHref: "",
title: "",
authorId: "",
length: "",
category: ""
};
const authorsFormattedForDropDown = state.authors.map(author => {
return {
value: author.id,
text: author.firstName + " " + author.lastName
};
});
return {
course: course,
authors: authorsFormattedForDropDown
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ManageCoursePage);
Very interesting question. :)
It works because router is defined as a childContext type in the react-router library. getChildContext will make this accessible in inside the application if you map contextTypes in a component.
This is helpful in many ways to avoid deeply passing the props from a parent component to deep child component.
Refer this in react-router library https://github.com/reactjs/react-router/blob/master/modules/RouterContext.js#L36
And also the documentation https://facebook.github.io/react/docs/context.html
I manage to pass context through children but only once. Context is never updated.
Yet I have seen many examples working like that, including react docs: https://facebook.github.io/react/docs/context.html
Here is my code:
Parent Component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
window:{
height:null,
width:null
}
};
}
getChildContext() {
return {
window: this.state.window
}
}
componentDidMount () {
window.addEventListener('resize', this.handleResize.bind(this));
this.handleResize();
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize.bind(this));
}
handleResize (){
this.setState({
window:{
width:window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth,
height:window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight
}
});
}
render() {
console.log(this.state.window);
// --> working
return (
{this.props.children}
);
}
}
App.propTypes = {
children: React.PropTypes.node.isRequired
};
App.childContextTypes = {
window: React.PropTypes.object
}
export default App;
Child Component:
class Child extends React.Component {
constructor(props, context) {
super(props);
this.state = {};
}
render () {
console.log(this.context.window);
// --> passed on first render, but never updated
return (
...
)
}
}
Child.contextTypes = {
window: React.PropTypes.object.isRequired
};
export default Child
Am i missing something?