Free React Course
For Beginners

L9 - React and network

React For Beginners by Itera

Key Points

  • Fetch
  • Axios
  • Network and store
  • Typical mistakes

Fetch

In a nutshell working with network is easy

Schema

  • Use fetch api to issue an network request
  • Update state on success
  • Handle error if any

Class based component example

Define a class with an initial state

class FreeApi extends PureComponent {
    state = { errored: false, apis: [] };
}
            

On ComponentDidMount implement fetching

async componentDidMount() {
    try {
        const rawData = await fetch(FREE_API_URL);
        const { entries } = await rawData.json();
        this.setState({ errored:false, apis: entries });
    } catch (e) {
        this.setState({ errored: true, apis: [] });
    }
}

Render the result

render() {
    const { apis, errored } = this.state;
    return errored 
        ? (<div>Something went wrong</div>) 
        : <ul>{apis.map(({ API, Link }) => (
                <li key={Link}>
                {API}::{Link}
                </li>
            ))}
        </ul>;
}

Full example

Functional example

Create custom hook with default state

import { PureComponent, useEffect, useState } from "react";

const useFreeApi = () => {
  const [data, setApi] = useState({ errored: false, apis: [] });
  return data;
};
            

Add useEffect hook with fetch inside

useEffect(() => {
    async function updateApi() {
        try {
            const rawData = await fetch(FREE_API_URL);
            const { entries } = await rawData.json();
            setApi({ errored: false, apis: entries });
        } catch (error) {
            setApi({ errored: true, apis: [] });
        }
    }
    updateApi();
}, []);

Pay attention to useEffect

If you will call fetch directly - the request will be issued every re-render

Pay attention to useEffect

Don't forget to set an empty array of dependencies to fire useEffect only once

Use the new hook as usual

const FreeApi = () => {
    const { errored, apis } = useFreeApi();
    return errored 
        ? (<div>Something went wrong</div>) 
        : (<ul>
            {apis.map(({ API, Link }) => (
                <li key={Link}>
                {API}::{Link}
                </li>
            ))}
          </ul>);
};

Full example

However, fetch has some pitfalls

Internet Explorer

Despite Internet Explorer being deprecated even by the author, it's still popular in a big companies

Fetch doesn't work in IE

Canceling request

Sometimes you will need to cancel a request

Fetch doesn't cancel

The work on canceling is ongoing

No progress tracking, no timeout

Fetch is a bit limited

Axios

Axios@0.26.1

  • ⭐ 92_000
  • Downloads: 28,198,607
  • Size: 16kB

Benefits

  • Works with IE 💔
  • Supports aborting request
  • Support progress
  • Support interceptors

Example

const axios = Axios.create();
...
useEffect(() => {
    async function updateApi() {
        try {
            const { data } = await axios.get(FREE_API_URL);
            const { entries } = data;
            setApi({ errored: false, apis: entries });
        } catch (error) {
            setApi({ errored: true, apis: [] });
        }
    }
    updateApi();
}, []);
            

Differences

  • Need to create an instance before using
  • Don't need to use .toJson() to get an object

Why to use - global config

const axios = Axios.create({
    baseURL: 'https://some-domain.com/api/',
    timeout: 1000,
    headers: {'X-Custom-Header': 'foobar'}
});
        

Why to use - easy to cancel

const abort = new AbortController();
useEffect(()=>{
    try {
      const { data } = await axios.get(FREE_API_URL, 
        { signal: abort.signal });
    }
    ...
    return ()=> abort.abort()
}, [])

You can use AbortController or legacy CancelToken

Why to use - interceptors

const axios = Axios.create();
axios.interceptors.response.use((response) => {
    console.log(response.request.responseURL);
    return response;
});

Full example

What to use?

  • Fetch - for a simple project without authentication
  • Axios - for anything else

What about other tools like isomorphic fetch?

Feel free to use any tool that feet requirements, just ensure it's stable and supported

Not OK

There is one big fail in the examples above - the component knows what data it needs (OK), how to get them (not OK) and how to put them to the store (double not OK)

We need a separations of responsibility

The store should be responsible about this stuff

Create a store

type ApiData = { 
    status: "pending" | "failed" | "success"; 
    entries: [] 
};

class Store {
    data: ApiData = { status: "pending", entries: [] };
    constructor() { makeAutoObservable(this); }
}
        

Create a method that will fetch API

fetchApi() {
  const abort = new AbortController();
  axios.get(FREE_API_URL, { signal: abort.signal })
    .then(({ data }) => {
      runInAction(() => (
        this.data = { status: 'success', entries: data.entries })
      );
    })
    .catch(() => {
      runInAction(() => (
        this.data = { status: 'failed', entries: [] })
      );
    });
    return abort;
}
        

Use from anywhere you need

const AppApp = observer(() => {
    useEffect(() => {
        const request = apiStore.fetchApi();
        return () => request.abort();
    }, []);

    const { status } = apiStore.data;

    if (status ==='pending') { return <>'pending'</> }
    if (status === 'failed') { return <>'failed'</> }
    return <ApiList/>
});
      

Pros

  • Usage simplicity - just call a method
  • Data stored in a one place - caching, logging, is very easy
  • Because of using store - data is accessible anywhere

Typical mistakes

Ignoring errors 💔

If something might fail - it will fail in the most inconvenient time

Start designing your component from failed point

Ignoring pending indications 💔

Your backend is not something super stable - it might be slow because of many reasons

Showing some kind of indication that response is awaited - might be a good idea

Ignoring unsubscribe 💔

Users will not always wait till the end of the call, they might just change the page

Always provide a way to cancel the network call

Using async in effect 💔

// this will not work
useEffect(async () => {
    await fetch(SOME_URL)
}, []);
        
 // but this will
useEffect(() => {
    async function myFetch() {
        await fetch(SOME_URL);
    }
    myFetch()
}, []);                    

Using fetch directly in the code 💔

const myApp = () => {
    const [data, state] = useState({});
    // calls BE each render
    fetch(SOME_URL).then(setState)
    return <></>
}

Using fetch without specifying dependency list in use effect 💔

const myApp = () => {
    const [data, setState] = useState({});

    useEffect(() => {
        // calls BE each render
        fetch(SOME_URL).then(setState);
    });
    return <></>;
};
          

Takeaway

  • Use fetch for a simple project and axios for middle/large
  • Move logic out of component
  • Don't ignore errors

Useful links

Join me

Twitter
GitHub