Creating your first stateful React component

Stateful components are the most appropriate place for your application to handle the interaction logic and manage the state. They make it easier for you to reason out how your application works. This reasoning plays a key role in building maintainable web applications.

React stores the component's state in this.state, and it sets the initial value of this.state to the value returned by the getInitialState() function. However, it's up to us to tell React what the getInitialState() function will return. Let's add this function to our React component:

{
  getInitialState: function () {
    return {
      isHidden: false
    };
  },

  render: function () {
    if (this.state.isHidden) {
      return null;
    }

    return React.createElement('h1', { className: 'header' }, 'React Component'),
  }
}

In this example, our getInitialState() function returns an object with a single isHidden property that is set to false. This is the initial state of our React component and our user interface. Notice that in our render() function, we're now referring to this.state.isHidden instead of this.props.isHidden.

Earlier in this chapter, you learned that we can pass data to the component's render() function via this.props or this.state. So, what is the difference between the two?

  • this.props stores read-only data that is passed from the parent. It belongs to the parent and cannot be changed by its children. This data should be considered immutable.
  • this.state stores data that is private to the component. It can be changed by the component. The component will rerender itself when the state is updated.

How do we update a component's state? There is a common way of informing React of a state change using setState(data, callback). This function takes two parameters:

  • The data function that represents the next state
  • The callback function, which you will rarely need to use because React keeps your user interface up to date for you

How does React keep your user interface up to date? It calls the component's render() function every time you update the component's state, including any child components which are rerendered as well. In fact, it rerenders the entire virtual DOM every time our render() function is called.

When you call the this.setState() function and pass it a data object that represents the next state, React will merge that next state with the current state. During the merge, React will overwrite the current state with the next state. The current state that is not overwritten by the next state will become part of the next state.

Imagine that this is our current state:

{
  isHidden: true,
  title: 'Stateful React Component'
}

We call this.setState(nextState) where nextState is as follows:

{
  isHidden: false
}

React will merge the two states into a new one:

{
  isHidden: false,
  title: 'Stateful React Component'
}

The isHidden property is updated and the title property is not deleted or updated in any way.

Now that we know how to update our component's state, let's create a stateful component that reacts to a user event:

{
  getInitialState: function () {
    return {
      isHeaderHidden: false,
      title: 'Stateful React Component'
    };
  },

  handleClick: function () {
    this.setState({
      isHeaderHidden: !this.state.isHeaderHidden
    });
  },

  render: function () {
    var headerElement = React.createElement('h1', { className: 'header', key: 'header' }, this.state.title);
    var buttonElement = React.createElement('button', { className: 'btn btn-default', onClick: this.handleClick, key: 'button' }, 'Toggle header'),

    if (this.state.isHeaderHidden) {
      return React.createElement('div', null, [ buttonElement ]);
    }

    return React.createElement('div', null, [ buttonElement, headerElement ]);
  }
}

In this example, we're creating a toggle button that shows and hides a header. The first thing we do is set our initial state object by returning it to the getInitialState() function. Our initial state has two properties: isHeaderHidden that is set to false and title that is set to 'Stateful React Component'. Now we can access this state object in our render() function via this.state. Inside our render() function, we create three React elements: h1, button, and div. Our div element acts as a parent element for our h1 and button elements. However, in one case we create our div element with two children, headerElement and buttonElement, and in the other case we create it with only one child, buttonElement. The case we choose depends on the value of this.state.isHeaderHidden. The current state of our component directly affects what the render() function will render. While this should look familiar to you, there is something new in this example that we haven't seen before.

Notice that we introduced a new property on our ReactComponent object, called handleClick(), which is a function that has no special meaning to React. It's part of our application logic, and we use it to handle the onClick events. You can add your own properties to the ReactComponent object. All of these will be available via a this reference, which you can access from any other function that itself is a property of the component object. For example, we are accessing a state object via this.state in both the render() and handleClick() functions.

What does our handleClick() function do? It updates our component's state by setting the new value of the isHeaderHidden property to the opposite of the existing one that it accesses via this.state.isHeaderHidden:

