Languages
[Edit]
EN

React - custom async useEffect hook

9 points
Created by:
Kadeem-Craig
456

In this article, we would like to show you how to create your own async useEffect hook function in React.

By default, React doesn't provide that kind of function, but there is a simple way how to create your own one.

Quick solution:

const useAsyncEffect = (callback, dependences) => {
  	React.useEffect(() => {
      	let invalid = false;
      	let cleanup = null;
        try {
      	    const promise = callback(() => invalid);
          	if (promise instanceof Promise) {
              	promise
                  	.then(result => {
                  		if (invalid) {
                            result?.();
                        } else {
                        	cleanup = result;
                        }
                    })
                    .catch(console.error);
            } else {
              	cleanup = promise; // in this case promise variable is just notmal cleanup function
            }
        } finally {
          	return () => {
                invalid = true;
              	cleanup?.();
            };
        }
    }, dependences);
};

// Usage example:

useAsyncEffect(async (isInvalid) => {
    console.log('MyComponent mounted...');
  	//TODO: some async operations with await keyword here...
    if (!isInvalid()) {
        //TODO: we can update component state here if still is mounted...
    }
    return () => {
        // Unmounted state handled because of empty array as dependencies used with useAsyncEffect().
        // To understand this case better, go to:
        //     https://dirask.com/posts/React-onMount-and-onUnmount-component-1AqNz1
        console.log('MyComponent unmounted...'); 
    };
}, []);

The main advantage of that kind function is the possibility to use await keyword when we do AJAX or other async operations with useEffect, that makes code more readable. In the below approach, we can use easily isInvalid() function that checks if the component state should be modified still because of current dependencies - after async AJAX request is done current conditions can be incorrect to bind result (different dependency values).

 

useAsyncEffect with AJAX example

In this section, we would like to show you how to use useAsyncEffect with fetch function (with AJAX requests).

Note: in below example we can check isInvalid() status always after any method with await keyword is called to finish useEffect logic execution in proper way, but most important is to do not change state on unmounted component what we do.

// ONLINE-RUNNER:browser;

//Note: Uncomment import lines during working with JSX Compiler.
// import React from "react";
// import ReactDOM from "react-dom";

const useAsyncEffect = (callback, dependences) => {
  	React.useEffect(() => {
      	let invalid = false;
      	let cleanup = null;
        try {
      	    const promise = callback(() => invalid);
          	if (promise instanceof Promise) {
              	promise
                  	.then(result => {
                  		if (invalid) {
                            result?.();
                        } else {
                        	cleanup = result;
                        }
                    })
                    .catch(console.error);
            } else {
              	cleanup = promise; // in this case promise variable is just notmal cleanup function
            }
        } finally {
          	return () => {
                invalid = true;
              	cleanup?.();
            };
        }
    }, dependences);
};

// Usage example:

const MyComponent = ({requestText}) => {
	const [response, setResponse] = React.useState();
    useAsyncEffect(async (isInvalid) => {
  		console.log('MyComponent mounted...');
      	try {
            const safeRequestText = encodeURIComponent(requestText);
      		const response = await fetch(`/examples/echo?text=${safeRequestText}`);
          	const responseText = await response.text();
          	if (!isInvalid()) {
          		setResponse(`Response: ${responseText}`);
            }
        } catch (error) {
          	if (!isInvalid()) {
          		setResponse('Request error!');
            }
        }
      	return () => {
        	console.log('MyComponent unmounted...');
        };
    }, []);
    return (
      <div>{response}</div>
    );
};

const App = () => {
    const [visible, setVisible] = React.useState(false);
  	const createHandleClick = (timeout) => {
    	return () => {
        	setVisible(true);
            setTimeout(() => setVisible(false), timeout);
        };
    };
    return (
      <div>
        <div>
          <span>Mount MyComponent and later unmount </span>
          {' '}
          <button disabled={visible} onClick={createHandleClick()}>
             immediately
          </button>
          {' or '}
          <button disabled={visible} onClick={createHandleClick(1000)}>
            after 1s
          </button>
        </div>
        {visible && <MyComponent requestText="Hi there!" />}
      </div>
    );
};

const root = document.querySelector('#root');
ReactDOM.render(<App />, root);

useAsyncEffect as timer example

async useEffect can be easy way used to write own timer logic. In the below example we used sleep() method that slows down useAsyncEffect function for 1 second.

// ONLINE-RUNNER:browser;

//Note: Uncomment import lines during working with JSX Compiler.
// import React from "react";
// import ReactDOM from "react-dom";

const useAsyncEffect = (callback, dependences) => {
  	React.useEffect(() => {
      	let invalid = false;
      	let cleanup = null;
        try {
      	    const promise = callback(() => invalid);
          	if (promise instanceof Promise) {
              	promise
                  	.then(result => {
                  		if (invalid) {
                            result?.();
                        } else {
                        	cleanup = result;
                        }
                    })
                    .catch(console.error);
            } else {
              	cleanup = promise; // in this case promise variable is just notmal cleanup function
            }
        } finally {
          	return () => {
                invalid = true;
              	cleanup?.();
            };
        }
    }, dependences);
};

// Usage example:

const sleep = (time) => new Promise(resolve => setTimeout(resolve, time));

const App = () => {
    const [counter, setCounter] = React.useState(0);
  	useAsyncEffect(async () => {
      	await sleep(1000);
      	setCounter(counter + 1);
    }, [counter]);
    return (
      <div>{counter}</div>
    );
};

const root = document.querySelector('#root');
ReactDOM.render(<App />, root);

ReactJS

React - custom async useEffect hook
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join