use-imported-hook

    1.1.5 • Public • Published

    use-imported-hook

    unit tests gzipped size PRs welcome

    Description

    This package allows you to dynamically import any hook in a React component!

    🎉 Lazy load a component's logic 🎉

    // MyComponent.jsx (importer)
    import useImportedHook from 'use-imported-hook/hook'
    
    export default function MyComponent() {
    	const [load, setLoad] = useState(false)
    	useImportedHook(
    		load && import('./useLazyHook.jsx'),
    	)
    	return (
    		<button onClick={() => setLoad(true)}>
    			Click me
    		</button>
    	)
    }

    🎉 Lazy load a custom hook 🎉

    // useHook.jsx (importer)
    import useImportedHook from 'use-imported-hook/hook'
    
    export default function useHook({load, ...props}) {
    	return useImportedHook(
    		load && import('./useLazyHook.jsx'),
    		props
    	)
    }

    🎉 And still write your importable hook like any hook you're used to 🎉

    // useLazyHook.jsx (importee)
    import { useEffect, useCallback, useState } from 'react'
    
    /* @__IMPORTABLE_HOOK__ */
    export default function useLazyHook({a, b, c}) {
    	const [d, setD] = useState(false)
    
    	useEffect(() => {
    		// ...
    	}, [a, b])
    
    	return useCallback(() => {
    		// ...
    	}, [c])
    }

    In the examples above, useLazyHook will only be loaded if load is true. This allows you to defer the loading of most of your components' logic (everything that isn't needed for the initial render).

    Setup

    1. install the package

      npm i --save use-imported-hook
    2. add the Babel plugin to your config

      // .babelrc.js
      module.exports = {
      	// ...
      	plugins: [
      		'module:use-imported-hook'
      	]
      }

    Syntax for useImportedHook

    useImportedHook<T, U>(
    	importPromise: false | Promise<{default: function(U): T}>,
    	parameters: T?,
    	defaultReturn: U?
    ): U
    Argument Required Example
    importPromise true bool && import('./relative-path.jsx')
    parameters false { a, b }
    defaultReturn false ""

    Parameters

    • importPromise (required)

      can either be falsy in which case the hook won't be loaded, or it can be a the promise returned by import(). By using it in combination with import(), webpack is able to package the hook in a separate chunk and to load it on demand.

      • The path passed to import() must be a relative path for babel to resolve it properly

        PRs welcome

          useImportedHook(bool && import('/src/hooks/useHook.jsx'))
          useImportedHook(bool && import('@alias/useHook.jsx'))
          useImportedHook(bool && import('./useHook.jsx'))
      • The path passed to import() must be a string literal for babel to run a static code analysis

        PRs won't fix

          useImportedHook(bool && import(`./${hook}.jsx`))
          useImportedHook(bool && import('./useHook.jsx'))
    • parameters (optional)

      is optional and will default to {}. Do note that it is a single argument, so if you need to pass more than one thing to your hook, you can use {a, b, c} or [a, b, c].

    • defaultReturn (optional)

      The return value of useImportedHook as long as the hook hasn't loaded yet.

    Return value

    • While importPromise is either falsy or pending, useImportedHook returns defaultReturn.

    • Once importPromise is truthy and resolves, useImportedHook returns whatever the imported hook returns.

    Syntax for the imported hook

    Because we do static code analysis with a Babel transform plugin to achieve this result, there are a few requirements to keep in mind:

    • The function containing all the built-in hooks must be the default export

        function withHooks() {
       	useEffect(() => {/*...*/})
        }
        export default function() {
       	withHooks()
        }
        function withHooks() {
       	useEffect(() => {/*...*/})
        }
        export default withHooks
    • The function containing all the built-in hooks must be labeled with a leading comment containing the exact string @__IMPORTABLE_HOOK__

       /* @__IMPORTABLE_HOOK__ */
       export default function() {
       	useEffect(() => {/*...*/})
       }
    • All of your built-in hooks must be in a single function

      PRs won't fix

        function moreStuff() {
       	useEffect(() => {
       		// ...
       	})
        }
      
       /* @__IMPORTABLE_HOOK__ */
       export default function useLazyHook() {
       	moreStuff()
       	return useCallback(() => { /* ... */ })
       }
       /* @__IMPORTABLE_HOOK__ */
       export default function useLazyHook() {
       	useEffect(() => {
       		// ...
       	})
       	return useCallback(() => { /* ... */ })
       }
    • Not all initial values for useState and useRef can be extracted statically

      PRs welcome

      • Allowed initial values
        • true and false
        • 0, 1, 2... (all integers)
        • 0.5, .1... (all floats)
        • "", "hello world" (all strings)
        • {} (empty object)
        • [] (empty array)
        • {a: 1} (non-empty objects if values are themselves allowed values)
        • [0, 1] (non-empty arrays if items are themselves allowed values)
        • !0, -1 (unary expressions)
        • null, undefined, NaN, Infinity
      • Forbidden initial values
        • myVar (variable identifiers)
        • `hello ${"world"}` (template literals)
        • () => {} (functions and arrow functions)

    Limitation: forbidden built-in hooks

    PRs welcome

    Currently, only a subset of all built-in Hooks in React are supported inside the imported hook:

    • useEffect
    • useCallback
    • useMemo
    • useLayoutEffect
    • useImperativeHandle
    • useDebugValue
    • useState
    • useRef
    • useReducer
    • useContext

    If your imported hook needs to use unsupported built-in hooks, the best approach is to declare the unsupported hooks before useImportedHook and pass them as arguments:

    // useHook.jsx (importer)
    import { useContext } from 'react'
    import useImportedHook from 'use-imported-hook/hook'
    
    export default function useHook({shouldLoad, ...props}) {
    	const a = useContext(MyContext)
    	useImportedHook(
    		shouldLoad && import('./useLazyHook.jsx'),
    		{...props, a}
    	)
    }
    // useLazyHook.jsx (importee)
    import { useEffect } from 'react'
    
    /* @__IMPORTABLE_HOOK__ */
    export default function useLazyHook({a, b}) {
    	useEffect(() => {
    		console.log(a) // value of `MyContext` provider
    	}, [a, b])
    }

    Limitation: multiple useImportedHook per component

    PRs welcome

    Currently, we don't support importing several hooks from within a single component (or hook).

    export default function MyComponent() {
    	useImportedHook(a && import('./hook1.jsx'))
    	useImportedHook(b && import('./hook2.jsx'))
    	return <></>
    }

    Managing Webpack chunks

    If you have several components that will load their hooks at the same time, you can give a clue to webpack to package them together in the same chunk:

    export default function ChatComponent({userLoggedIn}) {
    	useImportedHook(userLoggedIn && import(
    		/* webpackMode: "lazy-once" */
    		/* webpackChunkName: "user-logged-in" */
    		'./useChatHook.jsx'),
    	)
    	return <></>
    }
    export default function AccountSettings({userLoggedIn}) {
    	useImportedHook(userLoggedIn && import(
    		/* webpackMode: "lazy-once" */
    		/* webpackChunkName: "user-logged-in" */
    		'./useSettingsHook.jsx'),
    	)
    	return <></>
    }

    In the above example, webpack will put both useChatHook.jsx and useSettingsHook.jsx in the same .js chunk file.

    Install

    npm i use-imported-hook

    DownloadsWeekly Downloads

    44

    Version

    1.1.5

    License

    MIT

    Unpacked Size

    52.7 kB

    Total Files

    107

    Last publish

    Collaborators

    • sheraff