this.setState({
  isHeaderHidden: !this.state.isHeaderHidden
});

Our handleClick() function reacts to a user interaction with our user interface. Our user interface is a button element that a user can click on, and we can attach an event handler to it. In React, you can attach event handlers to a React element by passing them to the props parameter in the createElement() function:

React.createElement('button', { className: 'btn btn-default', onClick: this.handleClick }, 'Toggle header'),

React uses the CamelCase naming convention for event handlers; for example, onClick. You can find a list of all the supported events at http://facebook.github.io/react/docs/events.html#supported-events.

By default, React triggers the event handlers in the bubble phase, but you can tell React to trigger them in the capture phase by appending Capture to the event name; for example, onClickCapture.

React wraps a browser's native events into the SyntheticEvent object to ensure that all the supported events behave identically in Internet Explorer 8 and above.

The SyntheticEvent object provides the same API as the native browser's event, which means that you can use the stopPropagation() and preventDefault() methods as usual. If for some reason, you need to access that native browser's event, then you can do this via the nativeEvent property. To enable touch-event handling, simply call React.initializeTouchEvents(true).

Notice that passing the onClick property to our createElement() function in the previous example does not create an inline event handler in the rendered HTML markup:

<button class="btn btn-default" data-reactid=".0.$button">Toggle header</button>

This is because React doesn't actually attach event handlers to the DOM nodes themselves. Instead, React listens for all the events at the top level using a single event listener, and delegates them to their appropriate event handlers.

In the previous example, you learned how to create a stateful React component that a user can interact with and change its state. We created and attached an event handler to the click event that updates the value of the isHeaderHidden property. But have you noticed that the user interaction does not update the value of another property that we store in our state, title? Does that seem odd to you? We have data in our state that doesn't ever get changed. This observation raises an important question; what should we not put in our state?

Ask yourself a question: what data can I remove from a component's state and still keep its user interface always up to date? Keep asking and keep removing that data until you're absolutely certain that there is nothing left to remove without breaking your user interface.

In our example, we have the title property in our state object that we can move to our render() function without breaking the interactivity of our toggle button. The component will still work as expected:

{
  getInitialState: function () {
    return {
      isHeaderHidden: false
    };
  },

  handleClick: function () {
    this.setState({
      isHeaderHidden: !this.state.isHeaderHidden
    });
  },

  render: function () {
    var title = 'Stateful React Component';

    var headerElement = React.createElement('h1', { className: 'header', key: 'header' }, title);
    var buttonElement = React.createElement('button', { className: 'btn btn-default', onClick: this.handleClick, key: 'button' }, 'Toggle header'),

    if (this.state.isHeaderHidden) {
      return React.createElement('div', null, [ buttonElement ]);
    }

    return React.createElement('div', null, [ buttonElement, headerElement ]);
  }
}

On the other hand, if we move the isHeaderHidden property out of a state object, then we'll break the interactivity of our component because our render() function will not be triggered automatically by React every time a user clicks on our button. This is an example of broken interactivity:

{
  getInitialState: function () {
    return {};
  },

  isHeaderHidden: false,

  handleClick: function () {
    this.isHeaderHidden = !this.isHeaderHidden;
  },

  render: function () {
    var title = 'Stateful React Component';

    var headerElement = React.createElement('h1', { className: 'header', key: 'header' }, title);
    var buttonElement = React.createElement('button', { className: 'btn btn-default', onClick: this.handleClick, key: 'button' }, 'Toggle header'),

    if (this.state.isHeaderHidden) {
      return React.createElement('div', null, [ buttonElement ]);
    }

    return React.createElement('div', null, [ buttonElement, headerElement ]);
  }
}

This is an anti-pattern.

Remember this rule of thumb: a component's state should store data that a component's event handlers may change over time in order to rerender a component's user interface and keep it up to date. Keep the minimal possible representation of a component's state in a state object, and compute the rest of the data based on what's in state and props inside a component's render() function. Anything that you put in state, you'll need to update yourself. Anything that you put in render() will automatically get updated by React. Take advantage of React.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.234.225