Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Hook: Access to state inside an external function

In a React app, I would like to have a function initiate a series of async calls and then have the ability to alter the state available to those calls as they run. As an example, I would let the user initiate a retrieval of 5 data files, which could run in the background and take minutes, but give them the option to abort the process or trim the total file count.

Here's an idea of what it could look like, but unfortunately this pattern doesn't seem to work:

function App() {
  const [halt, setHalt] = useState(false);

  return (
      ...
      <button onClick={() => longProcess(halt)}>Start</button>
      <button onClick={() => setHalt(true)}>Stop</button>
      ...
  );
}

async function longProcess(halt) {
  for (const fileid of files_to_get) {
    // For example, halt if the user clicks the Stop button during execution
    if (halt) break;
    await getDataFile(fileid);
  }
}

Ideally, I want to use pure functional components and to allow the async function to be available for use by multiple components. So I've been using React Hooks across the board. I have come up with 3 solutions, but none of them quite fit the bill:

  • Using a class component, this.state will update asynchronously
    • Example
    • Downsides: not a functional component, async function is tied to the component
  • useRef() is a suggested option
    • Example
    • Downsides: we no longer get re-renders, and is this a common useRef pattern?
  • Pass the setter from useState, pass a function when calling it which will retrieve the current value
    • Example
    • Downsides: seems VERY hacky :)

I'd be curious if there's any clean way similar to the 3rd example, that I just haven't come across in my limited React experience. Other suggestions welcome as well!

like image 674
Derek Larson Avatar asked Oct 23 '25 16:10

Derek Larson


1 Answers

To have a reusable function I would define it inside a Hook.

The following proposal uses useState to execute the function. We need useState to trigger a render when the value changes. This value will call the function from inside a useEffect.

It also uses useRef so that the process can start and later read its value, that could have changed during execution.

const App = () => {
    const { startProcess, stopProcess } = useLongProcess();

    return (
        <Fragment>
            <button onClick={startProcess}>Start</button>
            <button onClick={stopProcess}>Stop</button>
        </Fragment>
    );
};

const useLongProcess = () => {
    const stop = useRef(false);
    const [start_process, setStartProcess] = useState(false);

    useEffect(() => {
        if (!start_process) {
            return;
        }

        const longProcess = async () => {
            for (const fileid of files_to_get) {
                if (stop.current) break;
                await getDataFile(fileid);
            }
        };

        longProcess();
    }, [start_process]);

    return {
        startProcess: () => setStartProcess(true),
        stopProcess: () => {
            stop.current = true;
        }
    };
};
like image 64
Alvaro Avatar answered Oct 25 '25 06:10

Alvaro



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!