[Full Stack React] Day 10: Interactivity

Keywords: Javascript React Attribute github

This article is reproduced from: Mass Translation
Translator: iOSDevLog
Links: http://www.zcfy.cc/article/3823
Original: https://www.fullstackreact.com/30-days-of-react/day-10/

Today, we'll show you how to add interactivity to our applications to make them attractive and interactive.

In this way, we built a few components without adding user interaction. Today we are going to change it.

User Interaction

Browsers are event-driven applications. Everything the user does in the browser triggers an event, from clicking on a button to even just moving the mouse. In simple JavaScript, we can listen to these events and attach a JavaScript function to interact with them.

For example, we can use JS to attach a function to the mousemove browser event:

export const go = () => {
  const ele = document.getElementById('mousemove');
  ele.innerHTML = 'Move your mouse to see the demo';
  ele.addEventListener('mousemove', function(evt) {
    const { screenX, screenY } = evt;
    ele.innerHTML = '<div>Mouse is at: X: ' +
          screenX + ', Y: ' + screenY +
                    '</div>';
  })
}

This leads to the following actions:

Move the mouse over the text

However, in React, we don't have to interact with browser event loops in raw JavaScript, because React provides us with a way to handle events using props.

For example, to listen to the mousemove event from the (rather insignificant) demo above React, we will set onMouseMove (note that the event name is named after the hump).

<div onMouseMove={(evt) => console.log(evt)}>
  Move the mouse
</div>

React provides many props, and we can set up to listen for different browser events, such as clicking, touching, dragging, scrolling, selecting events, and so on (see Event The document lists all of these.

To see some of them in action, here are some small demonstrations, some props, we can pass our elements. Each text element in the list sets the properties it lists. Try using lists to see how events are invoked and handled in elements.

We'll use the onClick attribute quite a lot in our applications, so it's a good idea to be familiar with it. In the title of our activity list, we have a search icon, and we have not yet associated it with displaying a search box.

Our want interaction is to display the search when the user clicks on the search icon. Recall that our Header component is implemented as follows:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(' ')}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

When the user clicks on the <div className= "fa-search Icon"</div> element, we need to run a function to update the state of the component so that the searchInputClasses object can be updated. Using onClick handlers, this is simple.

We make this component stateful (it needs to track whether the search field should be displayed). We can use the constructor() function (constructor) to transform our components into states:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }
  // ...
}

What is a constructor function?

In JavaScript, the constructor function is a function that runs when an object is created. It returns a reference to the Object function of the prototype that created the instance.

In plain English, constructors are functions that run when JavaScript runs to create new objects. We will use the constructor method to set instance variables when the object is created correctly.

When creating objects using ES6 class syntax, we must call the super() method before any other method. Call the super() function to call the constructor() function of the parent class. We'll call it with the same parameter, because the constructor() function of our class is called.

When the user clicks the button, we will update the status to indicate that the search Visible flag is updated. Since we want the user to close/hide the < input /> field after clicking on the search icon for the second time, we switch _to this state instead of setting it to true.

We created this method to bind our click events:

class Header extends React.Component {
  // ...
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }
  // ...
}

Finally, we can attach a click handler (using the onClick attribute) to the icon element to invoke our new showSearch() method. The whole updated source code for our Header component is as follows:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="fa fa-more"></div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(' ')}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

Try clicking on the search icon and watching the input fields appear and disappear (the animation effect is handled by CSS animation).

Input events

Whenever a form is built in React, we will use the input events provided by React. Most notably, we use the onSubmit() and onChange() attributes most often.

We update our search box demo to capture the text in the search field when updating. Whenever an < input /> onChange() property is set, it calls the function at every time in the field _change (). When we click on it and start typing, the function will be called.

Using this property, we can capture the value of our field.

Let's create a new subcomponent to contain a < form /> element instead of updating our < Header /> component. By shifting form processing responsibilities to our own forms, we can simplify the < Header /> code and call the parent component of the header file when our user submits the form, which is a common response mode.

We create a new component we call SearchForm. This new component is a stateful component because we need to keep the search input value (track its changes):

class SearchForm extends React.Component {
  // ...
  constructor(props) {
    super(props);

    this.state = {
      searchText: ''
    }
  }
  // ...
}

Now that we have written the HTML of the form in the < Header /> component, let's get it from our Header component and return it from our SearchForm.render() function:

class SearchForm extends React.Component {
  // ...
  render() {
    const { searchVisible } = this.state;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form className='header'>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />

        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </form>
    );
  }
}

Note that we lost the style in our < input /> field. Since we no longer have the search Visible state in our new <input/> component, we can no longer use it to stylize it. _ In any case, we can pass a support from our Header component, which tells SearchForm to render the input visible.

