hooks-as-store

Use React custom hooks in Svelte Apps.

Examples

Installation

npm install --save-dev hooks-as-store

Usage

Let’s say you have some custom hook code in ‘myhook.js’, which calles built-in hooks or other custom hooks:

import {useState, useEffect} from 'hooks-as-store';

export const myHook = (someprop,otherprop)=>{
    
    const [value,setValue] = useState(someprop);

    useEffect(()=>{
       ...
    },[otherprop]);

    return {value, setValue}
}

In svelte script part you can load this hook like this:

import {hook} from 'hooks-as-store';
import {myHook} from './myhook';

const hookStore = hook(myHook,"someprop", "abc");

First argument to the hook function is the custom hook you want to execute. You can pass props to the hook in the following arguments. Call the hook function in a non-reactive script part of a component (do not use $:). Never call the hook function inside a custom hook. hookStore is a readable store. If you want to access its values, you can unload it like this:

$: ({value,setValue} = $hookStore);

Notice the autosubscription ($hookstore) and the brackets around the whole statement.

The hook is automatically re-run, when a state inside is changed, e.g. when setValue is used.

You can re-execute the hook code with the run propery, eg. myHook.run(props). It does not automatically re-run, whenever the component is updated. If you want this behavior (like it is in React), you need to implement it yourself, eg.:

import {beforeUpdate} from 'svelte';
...
beforeUpdate(()=>{
    myHook.run("someprop", "abc")
})

If that is really necessary depends on your use-case. Often it may be enough to re-execute it when the props change:

let prop1 = "someprop",
    prop2 = "abc";
$: myHook.run(prop1, prop2);

Dependend hooks

If you have more than one custom hooks, which depend on each other, it might be useful to group them. The function hookGroup accepts an array of arrays. In the inner array, the first element should be the hook, the rest is filled with the props for this hook. Eg:

import {hoogGroup} from 'hooks-as-store';
import {hook1,hook2,hook3} from './myhooks.';

const hookResults = hookGroup([
        [hook1,hook1prop1,hook1prop2],//hook1 takes 2 props
        [hook2], //hook2 doesn't take props
        [hook3, hook3props]
    ])
// unwrap hookResults
$: ([{hook1returnvalue},undefined/*hook2 does not return anything*/,
    {hook3returnvalue1,hook3returnvalue2}] = $hookResults);

// re-execute all hooks if any prop changes
$: hookResults.run([hook1prop1,hook1prop2],[],[hook3props]);  

Once a state changes in any of the three hooks, all hooks are re-executed in the same order.

Extenal hooks

You can redirect the imports from ‘react’ to ‘hooks-as-store’ like this in ‘vite.config.js’:

...
const config: UserConfig = {
	...
	resolve:{
		alias:{
			react:'hooks-as-store'
		}
	},
    ...
}
...

For aliases on other bundlers I found this page helpful: Switching to Preact

If React errors occur (Invalid hook call. Hooks can only be called inside of the body of a function component. etc.), try deleting the ‘react’ folder in ‘node_modules’. Another option, which seems to be more persistent, is to go into node_modules/react/package.json and change ‘main’:

{
    "main":"../hooks-as-store/index.js",
}

To also change the imports in external libraries, it might be necessary to mark these libraries as noExternal in ‘vite.config.js’:

...
{
    ...
    ssr:{
		noExternal:["use-media","@wellyshen/use-web-animations"]
	}
}
...

Notes

Most of the built-in react hooks are implemented in this package, but not all are tested very well.

Context

The useContext hook makes use of Svelte contexts. If you can set the context with ‘setContext’ from ‘svelte’, it works. Some libraries offers custom provider components, eg. use-cart. This case is not yet usable with this library.

Contribution

Contributions are welcome. Some topics of interest:

  • good examples for uncommon hooks (eg. useId, useDeferredValue, useImperativeHandle etc.)
  • better/easier ‘alias’ strategy for imported react hooks / better vite configuration
  • Context strategy

The scope of this library is to enable the use of custom react hooks. It can be referenced as part of a general react interop library, but it is not planned to become one itself.

To expand this idea, you could say that a React functional component is a custom hook, which returns a part of a virtual dom tree. You could (easily?) implement a createElement function, obtain the tree from this library and create a generic svelte virtual dom tree renderer component. Something like this is not in the scope of this library, but let me know if you try it!