Giter Site home page Giter Site logo

pocesar / react-use-comlink Goto Github PK

View Code? Open in Web Editor NEW
45.0 2.0 4.0 125 KB

Three ways to use Comlink web workers through React Hooks (and in a typesafe manner).

License: MIT License

TypeScript 60.47% HTML 1.09% JavaScript 38.45%
react reactjs react-hooks react-hook comlink webworker webworkers web worker hooks

react-use-comlink's Introduction

NPM TypeScript

react-use-comlink

Three ways to use Comlink web workers through React Hooks (and in a typesafe manner).

Usage

// worker.ts
import { expose } from 'comlink'

export class MyClass {
  private _counter: number

  constructor(init: number) {
    this._counter = init
  }

  get counter() {
    return this._counter
  }

  increment(delta = 1) {
    this._counter += delta
  }
}

expose(MyClass)
// index.ts
import React from 'react'
import useComlink from 'react-use-comlink'
import { WorkerClass } from './worker'

const App: React.FC<{startAt: number}> = (props) => {
  const [state, setState] = React.useState(0)

  const { proxy } = useComlink<typeof WorkerClass>(
    () => new Worker('./worker.ts'),
    [ props.startAt ] // used to recreate the worker if it change, defaults to []
  )

  React.useEffect(() => {
    (async () => {
      // methods, constructors and setters are async
      const classInstance = await new proxy(0)

      await classInstance.increment(1)

      // even getters are asynchronous, regardless of type
      setState(await classInstance.counter)
    })()
  }, [proxy])

  return (
    <div>{state}</div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)

Also notice that the worker property is also exposed, so you may use the library directly with workers without having to use Comlink (kinda defeats the purpose, but oh well):

const App = () => {
  const { worker } = useComlink('./worker.js')

  useEffect(() => {
    worker.onmessage = (e) => {
      /*do stuff*/
    }

    worker.onerror = (e) => {
      /*do stuff*/
    }
  }, [worker])

  const callback = useCallback(() => {
    worker.postMessage('wow')
  }, [worker])

  return (<button onClick={callback}>Post WOW</button>)
}

API

The api is pretty straightforward, you have the in loco useComlink, the factory counter part createComlink and the singleton counter part createComlinkSingleton.

useComlink<T = unknown>(initWorker: Blob | string | () => Worker | string | Blob, deps: any[]): { proxy, worker }

Use directly inside components. Both object and properties are memoized and can be used as deps.

const MyComponent: React.FC = () => {
  const { proxy, worker } = useComlink(() => new Worker('./worker.js'), [deps])
}

createComlink<T = unknown>(initWorker: () => Worker | string | Blob, options = {}): () => { proxy, worker }

Creates a factory version that can spawn multiple workers with the same settings

// outside, just like react-cache, createResource
const useNumber = createComlink<number>(
  () => new Worker('worker.js')
)

const MyComponent: React.FC = () => {
  const { proxy, worker } = useNumber() // call like a hook

  useEffect(() => {
    (async () => {
      const number = await proxy
      // use number
    })()
  }, [proxy])

  return null
}

createComlinkSingleton<T = unknown>(initWorker: Worker, options: WorkerOptions = {}): () => { proxy, worker }

If you want to keep the same state between multiple components, be my guest. Not the best choice for modularity, but hey, I just make the tools. Notice that the worker is never terminated, and must be done on demand (on worker.terminate())

const useSingleton = createComlinkSingleton<() => Bad>(new Worker('./bad.idea.worker.js'))

const MyComponent: React.FC = () => {
  const { proxy } = useSingleton()

  useEffect(() => {
    (async () => {
      const isBad = await proxy()
    })()
  }, [proxy])

  return null
}

Comlink

Make sure the read the Comlink documentation, being the most important part what can be structure cloned

Caveats

Every function with Comlink is async (because you're basically communicating to another thread through web workers), even class instantiation (using new), so local state can't be retrieved automatically from the exposed class, object or function, having to resort to wrapping some code with self invoking async functions, or relying on .then(). If your render depends on the external worker result, you'll have to think of an intermediate state.

Although the worker will be terminated when the component is unmounted, your code might try to "set" the state of an unmounted component because of how workers work (:P) on their own separate thread in a truly async manner.

In the future, when react-cache and Concurrent Mode is upon us, this library will be updated to work nicely with Suspense and the async nature of Comlink

Example

Run npm run example from root then open http://localhost:1234

TODO

Write tests (hardcore web workers tests)

License

MIT

react-use-comlink's People

Contributors

pocesar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-use-comlink's Issues

Failing under create-react-app dev server

When running the example under the create-react-app dev server (started via npm run start), the workers don't work on first load. However, after editing a source file the workers will start to work. If you reload the page, the workers again will stop working.

My source code is here: https://github.com/acbeers/worker-test

What I see:

  • on first load, the code in useMemo is called twice. On the first call, the output at line 25 (console.log(res)) shows a resolved promise. On the second call, the promise is shown as pending.
  • when clicking the "Increase from comlink" button, the output at line 31 (console.log(instance)) shows that the promise is still pending.
  • after editing the source and saving, useMemo is again called twice. This time, the output at line 25 shows both promises resolve. And, the button will now work.

Have you gotten this example to work under cra's development server? Many thanks for any advice you can offer

Unexpected token '<'

I created a simple object similar to the example:

import { expose } from 'comlink'

export const SortWorker = {
  hello: 'world',
  sort() {
    return 'sort worker ran!'
  },
}

expose(SortWorker)

But I see this in the console: Uncaught SyntaxError: Unexpected token '<'

I am using create-react-app with hooks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.