My-opencode-config react-use-state
Guides proper usage of React useState hook. Use this skill when adding state to components, deciding between useState vs alternatives, or troubleshooting state update issues.
install
source · Clone the upstream repo
git clone https://github.com/flpbalada/my-opencode-config
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/flpbalada/my-opencode-config "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/react-use-state" ~/.claude/skills/flpbalada-my-opencode-config-react-use-state && rm -rf "$T"
manifest:
skills/react-use-state/SKILL.mdsource content
React: useState Hook Best Practices
Core Concept
useState is a React Hook that adds a state variable to your component, triggering re-renders when the state changes.
const [state, setState] = useState(initialState);
When to Use useState
Ideal Use Cases
| Use Case | Example |
|---|---|
| Form inputs | |
| UI state | |
| Simple counters | |
| Local component data | |
Use useState When
- State is local to the component
- State transitions are simple (direct value replacement)
- Changes should trigger re-renders
- You need to persist values between renders
When NOT to Use useState
Use useRef Instead
When you need mutable values that don't trigger re-renders:
// Interval IDs, DOM references, previous values const intervalRef = useRef(null); const inputRef = useRef(null);
Use useReducer Instead
When state logic is complex:
// Multiple related values, complex transitions const [state, dispatch] = useReducer(reducer, initialState);
Use
useReducer when:
- State has multiple sub-values
- Next state depends on previous state in complex ways
- You want to centralize state logic
Avoid Redundant State
If a value can be computed from props or other state, don't store it:
// BAD: Redundant state const [fullName, setFullName] = useState(''); useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastName]); // GOOD: Compute during render const fullName = `${firstName} ${lastName}`; // If expensive, use useMemo const sortedItems = useMemo(() => items.sort((a, b) => a.name.localeCompare(b.name)), [items] );
Don't Use for Global/Shared State
For state shared across multiple components:
- React Context for moderate sharing
- External stores (Zustand, Jotai) for complex apps
- Server state libraries (TanStack Query) for async data
Critical Rules
1. Never Mutate State Directly
// BAD: Mutation obj.x = 10; setObj(obj); // React ignores this! // GOOD: Create new object setObj({ ...obj, x: 10 }); // BAD: Array mutation arr.push(item); setArr(arr); // React ignores this! // GOOD: Create new array setArr([...arr, item]);
2. State Updates Are Asynchronous
function handleClick() { setCount(count + 1); console.log(count); // Still old value! // If you need the new value: const nextCount = count + 1; setCount(nextCount); console.log(nextCount); // New value }
3. Use Updater Function for Sequential Updates
// BAD: Only increments by 1 function handleClick() { setCount(count + 1); // 0 + 1 = 1 setCount(count + 1); // 0 + 1 = 1 (same stale value!) setCount(count + 1); // 0 + 1 = 1 } // GOOD: Increments by 3 function handleClick() { setCount(c => c + 1); // 0 -> 1 setCount(c => c + 1); // 1 -> 2 setCount(c => c + 1); // 2 -> 3 }
4. Use Initializer Function for Expensive Initial Values
// BAD: createTodos() runs every render const [todos, setTodos] = useState(createTodos()); // GOOD: createTodos runs only once const [todos, setTodos] = useState(createTodos); // Or with arrow function for arguments const [todos, setTodos] = useState(() => createTodos(userId));
5. Call Hooks at Top Level Only
// BAD: Conditional hook if (condition) { const [state, setState] = useState(0); // Error! } // GOOD: Always call, conditionally use const [state, setState] = useState(0); if (condition) { // use state here }
Common Patterns
Resetting State with Key
// Parent controls reset via key <Form key={version} /> // When version changes, Form remounts with fresh state
Storing Functions in State
// BAD: Function gets called const [fn, setFn] = useState(someFunction); // GOOD: Wrap in arrow function const [fn, setFn] = useState(() => someFunction); setFn(() => newFunction);
Updating Objects/Arrays
// Object: spread and override setForm({ ...form, email: newEmail }); // Nested object setUser({ ...user, address: { ...user.address, city: newCity } }); // Array: filter, map, spread setItems(items.filter(i => i.id !== id)); // Remove setItems([...items, newItem]); // Add setItems(items.map(i => i.id === id ? {...i, done: true} : i)); // Update
Quick Reference
DO
- Use for simple, local component state
- Create new objects/arrays when updating
- Use updater function when depending on previous state
- Use initializer function for expensive initial values
DON'T
- Store computed/derived values
- Mutate existing state objects/arrays
- Read state immediately after setting (it's a snapshot)
- Call
unconditionally during rendersetState
Alternative Hooks Comparison
| Hook | Use When |
|---|---|
| Simple state, primitives, basic objects |
| Complex state logic, multiple sub-values |
| Mutable values without re-renders |
| Expensive computed values |
| State shared across component tree |