Explain how to request data and render using React Hooks

  • 2021-08-31 07:08:37
  • OfStack

Preface

In daily development, asynchronous retrieval and rendering of data from the server is a fairly high-frequency operation. In the past, when using React Class component, we are already familiar with this operation, that is, in componentDidMount of Class component, data is obtained through ajax and setState triggers component update.

With the advent of Hook, we can use Hook instead of Class in one scenario. However, there are no functions such as setState and componentDidMount in Hook, so how can we get data asynchronously from the server and render it? This article shows you how to use Hook, a new feature of React, to write components and get data rendering.

Data rendering

Let's first look at a simple demo for data rendering


import React, { useState } from 'react';
 
function App() {
 const [data, setData] = useState({ products: [{
 productId: '123',
 productName: 'macbook'
 }] });
 
 return (
 <ul>
 {data.products.map(i => (
 <li key={i.productId}>
  {i.productName}
 </li>
 ))}
 </ul>
 );
} 
export default App;

In demo, an internal state called data is created through useState, and there is a product list data in this state to hold product data. The App component renders product list data to the page through products in data.

But now it is a dead data. If we expect to get data from the server and render it, we need fetch server data when the component rendering is completed, and then change state through setData to trigger rendering. Next, we are going to use axios to obtain data.


import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
 const [data, setData] = useState({ products: [{
 productId: '123',
 productName: 'macbook'
 }] });
 
 useEffect(async () => {
 const result = await axios(
 'https://c.com/api/products?date=today',
 );
 
 setData(result.data);
 });
 
 return (
 <ul>
 {data.products.map(i => (
 <li key={i.productId}>
  {i.productName}
 </li>
 ))}
 </ul>
 );
}
export default App;

The useEffect used in the code is one of the hook, called effect hook. useEffect is triggered every time the component is rendered, and we use it to retrieve data and update state. But the above code is flawed, have you found it?

Yes, as long as you run it once, you will find that the program has entered an infinite loop. Because useEffect is triggered not only when the component is didMounts, but also when the component is didUpdate. After obtaining data in useEffect, state is changed through setDate, which triggers component rendering update, and then enters useEffect again, and goes on indefinitely. This is not the result we want. What we initially want is to get the data once when didMounts. Therefore, in this case, we must pass an empty [] to the second parameter of the useEffect method, so that the logic in useEffect is only executed when the component didMounts.


import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
 const [data, setData] = useState({ products: [{
 productId: '123',
 productName: 'macbook'
 }] });
 
 useEffect(async () => {
 const result = await axios(
 'https://c.com/api/products?date=today',
 );
 
 setData(result.data);
 },[]); // Focus 
 
 return (
 <ul>
 {data.products.map(i => (
 <li key={i.productId}>
  {i.productName}
 </li>
 ))}
 </ul>
 );
}
export default App;

Although it seems that this mistake is relatively low-level, it is indeed a problem that many people often make when they start hook.

Of course, the second parameter of useEffect can also pass in a value. When there are values, useEffect will trigger when these values are updated. If it is only an empty array, it will only be triggered when didMounts.

In addition, execute this code and you will see the console warning, Promises and useEffect (async () = > ...) are not supported, but you can call an async function inside an effect.. Therefore, if you want to use async, you need to modify the following writing.


import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
 const [data, setData] = useState({ products: [{
 productId: '123',
 productName: 'macbook'
 }] });
 
 useEffect(() => {
 const fetchData = async()=>{
 const result = await axios(
  'https://c.com/api/products?date=today',
 );
 setData(result.data);
 }
 fetchData();
 },[]); 
 
 return (
 <ul>
 {data.products.map(i => (
 <li key={i.productId}>
  {i.productName}
 </li>
 ))}
 </ul>
 );
}
export default App;

Experience optimization

loading will be added to the interaction design of some request processes to relieve user anxiety. In the writing of Hook, how to realize it? It will be introduced below.


import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
 const [data, setData] = useState({ products: [{
 productId: '123',
 productName: 'macbook'
 }] });
 const [isLoading, setIsLoading] = useState(false);
 
 useEffect(() => {
 const fetchData = async()=>{
 setIsLoading(true);
 const result = await axios(
  'https://c.com/api/products?date=today',
 );
 setData(result.data);
 setIsLoading(false);
 }
 fetchData();
 },[]); 
 
 return (
 {isLoading ? (
 <div>Loading ...</div>
 ) : (
 <ul>
 {data.products.map(i => (
 <li key={i.productId}>
  {i.productName}
 </li>
 ))}
 </ul>
 )};
}
export default App;

This is achieved by adding an state called isLoading. We change the value of isLoading at the beginning and end of fetch to control the component content returned by return, thus displaying Loading components before request and product list after request.

Error handling

The request process often fails due to various reasons, such as network, server errors and so on. Therefore, error handling is essential.


import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
 const [data, setData] = useState({ products: [{
 productId: '123',
 productName: 'macbook'
 }] });
 const [isLoading, setIsLoading] = useState(false);
 const [isError, setIsError] = useState(false);
 
 useEffect(() => {
 const fetchData = async()=>{
 setIsError(false);
 setIsLoading(true);

 try{
  const result = await axios(
  'https://c.com/api/products?date=today',
  );
  setData(result.data);
 }catch(e){
  setIsError(true);
 }
 setIsLoading(false);
 }
 fetchData();
 },[]); 
 
 return (
 <div>
 {isError && <div> There's been a mistake ...</div>}
 {isLoading ? (
 <div>Loading ...</div>
 ) : (
 <ul>
 {data.products.map(i => (
 <li key={i.productId}>
  {i.productName}
 </li>
 ))}
 </ul>
 )};
 </div>
 
}
 
export default App;

When a request goes wrong, isError is set to true, and when rendering is triggered, the error prompt component is rendered. The process here is relatively simple, and in a real-world scenario, you can add more complex logic to error handling. isError is reset each time hook is run.

Finally

After reading this, you have basically learned how to use React Hooks to get data and render components.


Related articles: