Install
openclaw skills install use-effectRefactor React code away from direct useEffect usage. Use when Codex needs to review, rewrite, or prevent useEffect in React components, custom hooks, or frontend architecture; diagnose infinite loops or race conditions caused by effects; replace effect-driven state syncing with derived values, event handlers, query libraries, keyed remounts, or a mount-only wrapper such as useMountEffect; or enforce a no-direct-useEffect rule in linting and agent guidance.
openclaw skills install use-effectInspect the local React patterns before changing code. Prefer the project's existing data-fetching library, lifecycle wrapper, and lint setup over introducing new abstractions.
Treat direct useEffect as a code smell by default. Replace it with clearer control flow unless the code is genuinely synchronizing with an external system on mount or unmount.
useEffect in the relevant scope and classify why it exists.useMountEffect, use it instead of raw useEffect(..., []).If an effect sets state from other props or state, compute the value during render instead.
Common smell:
useEffect(() => {
setFilteredProducts(products.filter((p) => p.inStock));
}, [products]);
Preferred direction:
const filteredProducts = products.filter((p) => p.inStock);
Also collapse multi-step derived values instead of chaining effects through intermediary state.
If an effect fetches data and then writes it into local state, prefer the project's query or loader abstraction. Reuse the codebase's existing library when present.
Common smell:
useEffect(() => {
fetchProduct(productId).then(setProduct);
}, [productId]);
Preferred direction:
const { data: product } = useQuery({
queryKey: ["product", productId],
queryFn: () => fetchProduct(productId),
});
If the project does not already use a client-side query library, check whether the fetch belongs in framework loaders, server components, or route-level data APIs before adding one.
If state is only used as a flag to make an effect do work later, move that work into the event handler that caused it.
Common smell:
useEffect(() => {
if (liked) {
postLike();
setLiked(false);
}
}, [liked]);
Preferred direction:
const handleLike = () => {
postLike();
};
For true setup and cleanup with external systems, use the project's mount-only wrapper if it exists. Keep this category narrow: DOM integration, subscriptions, widget lifecycle, imperative browser APIs.
If a precondition gates the mount-only effect, prefer conditional rendering so the component mounts only when ready.
if (isLoading) return <LoadingScreen />;
return <VideoPlayer />;
Then let VideoPlayer run mount-only setup once.
key, Not Dependency ChoreographyIf the goal is "treat this as a new instance when identity changes", remount with key instead of writing effect logic that tries to reset local state.
return <VideoPlayer key={videoId} videoId={videoId} />;
Use this when the desired behavior is a fresh lifecycle boundary.
Flag direct useEffect when you see:
setState driven by props or other statefetch(...).then(setState) or async loading logic inside an effectDo not remove mount-only effects that are genuinely integrating with external systems unless you replace them with an equivalent lifecycle boundary.
When the user asks for a broader rollout:
useEffect.useMountEffect only if the codebase wants an explicit exception path.Read patterns.md when you need detailed smell tests, larger before/after examples, or help choosing between replacement patterns.