Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Expose intent-level event callbacks, not internal state mutation
Keep a component's state shape internal and expose simple semantic callbacks; don't make consumers reach in and mutate internal structures.
Bad example
| 1 | // Consumer must know the internal shape and manually keep |
| 2 | // derived fields (count) in sync — a leaky abstraction. |
| 3 | const handleDispatch = (action) => { |
| 4 | switch (action.type) { |
| 5 | case 'reply-created': |
| 6 | setReplies(prev => [action.reply, ...prev]); |
| 7 | setThread(prev => ({ ...prev, repliesCount: prev.repliesCount + 1 })); |
| 8 | break; |
| 9 | // ...more cases the consumer must implement |
| 10 | } |
| 11 | }; |
| 12 |
|
| 13 | <RepliesSection initialReplies={replies} onDispatch={handleDispatch} />; |
Explanation (EN)
Pushing a dispatch/switch into every consumer forces them to understand and re-implement the component's internal data structure, duplicating logic and creating boilerplate at each call site.
Objašnjenje (HR)
Guranje dispatch/switcha u svakog konzumenta tjera ih da razumiju i ponovno implementiraju internu strukturu podataka komponente, duplicirajuci logiku i stvarajuci boilerplate na svakom mjestu poziva.
Good example
| 1 | // Component owns its state shape; consumers react to intent. |
| 2 | <RepliesSection |
| 3 | initialReplies={replies} |
| 4 | onReplyCreated={reply => track('reply_created', reply.id)} |
| 5 | onStateChange={state => persist(state)} |
| 6 | />; |
Explanation (EN)
Intent-level callbacks let consumers run side effects without knowing the internal shape, keeping the component plug-and-play and the call sites clean.
Objašnjenje (HR)
Callbackovi na razini namjere omogucuju konzumentima pokretanje side-efekata bez poznavanja interne strukture, drzeci komponentu plug-and-play, a mjesta poziva ciste.
Notes (EN)
When you find yourself exposing a setter or dispatch so consumers can patch internal fields, that's a signal the abstraction is leaking — add a semantic callback instead.
Bilješke (HR)
Kad se nadjes da izlazes setter ili dispatch da konzumenti krpaju interna polja, to je znak da apstrakcija curi — dodaj semanticki callback umjesto toga.
Exceptions / Tradeoffs (EN)
If the component is genuinely meant to be fully controlled (state owned by the parent), a controlled prop + onChange pair is fine — but that's a deliberate design choice, not a default.
Iznimke / Tradeoffi (HR)
Ako je komponenta stvarno zamisljena kao potpuno kontrolirana (state vlasnistvo roditelja), kontrolirani prop + onChange par je u redu — ali to je namjerna dizajn odluka, ne default.