We define the search Visible property (using React.PropTypes, of course) and update the render function to display (or hide) the search < input /> using the new prop value. We will also set a default value of false for field visibility (because our Header displays/hides it well):

class SearchForm extends React.Component {
  static propTypes = {
    onSubmit: React.PropTypes.func.isRequired,
    searchVisible: React.PropTypes.bool
  }
  // ...
}

Now that we have our style on the < input /> element, let's add the function that the user typed in the search box, and we're going to capture the value of the search field. We can implement this workflow by attaching the onChange parameter to the < input /> element and passing a function to call it every time the < input /> element is changed.

class SearchForm extends React.Component {
  // ...
  updateSearchInput(e) {
    const val = e.target.value;
    this.setState({
      searchText: val
    });
  }
  // ...
  render() {
    const { searchVisible } = this.state;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form className='header'>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />

        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </form>
    );
  }
}

When we type a field, the updateSearchInput() function is called. We will track the value of the form by updating the status. In the updateSearchInput() function, we can call this.setState() directly to update the state of the component.

The value is saved as `event.target.value'on the target of the event object.

class SearchForm extends React.Component {
  // ...
  updateSearchInput(e) {
    const val = e.target.value;
    this.setState({
      searchText: val
    });
  }
  // ...
}

Control and Uncontrolled

We are creating so-called uncontrolled components because we do not set the value of the < input /> element. We cannot now provide any validation or post-processing of the input text values.

If we want to validate a field or manipulate the value of the < input /> component, we will have to create a so-called control component, which really just means that we pass a value attribute using value'. The render()`function of the controlled component version will be as follows:

class SearchForm extends React.Component {
  render() {
    return (
      <input
        type="search"
        value={this.state.searchText}
        className={searchInputClasses}
        onChange={this.updateSearchInput.bind(this)}
        placeholder="Search ..." />
    );
  }
} 

So far, we can't really submit forms, so our users can't really search. Let's change that we need to include component in a DOM element so that our users can submit the form by pressing the Enter key. We can use onSubmit support on the < form /> element to capture form submission.

Let's update the render() function to reflect this change.

class SearchForm extends React.Component {
  // ...
  submitForm(e) {
    e.preventDefault();

    const {searchText} = this.state;
    this.props.onSubmit(searchText);
  }
  // ...
  render() {
    const { searchVisible } = this.props;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form onSubmit={this.submitForm.bind(this)}>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />
      </form>
    );
  }
}

We immediately call event.preventDefault() on the submitForm() function. This prevents the browser from bubbling, thus reloading the default behavior of the entire page (the default function of the browser when submitting the form).

Now when we type in the < input /> field and press the Enter key, the submitForm() function is called as the event object.

Well, we can submit forms and content, but when do we actually search? For demonstration purposes, we will pass the search text to the parent-child component chain so that the Header can decide what to search for.

The SearchForm component certainly does not know what it is searching for, so we have to pass responsibility to the chain. We will use this callback strategy.

In order to pass the search function to the chain, our "SearchForm" will need to accept the functions called when submitting the form. Let's define an attribute we call SearchForm, which we can pass to our SearchForm component. As good developers, we will also add default prop values and propType s for this onSubmit function. Because we want to make sure that onSubmit() is defined, we will set the prop of onSubmit as a required parameter:

class SearchForm extends React.Component {
  static propTypes = {
    onSubmit: React.PropTypes.func.isRequired,
    searchVisible: React.PropTypes.bool
  }
  // ...
  static defaultProps = {
    onSubmit: () => {},
    searchVisible: false
  }
  // ...
}

When the form is submitted, we can call this function directly from props. Since we are tracking the search text in our state, we can call the function using the searchText value in that state, so the onSubmit() function can only get values and does not need to handle events.

class SearchForm extends React.Component {
  // ...
  submitForm(event) {
    // prevent the form from reloading the entire page
    event.preventDefault();
    // call the callback with the search value
    this.props.onSubmit(this.state.searchText);
  }
}

Now, when the user presses enter, we can call the onSubmit() function passed in props through our Header component.

We can use this SearchForm component in our Header component and pass the two properties we defined (searchVisible and onSubmit):

import React from 'react';
import SearchForm from './SearchFormWithSubmit'

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <SearchForm
          searchVisible={this.state.searchVisible}
          onSubmit={this.props.onSubmit} />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

export default Header

Now we have a search form component that can be used and reused in our applications. Of course, we haven't searched for anything yet. Let's solve this problem and achieve search.

