Passing property values

Properties are like state—they are data that get passed into components. However, properties are different from state in that they're only set once, when the component is rendered. In this section, we'll look at default property values. Then, we'll look at setting property values. After this section, you should be able to grasp the differences between component state and properties.

Default property values

Default property values work a little differently than default state values. They're set as a class property called defaultProps. Let's take a look at a component that declares default property values:

import React, { Component } from 'react'; 
 
export default class MyButton extends Component { 
  // The "defaultProps" values are used when the 
  // same property isn't passed to the JSX element. 
  static defaultProps = { 
    disabled: false, 
    text: 'My Button', 
  } 
 
  render() { 
    // Get the property values we want to render. 
    // In this case, it's the "defaultProps", since 
    // nothing is passed in the JSX. 
    const { disabled, text } = this.props; 
 
    return ( 
      <button disabled={disabled}>{text}</button> 
    ); 
  } 
} 

So, why not just set the default property values as an instance property, like we do with default state? The reason is that properties are immutable, and there's no need for them to be kept as an instance property value. State, on the other hand, changes all the time, so the component needs an instance level reference to it.

You can see that this component sets default property values for disabled and text. These values are only used if they're not passed in through the JSX markup used to render the component. Let's go ahead and render this component without any JSX properties, to make sure that the defaultProps values are used.

import React from 'react'; 
import { render } from 'react-dom'; 
 
import MyButton from './MyButton'; 
 
// Renders the "MyButton" component, without 
// passing any property values. 
render( 
  (<MyButton />), 
  document.getElementById('app') 
); 

The same principle of always having a default state applies with properties. You want to be able to render components without having to know in advance what the dynamic values of the component are. Now, we'll turn our attention to setting property values on React components.

Setting property values

First, let's create a couple components that expect different types of property values.

Note

In Chapter 7, Validating Component Properties, we'll go into more detail on validating the property values that are passed to components.

import React, { Component } from 'react'; 
 
export default class MyButton extends Component { 
 
  // Renders a "<button>" element using values 
  // from "this.props". 
  render() { 
    const { disabled, text } = this.props; 
 
    return ( 
      <button disabled={disabled}>{text}</button> 
    ); 
  } 
} 

This simple button component expects a Boolean disabledproperty and a string text property. Let's create one more component that expects an array property value:

import React, { Component } from 'react'; 
 
export default class MyList extends Component { 
  render() { 
 
    // The "items" property is an array. 
    const { items } = this.props; 
 
    // Maps each item in the array to a list item. 
    return ( 
      <ul> 
        {items.map(i => ( 
          <li key={i}>{i}</li> 
        ))} 
      </ul> 
    ); 
  } 
} 

As you can see, we can pass just about anything we want as a property value via JSX, just as long as it's a valid JavaScript expression. Now let's write some code to set these property values:

import React from 'react'; 
import { render as renderJSX } from 'react-dom'; 
 
// The two components we're to passing props to 
// when they're rendered. 
import MyButton from './MyButton'; 
import MyList from './MyList'; 
 
// This is the "application state". This data changes 
// over time, and we can pass the application data to 
// components as properties. 
const appState = { 
  text: 'My Button', 
  disabled: true, 
  items: [ 
    'First', 
    'Second', 
    'Third', 
  ], 
}; 
 
// Defines our own "render()" function. The "renderJSX()" 
// function is from "react-dom" and does the actual 
// rendering. The reason we're creating our own "render()" 
// function is that it contains the JSX that we want to 
// render, and so we can call it whenever there's new 
// application data. 
function render(props) { 
  renderJSX(( 
    <main> 
      { /* The "MyButton" component relies on the "text" 
           and the "disabed" property. The "text" property 
           is a string while the "disabled" property is a 
           boolean. */ } 
      <MyButton 
        text={props.text} 
        disabled={props.disabled} 
      /> 
 
      { /* The "MyList" component relies on the "items" 
           property, which is an array. Any valid 
           JavaScript data can be passed as a property. */ } 
      <MyList items={props.items} /> 
    </main> 
    ), 
    document.getElementById('app') 
  ); 
} 
 
// Performs the initial rendering... 
render(appState); 
 
// After 1 second, changes some application data, then 
// calls "render()" to re-render the entire structure. 
setTimeout(() => { 
  appState.disabled = false; 
  appState.items.push('Fourth'); 
  render(appState); 
}, 1000); 

The render() function looks like it's creating new React component instances every time it's called. Well, React is smart enough to figure out that these components already exist, and that it only needs to figure out what the difference in output will be with the new property values. React is very powerful—have I alluded to this yet?

Another takeaway from this example is that we have an appState object that holds onto the state of the application. Pieces of this state are then passed into components as properties, when the components are rendered. State has to live somewhere, and in this case, we've moved it outside of the component. We'll build on this topic in the next section, when we implement stateless functional components.