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. That’s why 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 the shape: the effect
body synchronously writes state, and the cleanup references the same setter. That’s the
subscribe/unsubscribe pair, but it also fires on effects that just reset state in their cleanup, with no
store in sight. Treat the diagnostic as “this has the external-store shape” 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());}