0

I have a React component with a state variable jobs. When the state variable ready is true, it should start executing jobs by a Web Worker (one at a time).

import React, { useEffect, useRef } from 'react'


// create webworker
const job_worker = new Worker("worker.bundle.js", { type: "module" });


function App() {

  const [jobs, set_jobs] = React.useState([
    { ... },
    { ... },
  ])
  const [ready, set_ready] = React.useState(false)

  // start worker loop
  useEffect(() => {
    const worker_loop = async () => {
      setTimeout(async () => {
        // check if ready to execute a job
        if (ready) {    // <== suffers from 'stale closure'

          // grab a job
          const job = jobsRef.current.find(j => !j.done)

          // listen for webworker results
          job_worker.onmessage = (e) => {
            console.log("received response from webworker: '", e.data, "'")
            
            // SET RESULT IN JOB

            // job is handled by worker; now start the worker_loop again
            return worker_loop()
          }
          
          // post job to worker
          job_worker.postMessage({job: job})
          return  // do not continue; 'onmessage' will continue the loop
        }

        return worker_loop()
      }, 1000)
    }

    // start worker_loop
    worker_loop()
  }, [])


  return (
    <div>
      {/* code to add jobs and set ready state */}
    </div>
  );
}

I tried to do this by using an (infinite) worker_loop, which is started when the React component mounts (using useEffect). The loop kinda works, but the ready variable inside the worker_loop stays at the initial state value (known as the 'stale closure' problem). Probably the same for the jobs state variable.

I've already tried to use 'createRef' as suggested here. But the problem persists. Also I feel like there is a much simpler solution.

Is there a better way to handle 'jobs' in a React-state variable? Some sort of 'job-runner process/function' with access to the React component. By the way, I am not obliged to use WebWorker.

crisg1201
  • 21
  • 3
  • why are you using setTimeout? use something else instead of wrapping the whole logic inside setTimeout(I have faced insane closure issues because of that) also pass "ready" in the dependency array. This will most probably solve your closure issue. – Kartik Malik Aug 09 '21 at 19:13
  • I think is challenging. If i must implement a workerin react i would try a useContext with an action define in a useRrducer. For maximum control and avoid mutations due to components destroyed or other states changing. And i belive i wouldnt try it. Isnt possible to run the workers server side? – Jose Javier Sanahuja Aug 09 '21 at 19:32
  • Given that the worker is single and global, I think it makes more sense to control it with some top-level JS code (not in React, but callable from React), with an array of jobs to run, current job being run, etc. With that, you can make a function that returns a promise for running a job, which you can resolve in React via e.g. `useAsync`. – edemaine Aug 09 '21 at 20:11

1 Answers1

1

Thanks for the comments!

It indeed makes more sense to control the jobs outside React. I solved it by creating a global state using @hookstate/core. This makes it possible to access and control the state outside of React. Much cleaner code!

crisg1201
  • 21
  • 3
  • Very interesting. Do you know if i export the global state to any component, the state will function like the useContext or redux? – Jose Javier Sanahuja Aug 11 '21 at 18:25
  • Author of Hookstate here. It will function like React.useState - much simpler than context or redux. Checkout the docs on the global state for hookstate – Andrew Aug 07 '22 at 09:40