The latest version of this text can be found at https://info340.github.io/.

Chapter 18 Interactive React

This chapter describes how React components can be used to build interactive and robust applications. It describes how you can store information in a Component’s state, assign event handlers, and leverage the component lifecycle to trigger re-rendering of your application.

18.1 State

So far, we’ve been discussing how components can receive properties to determine their visual rendering. It’s put nicely in this write-up:

props (short for properties) are a Component’s configuration, its options if you may. They are received from above and immutable as far as the Component receiving them is concerned. A Component cannot change its props, but it is responsible for putting together the props of its child Components.

So, we use props to pass data into components which render that data. However, if we want to begin building dynamic applications, we’ll need to use component state to track change in the state of the application (usually driven by a user).

A really nice overview of the difference between props and state is in this article, which points out a few key ideas:

  • “State is reserved only for interactivity, that is, data that changes over time”
  • Keep your state as minimal as possible, and compute information from that state

So, for example, if you wanted to have a searchable list, that component would recieve the list items as an array (which would not change) – this means that it is part of props. However, the search string will change, so we can store the search string in state, and compute the filtered list in our render() function.

Note: one of the most difficult parts of architecting a React application is figuring out what information should be stored as props or state (and in which Components). We suggest you practice this using simple examples, and think carefully about it as you build more robust applications.

We can begin by setting an initial state in the class’s constructor

// Component for searching a table
class SearchableTable extends React.Component {
    // Set an initial state: it will then be accessible as this.state.NAME
    constructor(props) {
        super(props); // exposes props to this.props in the constructor
        this.state = {
            search:''
        };
    }
    render() {
        // Use searchstring to filter data
        ...

        // Render a table in here
        return(...dom elements ...);
        ...
    }
});

If we were building a searchable table, the only thing that changes, and therefore the only data we need to store in state, is the search string – we can then filter down our data in the render function.

18.2 Component Lifecycle

The true benefit of using React components to build your application is that lifecycle events trigger a re-rendering of the component. As a result, you no longer need to concern yourself with assigning event handlers to update your DOM. Instead, you simply need to change the data (state or props) of your component, and React will handle the re-rendering.

As described in the documentation, there are a set of events that are triggered when a component is mounted on the DOM, when the component’s data is updated, and when the component unmounts. Each one of the lifecycle events will be triggered automatically at the appropriate time, and you can define the desired behavior as a method on your class. For example, it’s quite common to load data when the component is mounted. Here’s an example using the d3-request package to load a .csv file.

// Import the csv loading function and Component
import {Component} from 'react';
import { csv } from 'd3-request';

// Define class App by extending Component
class App extends Component {
    constructor(props) {
        super(props);

        // Set initial data as an empty array
        this.state = {
            data:[]
        }
    }

    // Load data when component mounts: this will trigger automatically
    componentDidMount() {
        // Use the csv method to load the data, then set the state
        csv('data/all_data.csv', (error, data) => {

            // Set the stated: this will trigger a re-rendering of the app
            this.setState({
                data: data
            });
        })
    }

    // render method
    render() {
        // Do something with your data in here....
        return <div>My App</div>
    }
}

In the above code, the componentDidMount method will trigger when the component is mounted to the dom. In the method, the state will be updated (this.setState()), which will in turn re-render the App component. You can write custom event handlers for any of the lifecycle methods, enabling you to have granular control over your application.

18.3 Events

Event handlers are assigned in React in the same way that you would assign them to an HTML element using the event name of your choice. Inside of a .jsx file, we could leverage the curly braces {} inside an HTML section to reference a JavaScript function.

<input onChange={functionName} />

What we need to figure out is what to do in that function. Rather than trying to update elements ourselves, we can simply update the state, and let React take care of the rest. When we change state or props, React will re-render our components. Continuing with our example of a searchable table, we could define a function as part of our component that changes it’s state, and then our UI will be re-rendered:

// Component for searching a table
class SearchApp extends React.Component {
    // Set an initial state: it will then be accessible as this.state.NAME
    constructor(porps) {
        super(props);
        this.state = {
            search:''
        };
    },
    // Define a `filter` function to be executed when the input element changes
    update(event) {
        // Get event value
        let value = event.target.value;

        // Change state
        this.setState({search:value});
    }
    render() {
        // Use searchstring to filter data
        ...

        // Render a table in here
        return(
            <input onChange={this.update} />
            ...other dom elements...
        );
        ...
    }
});

In the above section, the filter function (which we define – this is not a default React function) will be executed when the <input> element changes value. The filter function then set’s the state using this.setState({key:value}). Note do not try setting the state directly (i.e., this.state.searchString = 'something'). By setting the state, you will trigger an update, and React will re-render your DOM. For more information on events that trigger a re-render, see this State and Lifecycle article. For more information on events, see here.

18.4 Lifting Up State

In architecting a React application, you’ll need to make the following design choices:

  • What information is tracked as props v.s. state?
  • Which components track the state v.s simply receiving props?
  • How can you pass information across components?

A simple model for React applications is to have a singe <App> Component that tracks the state. That application will pass all information to the other components via props. However, this raises a difficult question:

Given React’s one-directional data flow, how can you pass information back to the <App> from a child Component?

For example, if we are building a SearchApp that is a searchable table, it may have an <UserInput> Component where the user is typing:

class SearchApp extends React.Component {
    render() {
        return (
            <div>
                <UserInput/>
                <Table/>
            </div>
        )
    }
}

The process of passing information back up to the SearchApp from the UserInput component is know as Lifting State Up. To accomplish this, we’ll need to pass an event handler from our parent (SearchApp) to the child (UserInput). This will allow the parent to register changes when the child experiences events. For example, a UserInput element could be structured to recieve a function as props that it will execute onChange:

class UserInput extends React.Component {
    render() {
        return (
            <div>
                <input onChange={this.props.update}/>
            </div>
        )
    }
}

The UserInput is build to be flexible: it will do whatever function is passed in as update. So, we can define a function in our SearchApp and pass it to the UserInput as a property:

class SearchApp extends React.Component {
    handleChange(event) {
        // Get event value
        let searchValue = event.target.value;

        // Set the state to trigger a re-rendering
        this.setState({search:searchValue})
    }
    render() {
        // Set the `update` property of the `UserInput` element
        return (
            <div>
                <UserInput update={this.handleChange}/>
                <Table/>
            </div>
        )
    }
}

To see a working example of this application, see this codepen.