feat: refactor 30+ skills to Anthropic progressive disclosure pattern
- All SKILL.md files now <500 lines (avg reduction 69%) - Detailed content extracted to references/ subdirectories - Frontmatter standardised: only name + description (Anthropic standard) - New skills: brand-guidelines, spec-coauthor, report-templates, skill-creator - Design skills: anti-slop guidelines, premium-proposals reference - Removed non-standard frontmatter fields (triggers, version, author, category) Plugins affected: infraestrutura, marketing, dev-tools, crm-ops, gestao, core-tools, negocio, perfex-dev, wordpress, design-media Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,28 @@
|
||||
---
|
||||
name: react-patterns
|
||||
description: Modern React patterns and best practices. Implements hooks, context,
|
||||
composition, and performance optimization patterns. Use when user mentions "react
|
||||
patterns", "hooks", "react best practices", "component design", "react optimization".
|
||||
author: Descomplicar® Crescimento Digital
|
||||
version: 2.0.0
|
||||
quality_score: 80
|
||||
user_invocable: true
|
||||
desk_task: 1478
|
||||
description: Padroes modernos React (18+/19) -- hooks, context, composicao, memoizacao, lazy loading e Server Components.
|
||||
---
|
||||
|
||||
# React Patterns
|
||||
|
||||
Skill para desenvolvimento React seguindo padrões modernos (React 18+/19).
|
||||
Skill para desenvolvimento React seguindo padroes modernos (React 18+/19).
|
||||
|
||||
> **Regra #48:** Projectos React (incluindo Next.js) devem ser desenvolvidos no **container dev** (`server:"dev"`, path `/root/Dev/<projecto>`). Sincroniza automaticamente com o PC local via Syncthing.
|
||||
## Contexto NotebookLM
|
||||
|
||||
ANTES de executar, consultar notebooks para contexto especializado:
|
||||
|
||||
| Notebook | ID | Consultar quando |
|
||||
|----------|-----|-----------------|
|
||||
| Programacao | 24947ffa-0019-448a-a340-2f4a275d2eb1 | Sempre |
|
||||
|
||||
```
|
||||
mcp__notebooklm__notebook_query({
|
||||
notebook_id: "24947ffa-0019-448a-a340-2f4a275d2eb1",
|
||||
query: "<adaptar ao contexto do pedido do utilizador>"
|
||||
})
|
||||
```
|
||||
|
||||
> **Regra #48:** Projectos React devem ser desenvolvidos no **container dev** (`server:"dev"`, path `/root/Dev/<projecto>`). Sincroniza via Syncthing.
|
||||
|
||||
## Quando Usar
|
||||
|
||||
@@ -24,65 +32,52 @@ Skill para desenvolvimento React seguindo padrões modernos (React 18+/19).
|
||||
- Migrar de class components para hooks
|
||||
- Usar Server Components (Next.js App Router)
|
||||
|
||||
## Regras Core
|
||||
|
||||
### Hooks Fundamentais
|
||||
## Hooks Fundamentais
|
||||
|
||||
```jsx
|
||||
// ✅ useState - estado simples
|
||||
// useState - estado simples
|
||||
const [count, setCount] = useState(0)
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
|
||||
// ✅ useEffect - side effects
|
||||
// useEffect - side effects
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
return () => cleanup() // cleanup function
|
||||
}, [dependency]) // array dependências
|
||||
return () => cleanup()
|
||||
}, [dependency])
|
||||
|
||||
// ✅ useCallback - memoizar funções
|
||||
const handleClick = useCallback(() => {
|
||||
doSomething(id)
|
||||
}, [id])
|
||||
// useCallback - memoizar funcoes
|
||||
const handleClick = useCallback(() => { doSomething(id) }, [id])
|
||||
|
||||
// ✅ useMemo - memoizar valores computados
|
||||
const expensiveValue = useMemo(() => {
|
||||
return computeExpensive(data)
|
||||
}, [data])
|
||||
// useMemo - memoizar valores computados
|
||||
const expensiveValue = useMemo(() => computeExpensive(data), [data])
|
||||
|
||||
// ✅ useRef - referências mutáveis
|
||||
// useRef - referencias mutaveis
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
```
|
||||
|
||||
### Component Patterns
|
||||
## Component Patterns
|
||||
|
||||
```jsx
|
||||
// ✅ CORRECTO: Functional components
|
||||
// SIM: Functional components
|
||||
function UserCard({ user, onSelect }: Props) {
|
||||
return (
|
||||
<div onClick={() => onSelect(user.id)}>
|
||||
{user.name}
|
||||
</div>
|
||||
)
|
||||
return <div onClick={() => onSelect(user.id)}>{user.name}</div>
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Composition over props drilling
|
||||
// SIM: Composition over props drilling
|
||||
function Layout({ children }) {
|
||||
return <main className="container">{children}</main>
|
||||
}
|
||||
|
||||
// ❌ ERRADO: Class components (legacy)
|
||||
class UserCard extends React.Component { ... }
|
||||
// NAO: Class components (legacy)
|
||||
```
|
||||
|
||||
### State Management
|
||||
## State Management
|
||||
|
||||
```jsx
|
||||
// ✅ Local state: useState
|
||||
// Para estado de um componente
|
||||
// Local state: useState (estado de um componente)
|
||||
|
||||
// ✅ Shared state: Context + useReducer
|
||||
// Shared state: Context + useReducer
|
||||
const AppContext = createContext<AppState | null>(null)
|
||||
|
||||
function AppProvider({ children }) {
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
return (
|
||||
@@ -92,73 +87,86 @@ function AppProvider({ children }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Server state: React Query / SWR
|
||||
// Server state: React Query / SWR
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['users'],
|
||||
queryFn: fetchUsers,
|
||||
})
|
||||
|
||||
// ❌ ERRADO: Redux para tudo
|
||||
// ❌ ERRADO: Estado global para estado local
|
||||
```
|
||||
|
||||
### Server Components (Next.js 13+)
|
||||
**NAO:** Redux para tudo. Estado global para estado local.
|
||||
|
||||
## Server Components (Next.js 13+)
|
||||
|
||||
```jsx
|
||||
// ✅ Server Component (default) - async, data fetching
|
||||
// Server Component (default) - async, data fetching
|
||||
async function UserList() {
|
||||
const users = await db.users.findMany() // Direct DB access
|
||||
const users = await db.users.findMany()
|
||||
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
|
||||
}
|
||||
|
||||
// ✅ Client Component - interactividade
|
||||
// Client Component - interactividade
|
||||
'use client'
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
|
||||
}
|
||||
|
||||
// ✅ Padrão: Server parent + Client children
|
||||
// page.tsx (Server) → InteractiveSection.tsx ('use client')
|
||||
// Padrao: Server parent + Client children
|
||||
// page.tsx (Server) -> InteractiveSection.tsx ('use client')
|
||||
```
|
||||
|
||||
### Performance
|
||||
## Performance
|
||||
|
||||
```jsx
|
||||
// ✅ Lazy loading componentes
|
||||
// Lazy loading componentes
|
||||
const HeavyComponent = lazy(() => import('./HeavyComponent'))
|
||||
|
||||
<Suspense fallback={<Loading />}>
|
||||
<HeavyComponent />
|
||||
</Suspense>
|
||||
|
||||
// ✅ React.memo para componentes puros
|
||||
// React.memo para componentes puros
|
||||
const MemoizedCard = memo(function Card({ data }) {
|
||||
return <div>{data.title}</div>
|
||||
})
|
||||
|
||||
// ✅ Virtualização para listas grandes
|
||||
// Virtualizacao para listas grandes
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
```
|
||||
|
||||
## Composition Patterns (Vercel Engineering)
|
||||
|
||||
Padroes de composicao para componentes React escalaveis. Ver `references/composition-patterns.md` para codigo detalhado.
|
||||
|
||||
**Regras principais:**
|
||||
|
||||
| Regra | Prioridade | Resumo |
|
||||
|-------|-----------|--------|
|
||||
| Evitar Boolean Props | CRITICAL | Variantes explicitas em vez de booleans |
|
||||
| Compound Components | HIGH | Context partilhado, composicao explicita |
|
||||
| State em Providers | HIGH | Boundary do Provider e o que importa |
|
||||
| Interface Generica | HIGH | state/actions/meta para dependency injection |
|
||||
| Children > Render Props | MEDIUM | Composicao natural com children |
|
||||
| React 19 APIs | MEDIUM | `use()` e ref como prop |
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-Pattern | Problema | Solução |
|
||||
| Anti-Pattern | Problema | Solucao |
|
||||
|--------------|----------|---------|
|
||||
| useEffect para tudo | Complexidade | Server Components / React Query |
|
||||
| Props drilling | Manutenção | Context ou Composition |
|
||||
| Props drilling | Manutencao | Context ou Composition |
|
||||
| Inline functions em JSX | Re-renders | useCallback |
|
||||
| State em URL | Perda estado | URL state com hooks |
|
||||
| Class components | Legacy | Functional + Hooks |
|
||||
| Index como key | Bugs listas | ID único |
|
||||
| Index como key | Bugs listas | ID unico |
|
||||
|
||||
## Checklist Componente
|
||||
|
||||
- [ ] Functional component (não class)
|
||||
- [ ] Functional component (nao class)
|
||||
- [ ] TypeScript types/interfaces
|
||||
- [ ] Props destructuring com defaults
|
||||
- [ ] Hooks no topo (não condicionais)
|
||||
- [ ] Keys únicos em listas
|
||||
- [ ] Hooks no topo (nao condicionais)
|
||||
- [ ] Keys unicos em listas
|
||||
- [ ] Error boundaries para erros
|
||||
- [ ] Loading states considerados
|
||||
|
||||
@@ -166,423 +174,18 @@ import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
|
||||
```
|
||||
components/
|
||||
├── ui/ # Componentes base (Button, Input, Card)
|
||||
├── features/ # Componentes feature-specific
|
||||
├── layouts/ # Layout components
|
||||
└── providers/ # Context providers
|
||||
ui/ # Componentes base (Button, Input, Card)
|
||||
features/ # Componentes feature-specific
|
||||
layouts/ # Layout components
|
||||
providers/ # Context providers
|
||||
|
||||
hooks/
|
||||
├── useAuth.ts
|
||||
├── useLocalStorage.ts
|
||||
└── useDebounce.ts
|
||||
useAuth.ts
|
||||
useLocalStorage.ts
|
||||
useDebounce.ts
|
||||
```
|
||||
|
||||
## Custom Hooks Úteis
|
||||
## Referencias
|
||||
|
||||
### useDebounce
|
||||
|
||||
```tsx
|
||||
function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
// Uso
|
||||
const searchTerm = useDebounce(input, 500);
|
||||
```
|
||||
|
||||
### useLocalStorage
|
||||
|
||||
```tsx
|
||||
function useLocalStorage<T>(key: string, initialValue: T) {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
};
|
||||
|
||||
return [storedValue, setValue] as const;
|
||||
}
|
||||
```
|
||||
|
||||
### useFetch
|
||||
|
||||
```tsx
|
||||
function useFetch<T>(url: string) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(setData)
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [url]);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
```
|
||||
|
||||
## Padrões Avançados
|
||||
|
||||
### Compound Components
|
||||
|
||||
```tsx
|
||||
// Tab.tsx
|
||||
const TabsContext = createContext<{
|
||||
activeTab: string;
|
||||
setActiveTab: (id: string) => void;
|
||||
} | null>(null);
|
||||
|
||||
function Tabs({ children, defaultTab }: Props) {
|
||||
const [activeTab, setActiveTab] = useState(defaultTab);
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Tabs.List = function TabList({ children }: Props) {
|
||||
return <div className="tab-list">{children}</div>;
|
||||
};
|
||||
|
||||
Tabs.Tab = function Tab({ id, children }: Props) {
|
||||
const { activeTab, setActiveTab } = useContext(TabsContext)!;
|
||||
return (
|
||||
<button
|
||||
className={activeTab === id ? 'active' : ''}
|
||||
onClick={() => setActiveTab(id)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.Panel = function TabPanel({ id, children }: Props) {
|
||||
const { activeTab } = useContext(TabsContext)!;
|
||||
return activeTab === id ? <div>{children}</div> : null;
|
||||
};
|
||||
|
||||
// Uso
|
||||
<Tabs defaultTab="home">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab id="home">Home</Tabs.Tab>
|
||||
<Tabs.Tab id="profile">Profile</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel id="home">Home content</Tabs.Panel>
|
||||
<Tabs.Panel id="profile">Profile content</Tabs.Panel>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Render Props (legacy, preferir hooks)
|
||||
|
||||
```tsx
|
||||
// Evitar - usar custom hook
|
||||
<DataFetcher url="/api/users" render={data => <UserList users={data} />} />
|
||||
|
||||
// Preferir
|
||||
const { data } = useFetch('/api/users');
|
||||
return <UserList users={data} />;
|
||||
```
|
||||
|
||||
## Composition Patterns (Vercel Engineering)
|
||||
|
||||
Padrões de composição para componentes React escaláveis. Fonte: Vercel composition-patterns.
|
||||
|
||||
### Regra #1: Evitar Boolean Props (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// ❌ ERRADO: Booleans duplicam estados possíveis
|
||||
<Composer isThread isEditing={false} showAttachments showFormatting={false} />
|
||||
|
||||
// ✅ CORRECTO: Variantes explícitas
|
||||
<ThreadComposer channelId="abc" />
|
||||
<EditMessageComposer messageId="xyz" />
|
||||
<ForwardMessageComposer messageId="123" />
|
||||
```
|
||||
|
||||
Cada boolean duplica os estados possíveis. 5 booleans = 32 combinações, maioria impossível.
|
||||
|
||||
### Regra #2: Compound Components com Context (HIGH)
|
||||
|
||||
```tsx
|
||||
// ✅ Compound Components: context partilhado, composição explícita
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null)
|
||||
|
||||
function ComposerFrame({ children }: { children: React.ReactNode }) {
|
||||
return <form>{children}</form>
|
||||
}
|
||||
|
||||
function ComposerInput() {
|
||||
const { state, actions: { update }, meta } = use(ComposerContext)
|
||||
return (
|
||||
<TextInput
|
||||
ref={meta.inputRef}
|
||||
value={state.input}
|
||||
onChangeText={(text) => update(s => ({ ...s, input: text }))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Exportar como compound
|
||||
const Composer = {
|
||||
Frame: ComposerFrame,
|
||||
Input: ComposerInput,
|
||||
Submit: ComposerSubmit,
|
||||
Footer: ComposerFooter,
|
||||
}
|
||||
|
||||
// Uso: composição clara do que cada variante renderiza
|
||||
<Composer.Frame>
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
```
|
||||
|
||||
### Regra #3: State em Providers (HIGH)
|
||||
|
||||
```tsx
|
||||
// ❌ ERRADO: Estado preso dentro do componente
|
||||
function ForwardComposer() {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Frame>...</Composer.Frame>
|
||||
}
|
||||
// Como ForwardButton acede ao state? useEffect sync? Refs? Prop drilling?
|
||||
|
||||
// ✅ CORRECTO: State lifted para Provider
|
||||
function ForwardProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const submit = useForwardMessage()
|
||||
const inputRef = useRef(null)
|
||||
return (
|
||||
<Composer.Provider
|
||||
state={state}
|
||||
actions={{ update: setState, submit }}
|
||||
meta={{ inputRef }}
|
||||
>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Agora QUALQUER componente dentro do Provider acede ao state
|
||||
function ForwardDialog() {
|
||||
return (
|
||||
<ForwardProvider>
|
||||
<Dialog>
|
||||
<ForwardComposer />
|
||||
<MessagePreview /> {/* acede state via context */}
|
||||
<ForwardButton /> {/* acede submit via context */}
|
||||
</Dialog>
|
||||
</ForwardProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// Botão FORA do Composer.Frame mas DENTRO do Provider
|
||||
function ForwardButton() {
|
||||
const { actions } = use(ComposerContext)
|
||||
return <Button onPress={actions.submit}>Forward</Button>
|
||||
}
|
||||
```
|
||||
|
||||
**Princípio chave:** O boundary do Provider é o que importa, não o nesting visual.
|
||||
|
||||
### Regra #4: Interface Genérica para Dependency Injection (HIGH)
|
||||
|
||||
```tsx
|
||||
// ✅ Definir interface genérica: state + actions + meta
|
||||
interface ComposerContextValue {
|
||||
state: { input: string; attachments: Attachment[]; isSubmitting: boolean }
|
||||
actions: { update: (fn: (s: State) => State) => void; submit: () => void }
|
||||
meta: { inputRef: React.RefObject<TextInput> }
|
||||
}
|
||||
|
||||
// ✅ Múltiplos providers implementam a MESMA interface
|
||||
function ChannelProvider({ channelId, children }) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
return <Composer.Provider state={state} actions={{ update, submit }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
function ForwardProvider({ children }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Provider state={state} actions={{ update: setState, submit: forward }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
// O mesmo Composer.Input funciona com AMBOS os providers!
|
||||
```
|
||||
|
||||
**Princípio:** UI = pedaços reutilizáveis compostos. State = injectado pelo provider. Trocar provider, manter UI.
|
||||
|
||||
### Regra #5: Children > Render Props (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ❌ Render props (inflexível, difícil de ler)
|
||||
<Composer
|
||||
renderHeader={() => <Header />}
|
||||
renderFooter={() => <><Formatting /><Emojis /></>}
|
||||
/>
|
||||
|
||||
// ✅ Children (composição natural)
|
||||
<Composer.Frame>
|
||||
<Header />
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Formatting />
|
||||
<Emojis />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
|
||||
// Excepção: render props quando parent passa dados
|
||||
<List data={items} renderItem={({ item }) => <Item item={item} />} />
|
||||
```
|
||||
|
||||
### Regra #6: React 19 APIs (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ✅ React 19: ref como prop normal (sem forwardRef)
|
||||
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
|
||||
return <input ref={ref} {...props} />
|
||||
}
|
||||
|
||||
// ✅ React 19: use() em vez de useContext()
|
||||
const value = use(MyContext) // pode ser chamado condicionalmente!
|
||||
```
|
||||
|
||||
### Checklist Composição
|
||||
|
||||
- [ ] Zero boolean props para variantes (usar componentes explícitos)
|
||||
- [ ] Compound components com context partilhado
|
||||
- [ ] State em Provider (não dentro do componente UI)
|
||||
- [ ] Interface genérica (state/actions/meta)
|
||||
- [ ] Children para composição, render props só para data passthrough
|
||||
- [ ] React 19: `use()` e ref como prop
|
||||
|
||||
---
|
||||
|
||||
## Datasets Dify (Consulta Obrigatória)
|
||||
|
||||
Em caso de dúvidas ou para aprofundar conhecimento, consultar os seguintes datasets via MCP:
|
||||
|
||||
| Dataset | ID | Prioridade |
|
||||
|---------|----|-----------:|
|
||||
| **TI (Tecnologia da Informação)** | `7f63ec0c-6321-488c-b107-980140199850` | 1 |
|
||||
| **Desenvolvimento de Software** | `e7c7decc-0ded-4351-ab14-b110b3c38ec9` | 1 |
|
||||
| **Desenvolvimento de WebSites** | `c8489151-de94-42b2-8cee-c0b961cfac6d` | 2 |
|
||||
| **UX e Usabilidade** | `e14ab89e-8910-43b6-becf-d57c78afd62d` | 3 |
|
||||
|
||||
### Como Consultar
|
||||
|
||||
```javascript
|
||||
// Pesquisar padrões React
|
||||
mcp__notebooklm__notebook_query, mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "e7c7decc-0ded-4351-ab14-b110b3c38ec9",
|
||||
query: "react hooks state management"
|
||||
})
|
||||
|
||||
// Server Components Next.js
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "7f63ec0c-6321-488c-b107-980140199850",
|
||||
query: "nextjs server components app router"
|
||||
})
|
||||
|
||||
// UX e performance
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "e14ab89e-8910-43b6-becf-d57c78afd62d",
|
||||
query: "performance loading states"
|
||||
})
|
||||
```
|
||||
|
||||
### Quando Consultar
|
||||
|
||||
- Implementar hooks customizados
|
||||
- Escolher state management
|
||||
- Optimizar performance (memoization, lazy loading)
|
||||
- Server Components vs Client Components
|
||||
- Padrões de UX em React
|
||||
|
||||
---
|
||||
**Versão**: 1.0.0 | **Autor**: Descomplicar®
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Quando NÃO Usar
|
||||
|
||||
- Para tarefas fora do domínio de especialização desta skill
|
||||
- Quando outra skill mais específica está disponível
|
||||
- Para operações que requerem aprovação manual obrigatória
|
||||
- Quando os requisitos não estão claramente definidos
|
||||
|
||||
|
||||
## Protocolo de Execução
|
||||
|
||||
1. **Análise Inicial**
|
||||
- Verificar requisitos e contexto
|
||||
- Identificar ferramentas necessárias
|
||||
|
||||
2. **Preparação**
|
||||
- Validar acesso a recursos
|
||||
- Preparar ambiente de trabalho
|
||||
|
||||
3. **Execução**
|
||||
- Executar operações de forma incremental
|
||||
- Validar cada passo antes de prosseguir
|
||||
|
||||
4. **Validação**
|
||||
- Verificar resultados obtidos
|
||||
- Confirmar sucesso da operação
|
||||
|
||||
5. **Conclusão**
|
||||
- Documentar alterações realizadas
|
||||
- Reportar status final e próximos passos
|
||||
|
||||
|
||||
## Exemplos de Uso
|
||||
|
||||
### Exemplo 1: Caso Básico
|
||||
```
|
||||
User: [requisição simples relacionada com react-patterns]
|
||||
Skill: [execução directa com validação]
|
||||
Output: [resultado conciso e accionável]
|
||||
```
|
||||
|
||||
### Exemplo 2: Caso Complexo
|
||||
```
|
||||
User: [requisição multi-passo ou complexa]
|
||||
Skill:
|
||||
1. Análise dos requisitos
|
||||
2. Planeamento da abordagem
|
||||
3. Execução faseada
|
||||
4. Validação contínua
|
||||
Output: [resultado detalhado com próximos passos]
|
||||
```
|
||||
|
||||
### Exemplo 3: Caso com Dependências
|
||||
```
|
||||
User: [requisição que depende de outros sistemas]
|
||||
Skill:
|
||||
1. Verificar dependências disponíveis
|
||||
2. Coordenar com skills/MCPs necessários
|
||||
3. Executar workflow integrado
|
||||
Output: [resultado completo com referências]
|
||||
```
|
||||
- `references/composition-patterns.md` - Compound components, providers, dependency injection (Vercel Engineering)
|
||||
- `references/custom-hooks.md` - useDebounce, useLocalStorage, useFetch com codigo completo
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# React - Composition Patterns (Vercel Engineering)
|
||||
|
||||
Padroes de composicao para componentes React escalaveis. Fonte: Vercel composition-patterns.
|
||||
|
||||
## Regra #1: Evitar Boolean Props (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// NAO: Booleans duplicam estados possiveis
|
||||
<Composer isThread isEditing={false} showAttachments showFormatting={false} />
|
||||
|
||||
// SIM: Variantes explicitas
|
||||
<ThreadComposer channelId="abc" />
|
||||
<EditMessageComposer messageId="xyz" />
|
||||
<ForwardMessageComposer messageId="123" />
|
||||
```
|
||||
|
||||
Cada boolean duplica os estados possiveis. 5 booleans = 32 combinacoes, maioria impossivel.
|
||||
|
||||
---
|
||||
|
||||
## Regra #2: Compound Components com Context (HIGH)
|
||||
|
||||
```tsx
|
||||
// Compound Components: context partilhado, composicao explicita
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null)
|
||||
|
||||
function ComposerFrame({ children }: { children: React.ReactNode }) {
|
||||
return <form>{children}</form>
|
||||
}
|
||||
|
||||
function ComposerInput() {
|
||||
const { state, actions: { update }, meta } = use(ComposerContext)
|
||||
return (
|
||||
<TextInput
|
||||
ref={meta.inputRef}
|
||||
value={state.input}
|
||||
onChangeText={(text) => update(s => ({ ...s, input: text }))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Exportar como compound
|
||||
const Composer = {
|
||||
Frame: ComposerFrame,
|
||||
Input: ComposerInput,
|
||||
Submit: ComposerSubmit,
|
||||
Footer: ComposerFooter,
|
||||
}
|
||||
|
||||
// Uso: composicao clara
|
||||
<Composer.Frame>
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Regra #3: State em Providers (HIGH)
|
||||
|
||||
```tsx
|
||||
// NAO: Estado preso dentro do componente
|
||||
function ForwardComposer() {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Frame>...</Composer.Frame>
|
||||
}
|
||||
|
||||
// SIM: State lifted para Provider
|
||||
function ForwardProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const submit = useForwardMessage()
|
||||
const inputRef = useRef(null)
|
||||
return (
|
||||
<Composer.Provider
|
||||
state={state}
|
||||
actions={{ update: setState, submit }}
|
||||
meta={{ inputRef }}
|
||||
>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Qualquer componente dentro do Provider acede ao state
|
||||
function ForwardDialog() {
|
||||
return (
|
||||
<ForwardProvider>
|
||||
<Dialog>
|
||||
<ForwardComposer />
|
||||
<MessagePreview />
|
||||
<ForwardButton />
|
||||
</Dialog>
|
||||
</ForwardProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function ForwardButton() {
|
||||
const { actions } = use(ComposerContext)
|
||||
return <Button onPress={actions.submit}>Forward</Button>
|
||||
}
|
||||
```
|
||||
|
||||
**Principio chave:** O boundary do Provider e o que importa, nao o nesting visual.
|
||||
|
||||
---
|
||||
|
||||
## Regra #4: Interface Generica para Dependency Injection (HIGH)
|
||||
|
||||
```tsx
|
||||
// Definir interface generica: state + actions + meta
|
||||
interface ComposerContextValue {
|
||||
state: { input: string; attachments: Attachment[]; isSubmitting: boolean }
|
||||
actions: { update: (fn: (s: State) => State) => void; submit: () => void }
|
||||
meta: { inputRef: React.RefObject<TextInput> }
|
||||
}
|
||||
|
||||
// Multiplos providers implementam a MESMA interface
|
||||
function ChannelProvider({ channelId, children }) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
return <Composer.Provider state={state} actions={{ update, submit }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
function ForwardProvider({ children }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Provider state={state} actions={{ update: setState, submit: forward }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
// O mesmo Composer.Input funciona com AMBOS os providers
|
||||
```
|
||||
|
||||
**Principio:** UI = pecas reutilizaveis. State = injectado pelo provider. Trocar provider, manter UI.
|
||||
|
||||
---
|
||||
|
||||
## Regra #5: Children > Render Props (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// NAO: Render props (inflexivel)
|
||||
<Composer
|
||||
renderHeader={() => <Header />}
|
||||
renderFooter={() => <><Formatting /><Emojis /></>}
|
||||
/>
|
||||
|
||||
// SIM: Children (composicao natural)
|
||||
<Composer.Frame>
|
||||
<Header />
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Formatting />
|
||||
<Emojis />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
|
||||
// Excepcao: render props quando parent passa dados
|
||||
<List data={items} renderItem={({ item }) => <Item item={item} />} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Regra #6: React 19 APIs (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// React 19: ref como prop normal (sem forwardRef)
|
||||
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
|
||||
return <input ref={ref} {...props} />
|
||||
}
|
||||
|
||||
// React 19: use() em vez de useContext()
|
||||
const value = use(MyContext) // pode ser chamado condicionalmente
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist Composicao
|
||||
|
||||
- [ ] Zero boolean props para variantes (usar componentes explicitos)
|
||||
- [ ] Compound components com context partilhado
|
||||
- [ ] State em Provider (nao dentro do componente UI)
|
||||
- [ ] Interface generica (state/actions/meta)
|
||||
- [ ] Children para composicao, render props so para data passthrough
|
||||
- [ ] React 19: `use()` e ref como prop
|
||||
66
dev-tools/skills/react-patterns/references/custom-hooks.md
Normal file
66
dev-tools/skills/react-patterns/references/custom-hooks.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# React - Custom Hooks Uteis
|
||||
|
||||
## useDebounce
|
||||
|
||||
```tsx
|
||||
function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
// Uso
|
||||
const searchTerm = useDebounce(input, 500);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## useLocalStorage
|
||||
|
||||
```tsx
|
||||
function useLocalStorage<T>(key: string, initialValue: T) {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
};
|
||||
|
||||
return [storedValue, setValue] as const;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## useFetch
|
||||
|
||||
```tsx
|
||||
function useFetch<T>(url: string) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(setData)
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [url]);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user