React - Ajax - axios - configure agent to solve cross domain problems - message subscription / publishing mechanism - PubSubJS - Fetch - github user search case

Posted by drawmack on Wed, 05 Jan 2022 08:56:46 +0100

1, Understand

1. Pre description

  1. React itself only focuses on the interface and does not contain the code to send ajax requests
  2. The front-end application needs to interact with the background through ajax requests (json data)
  3. Third party ajax libraries (or self encapsulation) need to be integrated in React applications

2. Common ajax request Libraries

  1. jQuery: it's heavy. It's not recommended if you need to introduce it separately
  2. axios: lightweight, recommended
    ① ajax that encapsulates the XmlHttpRequest object
    ② promise style
    ③ It can be used on the browser side and node server side

2, axios

Installing axios

npm install axios

1. Documentation

https://github.com/axios/axios

2. Related API

axios(config): a general / essential way to send any type of request
axios(url[, config]): you can only specify the url to send a get request
axios.request(config): equivalent to axios(config)
axios.get(url[, config]): Send a get request
axios.delete(url[, config]): Send a delete request
axios.post(url[, data, config]): Send a post request
axios.put(url[, data, config]): Send a put request

axios.defaults.xxx: requested default global configuration (method \ baseurl \ parameters \ timeout...)
axios.interceptors.request.use(): add request interceptor
axios.interceptors.response.use(): add a response interceptor

axios.create([config]): create a new Axios (it does not have the following functions)

axios.Cancel(): the error object used to create the cancel request
axios.CancelToken(): a token object used to create a cancellation request
axios.isCancel(): is it an error to cancel the request
axios.all(promises): used to batch execute multiple asynchronous requests
axios.spread(): used to specify the method of callback function to receive all successful data

3, Configuring agents in React to solve cross domain problems

1. Configure agent method

Solve the cross domain problem and open the intermediate agent in React
Package. In the project JSON, add a line "proxy" at the end:“ http://loaclhost:5000 "Write to port number
Then restart the scaffold
When sending the request again, write your own 3000 port directly
Some resources of port 3000 directly request port 3000, and those without port 3000 request port 5000 set by the agent

In package Add the following configuration to JSON

"proxy":"http://localhost:5000"

explain:

  1. Advantages: the configuration is simple, and the front end can request resources without any prefix.
  2. Disadvantages: multiple agents cannot be configured.
  3. Working principle: configure the agent in the above way. When 3000 nonexistent resources are requested, the request will be forwarded to 5000 (matching the front-end resources first)

2. Configure multiple agent methods

Configure multiple agents, not in package JSON configuration

  1. Step 1: create an agent profile
stay src To create a profile: src/setupProxy.js
  1. Write setupproxy JS configure specific proxy rules:
const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  //api1 is the request to be forwarded (all requests with / api1 prefix will be forwarded to 5000)
      target: 'http://localhost:5000 ', / / configure the forwarding destination address (the server address that can return data)
      changeOrigin: true, //Controls the value of the host field in the request header received by the server
      /*
      	changeOrigin When set to true, the host in the request header received by the server is: localhost:5000
      	changeOrigin When set to false, the host in the request header received by the server is: localhost:3000
      	changeOrigin The default value is false, but we generally set the changeOrigin value to true
      */
      pathRewrite: {'^/api1': ''} //Remove the request prefix and ensure that the normal request address is given to the background server (must be configured)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
  )
}

explain:
3. Advantages: multiple agents can be configured to flexibly control whether requests go through agents.
4. Disadvantages: the configuration is cumbersome, and the front end must add a prefix when requesting resources.

4, Case - github user search

1. Effect

Request address: https://api.github.com/search/users?q=xxxxxx

2. React implementation

2.1 implementation of static page splitting

App.jsx

import React, { Component } from 'react'
import Search from './Search'
import Users from './Users'

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <Search />
        <Users />
    </div>
    )
  }
}

Search/index.js

import React, { Component } from 'react'

export default class Search extends Component {
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">search Github user</h3>
      <div>
          <input type="text" placeholder="Please enter the user name you want to search" />&nbsp;
          <button>search</button>
      </div>
      </section>
    )
  }
}

