The article is a translation. See the original reading

You’ve no doubt seen (or written about) render modes like this:

  1. Render a Loading placeholder icon when requesting data through AJAX

  2. Re-render the component when the data is returned

Let’s take a simple example using the Fetch API:

import React, { useState, useEffect } from 'react';

const SomeComponent = (props) = > {
  const [someData, setSomeData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() = > {
    setLoading(true);
    fetch('/some-data')
      .then(response= > response.json())
      .then(data= > setSomeData(data))
      .catch(error= > setError(error))
      .finally(() = > setLoading(false)); } []);return (
    <React.Fragment>
      {loading && <div>{'Loading... '}</div>} {! loading && error &&<div>{`Error: ${error}`}</div>} {! loading && ! error && someData &&<div>{/* INSERT SOME AMAZING UI */}</div>}
    </React.Fragment>
  );
};
Copy the code

As our application gets bigger, let’s say we have n components that use the same data.

To reduce repeated requests, I decided to use LocalStorage to cache server data.

Does this mean that the same rendering logic has to be written n times?

Decouple data requests

How? Let’s separate the data request part into a custom hook — useSomeData.

import React, { useState, useEffect } from 'react';

const useSomeData = () = > {
  const cachedData = JSON.parse(localStorage.getItem('someData'));
  const [someData, setSomeData] = useState(cachedData);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() = > {
    if(! someData) { setLoading(true);
      fetch('/some-data')
        .then(response= > response.json())
        .then(data= > {
          localStorage.setItem('someData'.JSON.stringify(data));
          return setSomeData(data);
        })
        .catch(error= > setError(error))
        .finally(() = > setLoading(false)); }} []);return { someData, loading, error };
};
Copy the code

Using useSomeData it looks like this:

const SomeComponent = (props) = > {
  const { someData, loading, error } = useSomeData();
  return (
    <React.Fragment>
      {loading && <div>{'Loading... '}</div>} {! loading && error &&<div>{`Error: ${error}`}</div>} {! loading && ! error && someData &&<div>{/* INSERT SOME AMAZING UI */}</div>}
    </React.Fragment>
  );
};

const AnotherComponent = (props) = > {
  const { someData, loading, error } = useSomeData();
  return (
    <React.Fragment>
      {loading && <div>{'Loading... '}</div>} {! loading && error &&<div>{`Error: ${error}`}</div>} {! loading && ! error && someData &&<div>{/* INSERT ANOTHER AMAZING UI */}</div>}
    </React.Fragment>
  );
};
Copy the code

Reusing code is great, but is that it?

Custom data request

As our application became more complex, I decided to go to Redux.

In this case, only a simple modification of useSomeData is required, without changing the business components at all:

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { selectSomeData } from 'path/to/data/selectors';
import { fetchSomeData } from 'path/to/data/action';

const useSomeData = () = > {
  const dispatch = useDispatch();
  const someData = useSelector(selectSomeData);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() = > {
    if(! someData) { setLoading(true);
      dispatch(fetchSomeData())
        .catch(error= > setError(error))
        .finally(() = > setLoading(false)); }} []);return { someData, loading, error }; 
};
Copy the code

One day I decided to jump on the GraphQL bandwagon. Again, you can simply modify useSomeData without changing the business components:

import { gql, useQuery } from '@apollo/client';

const FETCH_SOME_DATA = gql` fetchSomeData { data { # some fields } } `;

const useSomeData = () = > {
  const { data, loading, error } = useQuery(FETCH_SOME_DATA);
  return { someData: data, loading, error }; 
};
Copy the code

Whenever I change the data request/state management part voluntarily (or involuntarily), ALL I need to do is change the corresponding hook.

Like the classic dependency inversion principle (D in SOLID). Although not object-oriented, we defined an abstract interface and implemented classes based on it.

UseSomeData actually provides an interface to the business components that use it.

Developers don’t need to care about how useSomeData works, just the data they receive, the loading status, and the error messages.

In theory, the UI can be decoupled from the data layer and migrated to any data layer at any time, provided the right interfaces are defined.