[] (# implementation-search) implements search

To implement search in our components, we want to pass search responsibility from our Header component to the container component, which we call Panel.

First, let's implement a pattern that passes callbacks to the parent component in a child component from the Panel container to the Header component.

On the Header component, let's update the propTypes of an attribute, which we define as the onSearch attribute:

class Header extends React.Component {
  // ...
}
Header.propTypes = {
  onSearch: React.PropTypes.func
}

In the'submitForm()'function of the Header component, call the onSearch() property, and we will pass it in:

class Header extends React.Component {
  // ...
  submitForm(val) {
    this.props.onSearch(val);
  }
  // ...
}
Header.propTypes = {
  onSearch: React.PropTypes.func
}

Note that our virtual tree is as follows:

<Panel>
  <Header>
    <SearchForm></SearchForm>
  </Header>
</Panel> 

When < SearchForm /> is updated, it conveys its awareness, searches for changes in input to its parent component < Header />, and when it is passed up to the < Panel /> component. This method is _very common_ in React applications and provides a good set of functional isolation for our components.

Back in the Panel component we built on the seventh day, we will pass a function to the Header as the onSearch() attribute of the Header. What we're saying here is that when submitting a search form, we want the search form to call back to the header component and then call the Panel component to process the search.

Since the Header component can't control the content list, the Panel component can be defined here as we do here, and we must pass more responsibility to them.

In any case, our Panel component is essentially a copy of the Content component we used before:

class Panel extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false, // <~ set loading to false
      activities: data,
      filtered: data,
    }
  }

  componentDidMount() {this.updateData();}
  componentWillReceiveProps(nextProps) {
    // Check to see if the requestRefresh prop has changed
    if (nextProps.requestRefresh !== this.props.requestRefresh) {
      this.setState({loading: true}, this.updateData);
    }
  }

  handleSearch = txt => {
    if (txt === '') {
      this.setState({
        filtered: this.state.activities
      })
    } else {
      const { activities } = this.state
      const filtered = activities.filter(a => a.actor && a.actor.login.match(txt))
      this.setState({
        filtered
      })
    }
  }

  // Call out to github and refresh directory
  updateData() {
    this.setState({
      loading: false,
      activities: data
    }, this.props.onComponentRefresh);
  }

  render() {
    const {loading, filtered} = this.state;

    return (
      <div>
        <Header
          onSubmit={this.handleSearch}
          title="Github activity" />
        <div className="content">
          <div className="line"></div>
          {/* Show loading message if loading */}
          {loading && <div>Loading</div>}
          {/* Timeline item */}
          {filtered.map((activity) => (
            <ActivityItem
              key={activity.id}
              activity={activity} />
          ))}

        </div>
      </div>
    )
  }
}

We update our status to include a searchFilter string, which will only be the search value:

class Panel extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      searchFilter: '',
      activities: []
    }
  }
}

To actually handle the search, we need to pass the onSearch() function to our Header component. We define an onSearch() function in our Panel component and pass it to the Header attribute in the render() function.

class Panel extends React.Component {
  // ...
  // after the content has refreshed, we want to 
  // reset the loading variable
  onComponentRefresh() {this.setState({loading: false});}

  handleSearch(val) {
    // handle search here
  }

  render() {
    const {loading} = this.state;

    return (
      <div>
        <Header
          onSearch={this.handleSearch.bind(this)} 
          title="Github activity" />
        <Content 
          requestRefresh={loading}
          onComponentRefresh={this.onComponentRefresh.bind(this)}
          fetchData={this.updateData.bind(this)} />
      </div>
    )
  }
}

What we're doing here is adding a handleSearch() function and passing it to the title. Now when the user types in the search box, the handleSearch() function on our Panel component will be called.

In order to implement search, we need to track this string and update our updateData() function to consider search filtering. First let's set searchFilter to state. We can also force content to reload data by setting the load to true, so we can do this in one step:

class Panel extends React.Component {
  // ...
  handleSearch(val) {
    this.setState({
      searchFilter: val,
      loading: true
    });
  }
  // ...
}

Finally, we update our updateData() function to consider search accounts.

class SearchableContent extends React.Component {
  // ...
      this.setState({loading: true}, this.updateData);
  // ...
}

Although this may seem complicated, it is virtually identical to our existing updateData() function, except that we update our fetch() results to call the filter() method on the json collection.

All collection.filter() functions run functions passed by each element, and filter _out_returns the value of the forged value, retaining the value returned to the true value. Our search function only finds matches on actor.login (Github user) of Github activity to see if it matches the searchFilter value correctly.

With the update of updateData(), our search is complete.

Try searching for auser.

Now we have a three-tier application component to handle the search of nested sub-components. We jumped from the initial stage to the intermediate stage through this post. Depend on yourself. These are some important materials. Make sure you understand this, because we will often use the concepts we introduced today.

Posted by press711 on Fri, 07 Jun 2019 12:06:52 -0700