User/index.jsx

import React, { Component } from 'react'
import './index.css'

export default class Users extends Component {
  render() {
    return (
      <div className="row">
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
              <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px' }}/>
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px' }}/>
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px' }}/>
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px' }}/>
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px' }}/>
          </a>
          <p className="card-text">reactjs</p>
        </div>
      </div>
    )
  }
}

User/index.css

.album {
  min-height: 50rem; /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding: .75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card > img {
  margin-bottom: .75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}

2.2 implementation of dynamic interaction

If github access fails, you can forge a server to return some fixed results to make the user experience better
Build a server using express

serve.js

const express = require("express")
const axios = require("axios")
const app = express()


/*
  Request address: http://localhost:3000/search/users?q=aa

  Background routing
    key:  /search/users
    value:  function () {}
*/
app.get("/search/users", function (req, res) {
  const {q} = req.query
  axios({
    url: 'https://api.github.com/search/users',
    params: {q}
  }).then(response => {
    res.json(response.data)
  })
})

app.get("/search/users2", function (req, res) {
  res.json({
    items: [
      {
        login: "yyx990803",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 1,
      },
      {
        login: "ruanyf",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 2,
      },
      {
        login: "yyx9908032",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 3,
      },
      {
        login: "ruanyf2",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 4,
      },
      {
        login: "yyx9908033",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 5,
      },
      {
        login: "ruanyf3",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 6,
      },
      {
        login: "yyx9908034",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 7,
      },
      {
        login: "ruanyf4",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 8,
      },
      {
        login: "yyx9908035",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 9,
      },
    ],
  });
});



app.listen(5000, "localhost", (err) => {
  if (!err){
  	console.log("The server started successfully")
  	console.log("request github For real data, please visit: http://localhost:5000/search/users")
  	console.log("To request local simulation data, please visit: http://localhost:5000/search/users2")
  } 
  else console.log(err);
})

src/setupProxy.js

Set up a proxy server to solve cross domain problems Src / setupproxy js

const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  //api1 is the request to be forwarded (all requests with / api1 prefix will be forwarded to 5000)
      target: 'http://localhost:5000 ', / / configure the forwarding destination address (the server address that can return data)
      changeOrigin: true, //Controls the value of the host field in the request header received by the server
      pathRewrite: {'^/api1': ''} //Remove the request prefix and ensure that the normal request address is given to the background server (must be configured)
    })
  )
}

App.jsx

Define status data in App
The method of operating status is placed in the App

export default class App extends Component {
  state = {
    users: []
  }
  saveUsers = (users) => {
    this.setState({ users })
  }
  render() {
    const {users} = this.state
    return (
      <div className="container">
        <Search saveUsers={this.saveUsers} />
        <Users users={users} />
    </div>
    )
  }
}

Search/index/jsx

export default class Search extends Component {
  search = () => {
    // Get user input (continuous deconstruction assignment + rename)
    const {keyWordElement: {value: keyWord}} = this
    // console.log(keyWord)
    // Send network request
    axios.get(`/api1/search/users?q=${keyWord}`).then(
      response => {
        console.log('success')
        this.props.saveUsers(response.data.items)
      },
      error => {console.log('fail',error)}
    )
  }
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">search Github user</h3>
      <div>
          <input ref={c => this.keyWordElement = c} type="text" placeholder="Please enter the user name you want to search" />&nbsp;
          <button onClick={this.search}>search</button>
      </div>
      </section>
    )
  }
}

Users/index.jsx

export default class Users extends Component {
  render() {
    return (
      <div className="row">
        {
          this.props.users.map((userObj) => {
            return (
              <div key={userObj.id} className="card">
                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                    <img alt="avatar" src={userObj.avatar_url} style={{ 'width': '100px' }}/>
                </a>
                <p className="card-text">{userObj.login}</p>
              </div>
            )
          })
        }
      </div>
    )
  }
}

Effect display

2.3 optimize user experience

