VIDSOL-623: docs: add coding rules to Copilot instructions - hook patterns and American English#402
Conversation
1. American English spelling — cspell is configured for American English; British spellings (synchronised, initialise, behaviour, colour) fail CI. 2. No direct setState in effect body — the project enforces react-hooks/set-state-in-effect. Wrap setState calls in a named helper function declared inside the effect instead. 3. No setState during render (derived-state pattern) — risks render loops with non-stable references. Use useRef to track the previous prop value and derive the return value directly during render without setState. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates .github/copilot-instructions.md with three new rules to help contributors (and Copilot) avoid mistakes that caused CI failures in PR #396 (the Video Stats Overlay feature). The rules cover American English spelling enforcement, preventing direct setState calls in effect bodies, and preventing setState during render.
Changes:
- Adds a
## Spellingsubsection documenting the@cspell/eslint-pluginAmerican English enforcement and common trap words. - Adds a
## setState in effects and during rendersection documenting the rule against directsetStateinuseEffect/useLayoutEffectbodies, with a named-inner-function workaround. - Adds documentation for the derived-state-during-render anti-pattern and the correct
useRef-based approach for synchronously deriving values from a changed prop.
You can also share your feedback on Copilot code review. Take the survey.
.github/copilot-instructions.md
Outdated
| - **Rule:** Never call `setState` directly inside an effect body (`useEffect` or `useLayoutEffect`). The project enforces `react-hooks/set-state-in-effect`, which flags any synchronous `setState` call at the top level of an effect callback. | ||
| - **Rule:** When you need to update state from inside an effect, wrap the call in a **named helper function** declared inside the effect. Calls through a function are not flagged by the rule. | ||
|
|
||
| **Violation:** | ||
|
|
||
| ```tsx | ||
| // Bad: setState called directly in effect body — triggers react-hooks/set-state-in-effect |
There was a problem hiding this comment.
The inline code comment on this line repeats the incorrect rule name react-hooks/set-state-in-effect. It should be updated to react-hooks/no-direct-set-state-in-use-effect to be consistent with the actual rule name in eslint-plugin-react-hooks v7.
| - **Rule:** Never call `setState` directly inside an effect body (`useEffect` or `useLayoutEffect`). The project enforces `react-hooks/set-state-in-effect`, which flags any synchronous `setState` call at the top level of an effect callback. | |
| - **Rule:** When you need to update state from inside an effect, wrap the call in a **named helper function** declared inside the effect. Calls through a function are not flagged by the rule. | |
| **Violation:** | |
| ```tsx | |
| // Bad: setState called directly in effect body — triggers react-hooks/set-state-in-effect | |
| - **Rule:** Never call `setState` directly inside an effect body (`useEffect` or `useLayoutEffect`). The project enforces `react-hooks/no-direct-set-state-in-use-effect`, which flags any synchronous `setState` call at the top level of an effect callback. | |
| - **Rule:** When you need to update state from inside an effect, wrap the call in a **named helper function** declared inside the effect. Calls through a function are not flagged by the rule. | |
| **Violation:** | |
| ```tsx | |
| // Bad: setState called directly in effect body — triggers react-hooks/no-direct-set-state-in-use-effect |
- Run nx affected lint/test locally before pushing to avoid wasting CI runs - Check CI status after every push (QA + tests workflows) - Do not mark a task complete while CI is pending or failing - Fix failures locally first, verify, then push — no speculative fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
You can also share your feedback on Copilot code review. Take the survey.
| ); | ||
| }; | ||
| ``` No newline at end of file | ||
| --- |
There was a problem hiding this comment.
The closing triple-backtick for the preceding **Correct (explicit linear flow):** code block (which opened at line 1634) was removed in this diff. The new --- horizontal rule is placed immediately after }; on line 1681 without closing the code fence first. This breaks the Markdown structure of the document — the entire section from --- through the ## setState in effects and during render content will be rendered as part of the previous code block, making the new rules invisible and unformatted.
The closing ``` must be re-added before the --- separator.
There was a problem hiding this comment.
Hi @czoli1976, it looks like the code example got cut off here.
| - **Rule:** Do not add new state management libraries. Use only existing tooling. | ||
| - **Rule:** Components must be kept small, focused, and composable. | ||
|
|
||
| ## Spelling |
There was a problem hiding this comment.
I'd prefer to rely on the plugin doing its job rather than unnecessarily increasing the docs. Don't you think this is redundant, @czoli1976? Also, if these instructions are specifically for AI models, I'd argue that it is unlikely for them to have to many grammar mistakes...
| ); | ||
| }; | ||
| ``` No newline at end of file | ||
| --- |
There was a problem hiding this comment.
Hi @czoli1976, it looks like the code example got cut off here.
|
|
||
| --- | ||
|
|
||
| - **Rule:** Never call `setState` during the render function body (derived-state-during-render pattern). This can cause render loops when a caller passes a non-stable reference, and React may log warnings. |
There was a problem hiding this comment.
Hi @czoli1976,
I’m not sure these rules are beneficial.
They implicitly encourage relying on useEffect as a side-effect listener, which tends to push code toward an observable-style pattern. In practice, this often makes components harder to follow, maintain, and debug.
Whenever possible, state transitions should happen in a linear flow during render or in event handlers, rather than being triggered indirectly through effects.
In general, effects should be used to hook into the component lifecycle or synchronize with external systems, not as listeners or triggers for internal state changes.
We already have some rules addressing this area, and we also have the derived-state-during-render rule enabled. Because of that, adding this rule seems both redundant and potentially misleading.
Given this, I think it would be better not to introduce these rules:
- Rule: Never call
setStatedirectly inside an effect body (useEffectoruseLayoutEffect). To follow React best practices, enable the ESLint rulereact-hooks/no-direct-set-state-in-use-effect, which flags any synchronoussetStatecall at the top level of an effect callback.- Already activated, redundant
- Rule: When you need to update state from inside an effect, wrap the call in a named helper function declared inside the effect instead of calling
setStateinline.- We should discourage use of state on useEffects unless is strictly necessary
- Rule: Never call
setStateduring the render function body (derived-state-during-render pattern). This can cause render loops when a caller passes a non-stable reference, and React may log warnings.- Linter already warms about this
- Rule: When you need to derive fresh values from a changed prop without waiting for an effect, track the previous prop value in a
useRefand derive the return value directly during render — mutating a ref does not trigger a re-render.- Example incomplete, could encourage pour UX, there is no way of knowing if stats are been requested, or publisher was not provided, also looks like a very specific use case not so valuable.
|
|
||
| - **Rule:** Before pushing a branch, always run the linter and tests locally for affected projects. Only push once local checks pass. | ||
|
|
||
| ```bash |
There was a problem hiding this comment.
This is misleading... you should run the test for all the projects, we have a mono repo which declare varios projects which then are reuse in others, so green flags at project level doesn't mean that other projects where not affected.
|
We can delete it by now. |
What and Why
This PR adds three new rules to
.github/copilot-instructions.mdbased on mistakes caught during code review and CI failures in PR #396 (VIDSOL-623).Rules added
1. American English spelling
The project uses
cspellconfigured for American English. British spellings such as synchronised, initialise, behaviour, or cancelled fail the👮 QACI job. This rule makes the constraint explicit so contributors know before they push.2. No
setStatedirectly in effect bodiesThe project enforces a non-standard
react-hooks/set-state-in-effectESLint rule that flags any synchronoussetStatecall at the top level of auseEffect/useLayoutEffectcallback. The fix is to wrap the call inside a named helper function declared within the effect. This rule documents the pattern with a before/after example.3. No
setStateduring render (derived-state pattern)Calling
setStateinside the render body when a prop differs from a tracked state value can cause infinite render loops if the caller passes a non-stable reference (e.g. an object literal). The correct approach is to use auseRefto track the last-seen prop and derive the return value directly during render — ref mutation is safe during render and does not trigger a re-render. This rule documents the anti-pattern and the correct ref-derived-return implementation.Reviewers: please pay particular attention to whether the code examples are clear and actionable. The goal is for Copilot (and human contributors) to catch these patterns before code review.