Search code examples
reactjsaxiosuse-effect

React HOC Using useEffect()


So i am using a HOC for general error handling purposes in react like this:

import React, { useState, useEffect } from 'react'
import Modal from '../../UI/Modal/Modal'
const WithErrorHandler = (WrappedComponent, axios) => {
    const NewComponent = props => {
        console.log('UseState')
        const [error, setError] = useState(null)
        console.log('runs')
        useEffect(() => {
            const req = axios.interceptors.request.use(config => {
                console.log('request intercepted')
                return config
            })
            const res = axios.interceptors.response.use(null, error => {
                setError(error)
                return Promise.reject(error)
            })
            
            return () => {
                axios.interceptors.request.eject(req)
                axios.interceptors.response.eject(res)
            }
        }, [])
        return (
            <div>
                {console.log('render')}
                {error ? (
                    <Modal clickHandler={() => setError(null)}> {error.message}</Modal>
                ) : null}
                <WrappedComponent {...props} />
            </div>
        )
    }
    return NewComponent
}
export default WithErrorHandler

The problem i have run into is that i have a component which fires an axios request in it's useEffect(). When i try to wrap this component with my WithErrorHandler the useEffect of the wrapped component fires first then the useEffect of HOC withErrorHandler runs. This causes the axios request to be made faster than the HOC could register the axios interceptors. Any ideas on how to fix this would be aprreciated.


Solution

  • You can define an intermediate state which prevents from rendering wrapped component.

    const WithErrorHandler = (WrappedComponent, axios) => {
      const NewComponent = (props) => {
            
        const [ready, setReady] = useState(false); // HERE
    
        console.log("UseState");
        const [error, setError] = useState(null);
        console.log("runs");
        useEffect(() => {
          const req = axios.interceptors.request.use((config) => {
            console.log("request intercepted");
            return config;
          });
          const res = axios.interceptors.response.use(null, (error) => {
            setError(error);
            return Promise.reject(error);
          });
    
          setReady(true); // HERE
    
          return () => {
            axios.interceptors.request.eject(req);
            axios.interceptors.response.eject(res);
          };
        }, []);
    
        if (!ready) return null; // HERE
    
        return (
          <div>
            {console.log("render")}
            {error ? (
              <Modal clickHandler={() => setError(null)}> {error.message}</Modal>
            ) : null}
            <WrappedComponent {...props} />
          </div>
        );
      };
      return NewComponent;
    };
    

    What it does is that it makes sure that axios interceptor is initialized and it is good to render wrapped component.

    Instead of if (!ready) return null; you can return a more sensible state from your HOC for instance, if (!ready) return <p>Initializing...</p>