In the Users component, there should be not only user list pages, but also

  1. Welcome to the interface [opening page for the first time]
  2. Search loading page [click the button to display between sending request and receiving response]
  3. Search failure page [request failure display]

There are four different displays, so you need different state to control

// Initialization status
state = { 
  users: [], // users initial value
  isFirst: true, // Open page for the first time
  isLoading: false, // Identifies whether the is loading
  err:'' // Message of request failure
}

App.jsx

export default class App extends Component {
  // Initialization status
  state = { 
    users: [], // users initial value
    isFirst: true, // Open page for the first time
    isLoading: false, // Identifies whether the is loading
    err:'' // Message of request failure
  }
  // saveUsers = (users) => {
  //   this.setState({ users })
  // }
  // Update App state
  updateAppState = (stateObj) => {
    this.setState(stateObj)
  }
  render() {
    return (
      <div className="container">
        <Search updateAppState={this.updateAppState} />
        <Users {...this.state} />
    </div>
    )
  }
}

Search/index/jsx

export default class Search extends Component {
  search = () => {
    // Get user input (continuous deconstruction assignment + rename)
    const {keyWordElement: {value: keyWord}} = this
    // console.log(keyWord)
    // Notify App of update status before sending request
    this.props.updateAppState({
      isFirst: false,
      isLoading: true
    })
    // Send network request
    axios.get(`/api1/search/users?q=${keyWord}`).then(
      response => {
        // console.log('success')
        // If the request is successful, notify the App of the update status
        this.props.updateAppState({isLoading: false, users: response.data.items})
        // this.props.saveUsers(response.data.items)
      },
      error => {
        // console.log('failed ', error)
        // Request failed, notify App of update status
        this.props.updateAppState({isLoading: false, err: error.message})
      }
    )
  }
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">search Github user</h3>
      <div>
          <input ref={c => this.keyWordElement = c} type="text" placeholder="Please enter the user name you want to search" />&nbsp;
          <button onClick={this.search}>search</button>
      </div>
      </section>
    )
  }
}

Users/index.jsx

export default class Users extends Component {
  render() {
    const {users, isFirst, isLoading, err} = this.props
    return (
      <div className="row">
        {
          isFirst ? <h2>Welcome, please enter keywords and click search</h2> :
          isLoading ? <h2>Loading...</h2> :
          err ? <h2 style={{color: 'red'}}>{err}</h2> :
          users.map((userObj) => {
            return (
              <div key={userObj.id} className="card">
                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                    <img alt="avatar" src={userObj.avatar_url} style={{ 'width': '100px' }}/>
                </a>
                <p className="card-text">{userObj.login}</p>
              </div>
            )
          })
        }
      </div>
    )
  }
}

Effect display

5, Message subscription / publishing mechanism

In the previous case, the communication between sibling components always depends on the parent component
Now let's introduce the message subscription publish mechanism to communicate between sibling components

1. Introduce PubSubJS Library

https://github.com/mroderick/PubSubJS

1. Tool library: PubSubJS
2. download: npm install pubsub-js
3.Basic use
 In the component receiving data, click subscribe
import PubSub from 'pubsub-js' //introduce
PubSub.subscribe('delete', function(data){ }); // subscribe
PubSub.publish('delete', data) // Data carried by publishing message

2. Use in case

Users component [receives] data, so users component [subscribes] message
The Search component sends data and publishes a message

App.js

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <Search />
        <Users />
    </div>
    )
  }
}

Users/index.jsx

The User component uses the state data state. The state is defined here and the message is subscribed here
The Search component changes the status data and publishes messages here

export default class Users extends Component {
  // Initialization status
  state = { 
    users: [], // users initial value
    isFirst: true, // Open page for the first time
    isLoading: false, // Identifies whether the is loading
    err:'' // Message of request failure
  }
  componentDidMount() {
    // Subscription message
    PubSub.subscribe('ykyk', (_, data) => {
      this.setState(data)
    })
  }
  render() {
    const {users, isFirst, isLoading, err} = this.state
    return (
      <div className="row">
        {
          isFirst ? <h2>Welcome, please enter keywords and click search</h2> :
          isLoading ? <h2>Loading...</h2> :
          err ? <h2 style={{color: 'red'}}>{err}</h2> :
          users.map((userObj) => {
            return (
              <div key={userObj.id} className="card">
                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                    <img alt="avatar" src={userObj.avatar_url} style={{ 'width': '100px' }}/>
                </a>
                <p className="card-text">{userObj.login}</p>
              </div>
            )
          })
        }
      </div>
    )
  }
}

