React HOC Using useEffect()

Viewed 502

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.

2 Answers

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>

You need an extra render for the NewComponent callback to run, adding a conditional rendering on WrappedComponent should do the trick.

Notice that we set isFirstRender on promise success, change it dependenly on your use case.

const WithErrorHandler = (WrappedComponent, axios) => {
  const NewComponent = (props) => {
    const [isFirstRender, setIsFirstRender] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
      if (isFirstRender) {
        const req = axios.interceptors.request.use((config) => {
          return config;
        });

        // Check req success
        if (req.isSuccess) { setIsFirstRender(false); }

        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);
        };
      }
    }, [isFirstRender]);

    return (
      <div>
        {error ? (
          <Modal clickHandler={() => setError(null)}> {error.message}</Modal>
        ) : null}
        {!isFirstRender && <WrappedComponent {...props} />}
      </div>
    );
  };
  return NewComponent;
};
Related