Skip to main content

injectEffect

import { injectEffect } from '@zedux/react'

An injector that attaches a side effect to an atom instance. Runs the effect on initial atom evaluation and again every time the passed dependencies change on subsequent evaluations.

This is almost exactly equivalent to React's useEffect() hook. injectEffect() has two additional features:

  • injectEffect() accepts an async callback function (a function that returns a promise).
  • injectEffect() accepts a 3rd config parameter.

Async Callback

injectEffect(async () => {
const val = await fetchVal()
}, [])

This is only for convenience in cases where you don't have any cleanup. When you do have cleanup tasks to perform, the basically ugly useEffect workarounds are still what we use:

injectEffect(() => {
const controller = new AbortController()

const asyncFn = async () => {
const data = await fetch(url, { signal: controller.signal })
...
}

return () => controller.abort()
})
note

We may make useEffect callbacks take an AbortController to hook into to perform cleanup tasks. If that sounds useful for you, open an issue and let's discuss!

SSR

injectEffect() does not run the callback when the ssr ecosystem config option is set to true. This mimics useEffect's SSR behavior.

Examples

Live Sandbox
1234567891011121314151617
const secondsAtom = atom('seconds', () => {
const store = injectStore(0)

injectEffect(() => {
const intervalId = setInterval(() => store.setState(val => val + 1), 1000)

return () => clearInterval(intervalId)
}, [])

return store
})

function Seconds() {
const state = useAtomValue(secondsAtom)

return <div>Seconds: {state}</div>
}

Miscellaneous:

// empty deps - only runs once - when the atom instance is created.
injectEffect(sideEffect, [])

// no deps - runs on every evaluation
injectEffect(sideEffect)

// with deps - runs again when any deps change
injectEffect(sideEffect, [depA, depB])

// return a cleanup function that will run when this atom instance is destroyed
injectEffect(() => {
const subscription = stream.subscribe(...)

return () => subscription.unsubscribe()
}, [])

// the cleanup function will also run every time deps change
injectEffect(() => {
const subscription = stream.subscribe(...)

return () => subscription.unsubscribe()
}, [depA, depB])

// async functions supported (can't return a cleanup function if you do this)
injectEffect(async () => {
const val = await fetchVal()
})

Signature

injectEffect = (effect, deps?, config?) => void
effect

Required. A function.

The effect can do anything but it shouldn't reference unstable variables outside the effect, unless those variables are added to the deps array.

Signature:

effect = () => cleanup
Returns

A cleanup function, a promise, or nothing.

Return a function to clean up subscriptions, clear timeouts, destroy resources, and generally prevent memory leaks.

If a cleanup function is returned, it will be called when the effect reruns due to deps changing or when the current atom instance is destroyed. On deps change, this is called before the effect function is rerun.

deps

Optional (though you'll almost always want to pass it). An array containing absolutely anything.

If any items in this array change on a subsequent evaluation, the previous effect's cleanup function will be called (if you returned one) and the effect will be rerun.

Pass an empty array to prevent the effect from ever rerunning, as long as this atom instance is alive:

injectEffect(oneOffEffect, [])

If not passed, the effect will rerun on every evaluation. This is almost never what you want.

config

Optional. An object containing a single optional boolean property:

{ synchronous }

By default, injectEffect() defers executing the side effect until the next turn of the event loop. This mimics useEffect's behavior.

Passing { synchronous: true } prevents injectEffect() from deferring execution of the callback. It will run immediately.

This can be useful for situations where you're expecting an atom to be used exclusively outside React. The default behavior of deferring side effects can be counterintuitive when e.g. an atom instance is created, used, and destroyed synchronously in one turn of the event loop. In this case, effects declared in that atom will never run. Use the synchronous option to make sure they do.

Returns
Nothing. Void. Emptiness.

See Also