Derek Larson
Derek Larson

Reputation: 53

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:

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!

Upvotes: 4

Views: 6562

Answers (2)

Alvaro
Alvaro

Reputation: 9662

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;
        }
    };
};

Upvotes: 4

Vl4dimyr
Vl4dimyr

Reputation: 916

Right now you have no way of updating the halt state inside the longProcess function once it's called. So you could work with some global object to update the state but that is not best practice.

So maybe take a look at React's useCallback and other hooks, and maybe change your loop to recursively calling one of those hooks so maybe this can help to update the state. Hope this helps even though I don't know a solution right now.

Upvotes: 0

Related Questions