avoidExternalStoreSubscriptionAvoid using an effect to subscribe to an external store. Instead, use "useSyncExternalStore" to manage "{{state}}".small-rules/no-external-store-subscriptionDisallow subscribing to an external store in an effect.
Ported from
eslint-plugin-react-you-might-not-need-an-effect/no-external-store-subscription.
The upstream rule points straight at the “Subscribing to an external
store” section of
the React docs, so that’s where the intent comes from.
By “external state” I mean state that React doesn’t own. You can read it and subscribe to it, but you can’t
derive it from props or other state. On the web that’s browser APIs or your store. roblox-ts has no window
or navigator, so in practice it’s stores: store.get() to read, store.subscribe() to listen,
store.unsubscribe() to clean up. The examples here use a store instead of the online-status code from the
React docs.
The rule doesn’t actually check that you subscribed to anything, though. It keys off this pattern: the
effect body synchronously writes state, and the cleanup references the same setter. That’s the
subscribe/unsubscribe pair, but it also flags effects that just reset state in their cleanup, with no store
in sight. Treat the diagnostic as “this looks like an external store” and check whether there’s a real store
before reaching for useSyncExternalStore.
avoidExternalStoreSubscriptionAvoid using an effect to subscribe to an external store. Instead, use "useSyncExternalStore" to manage "{{state}}".This rule accepts one options object after the severity.
import { useEffect, useState } from "@rbxts/react";
interface Store { get: () => number; subscribe: (callback: () => void) => void; unsubscribe: (callback: () => void) => void;}
export function useStoreValue(store: Store): number { const [value, setValue] = useState(0); useEffect(() => { setValue(store.get()); function update(): void { setValue(store.get()); } store.subscribe(update); return (): void => { store.unsubscribe(update); }; }, [store]); return value;}import { useSyncExternalStore } from "@rbxts/react";
interface Store { get: () => number; subscribe: (callback: () => void) => void; unsubscribe: (callback: () => void) => void;}
declare const store: Store;
function subscribe(callback: () => void): () => void { store.subscribe(callback); return (): void => { store.unsubscribe(callback); };}
export function useStoreValue(): number { return useSyncExternalStore(subscribe, (): number => store.get());}