Search/index.jsx

The Search component changes the status data and publishes a message here [publish a message as soon as the status is changed]

export default class Search extends Component {
  search = () => {
    const {keyWordElement: {value: keyWord}} = this
    // Notify Users of update status before sending request
    // this.props.updateAppState({isFirst: false,isLoading: true})
    PubSub.publish('ykyk', {isFirst: false,isLoading: true})
    // Send network request
    axios.get(`/api1/search/users?q=${keyWord}`).then(
      response => {
        // If the request is successful, notify Users to update the status
        PubSub.publish('ykyk', {isLoading: false, users: response.data.items})
        // this.props.updateAppState({isLoading: false, users: response.data.items})
      },
      error => {
        // Request failed, notify Users to update status
        PubSub.publish('ykyk', {isLoading: false, err: error.message})
        // this.props.updateAppState({isLoading: false, err: error.message})
      }
    )
  }
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">search Github user</h3>
      <div>
          <input ref={c => this.keyWordElement = c} type="text" placeholder="Please enter the user name you want to search" />&nbsp;
          <button onClick={this.search}>search</button>
      </div>
      </section>
    )
  }
}

6, Fetch

axios encapsulates xhr at the front end
Fetch is a built-in network request method, which does not need to be downloaded and installed separately

1. Documentation

  1. https://github.github.io/fetch/
  2. [related blog posts] traditional Ajax is dead and Fetch is immortal

2. Features

  1. fetch: a native function that no longer uses the XmlHttpRequest object to submit ajax requests
  2. Older browsers may not support

3. Example demonstration

Search/index.jsx

Before optimization

export default class Search extends Component {
	search = async()=>{
		//Get user input (continuous deconstruction assignment + rename)
		const {keyWordElement:{value:keyWord}} = this
		//Notify List of update status before sending request
		PubSub.publish('ykyk',{isFirst:false,isLoading:true})
		
		//Send network request --- send using fetch (not optimized)
		fetch(`/api1/search/users2?q=${keyWord}`).then(
			response => {
				console.log('Successfully contacted the server');
				return response.json()
			},
			error => {
				console.log('Failed to contact the server',error);
				return new Promise(()=>{})
			}
		).then(
			response => {console.log('Data acquisition succeeded',response);},
			error => {console.log('Failed to get data',error);}
		)
	}
}

After optimization

export default class Search extends Component {
	search = async()=>{
		//Get user input (continuous deconstruction assignment + rename)
		const {keyWordElement:{value:keyWord}} = this
		//Notify List of update status before sending request
		PubSub.publish('ykyk',{isFirst:false,isLoading:true})

		//Send network request --- send using fetch (optimized)
		try {
			const response = await fetch(`/api1/search/users2?q=${keyWord}`)
			const data = await response.json()
			// console.log(data);
			PubSub.publish('ykyk',{isLoading:false, users:data.items})
		} catch (error) {
			// console.log('request error ', error);
			PubSub.publish('ykyk',{isLoading:false, err:error.message})
		}
	}
}

7, Summary

  1. When designing the status, we should consider comprehensively. For example, for components with network requests, we should consider what to do if the request fails.
  2. Message subscription and publishing mechanism
    ① Subscribe first and then publish (Understanding: it feels like an empty dialogue)
    ② It is suitable for communication between any components
    ③ To unsubscribe in componentWillUnmount of a component
  3. fetch send request (design idea of separation of concerns)
try {
	const response= await fetch(`/api1/search/users2?q=${keyWord}`)
	const data = await response.json()
	console.log(data);
} catch (error) {
	console.log('Request error',error);
}

Topics: Javascript Front-end github React Ajax