Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-best-practices-build/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const SKILLS: Record<string, SkillConfig> = {
architecture: 1,
state: 2,
patterns: 3,
react19: 4,
},
},
}
Expand Down
85 changes: 66 additions & 19 deletions skills/composition-patterns/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Composition patterns for building flexible, maintainable React components. Avoid
3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM**
- 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants)
- 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props)
4. [React 19 APIs](#4-react-19-apis) — **MEDIUM**
- 4.1 [React 19 API Changes](#41-react-19-api-changes)

---

Expand Down Expand Up @@ -247,11 +249,7 @@ const Composer = {
</Composer.Provider>
```

Consumers explicitly compose exactly what they need. No hidden conditionals. And

the state, actions and meta are dependency-injected by a parent provider,

allowing multiple usages of the same component structure.
Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.

---

Expand Down Expand Up @@ -456,12 +454,13 @@ function ComposerInput() {
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState(initialState)
const inputRef = useRef(null)
const submit = useForwardMessage()

return (
<ComposerContext
value={{
state,
actions: { update: setState, submit: useForwardMessage() },
actions: { update: setState, submit },
meta: { inputRef },
}}
>
Expand Down Expand Up @@ -538,15 +537,15 @@ function ForwardMessageDialog() {
)
}

// This button lives OUTSIDE Composer.Frame but can still submit!
// This button lives OUTSIDE Composer.Frame but can still submit based on its context!
function ForwardButton() {
const {
actions: { submit },
} = use(ComposerContext)
return <Button onPress={submit}>Forward</Button>
}

// This preview lives OUTSIDE Composer.Frame but can read state!
// This preview lives OUTSIDE Composer.Frame but can read composer's state!
function MessagePreview() {
const { state } = use(ComposerContext)
return <Preview message={state.input} attachments={state.attachments} />
Expand Down Expand Up @@ -738,7 +737,7 @@ itself.
<EditMessageComposer messageId="xyz" />

// Or
<ForwardMessageComposer />
<ForwardMessageComposer messageId="123" />
```

Each implementation is unique, explicit and self-contained. Yet they can each
Expand Down Expand Up @@ -780,16 +779,18 @@ function EditMessageComposer({ messageId }: { messageId: string }) {
)
}

function ForwardMessageComposer() {
function ForwardMessageComposer({ messageId }: { messageId: string }) {
return (
<Composer.Frame>
<Composer.Input placeholder="Add a message, if you'd like." />
<Composer.Footer>
<Composer.Formatting />
<Composer.Emojis />
<Composer.Mentions />
</Composer.Footer>
</Composer.Frame>
<ForwardMessageProvider messageId={messageId}>
<Composer.Frame>
<Composer.Input placeholder="Add a message, if you'd like." />
<Composer.Footer>
<Composer.Formatting />
<Composer.Emojis />
<Composer.Mentions />
</Composer.Footer>
</Composer.Frame>
</ForwardMessageProvider>
)
}
```
Expand Down Expand Up @@ -859,7 +860,7 @@ function ComposerFrame({ children }: { children: React.ReactNode }) {
}

function ComposerFooter({ children }: { children: React.ReactNode }) {
return <footer className='flex'>{children}</div>
return <footer className='flex'>{children}</footer>
}

// Usage is flexible
Expand Down Expand Up @@ -892,6 +893,52 @@ Use children when composing static structure.

---

## 4. React 19 APIs

**Impact: MEDIUM**

React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.

### 4.1 React 19 API Changes

**Impact: MEDIUM (cleaner component definitions and context usage)**

> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.

In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.

**Incorrect: forwardRef in React 19**

```tsx
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
return <TextInput ref={ref} {...props} />
})
```

**Correct: ref as a regular prop**

```tsx
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
return <TextInput ref={ref} {...props} />
}
```

**Incorrect: useContext in React 19**

```tsx
const value = useContext(MyContext)
```

**Correct: use instead of useContext**

```tsx
const value = use(MyContext)
```

`use()` can also be called conditionally, unlike `useContext()`.

---

## References

1. [https://react.dev](https://react.dev)
Expand Down
10 changes: 9 additions & 1 deletion skills/composition-patterns/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ description:
React composition patterns that scale. Use when refactoring components with
boolean prop proliferation, building flexible component libraries, or
designing reusable APIs. Triggers on tasks involving compound components,
render props, context providers, or component architecture.
render props, context providers, or component architecture. Includes React 19
API changes.
license: MIT
metadata:
author: vercel
Expand Down Expand Up @@ -35,6 +36,7 @@ Reference these guidelines when:
| 1 | Component Architecture | HIGH | `architecture-` |
| 2 | State Management | MEDIUM | `state-` |
| 3 | Implementation Patterns | MEDIUM | `patterns-` |
| 4 | React 19 APIs | MEDIUM | `react19-` |

## Quick Reference

Expand All @@ -60,6 +62,12 @@ Reference these guidelines when:
- `patterns-children-over-render-props` - Use children for composition instead
of renderX props

### 4. React 19 APIs (MEDIUM)

> **⚠️ React 19+ only.** Skip this section if using React 18 or earlier.

- `react19-no-forwardref` - Don't use `forwardRef`; use `use()` instead of `useContext()`

## How to Use

Read individual rule files for detailed explanations and code examples:
Expand Down
5 changes: 5 additions & 0 deletions skills/composition-patterns/rules/_sections.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ composed components.
**Impact:** MEDIUM
**Description:** Specific techniques for implementing compound components and
context providers.

## 4. React 19 APIs (react19)

**Impact:** MEDIUM
**Description:** React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.
42 changes: 42 additions & 0 deletions skills/composition-patterns/rules/react19-no-forwardref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: React 19 API Changes
impact: MEDIUM
impactDescription: cleaner component definitions and context usage
tags: react19, refs, context, hooks
---

## React 19 API Changes

> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.
In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.

**Incorrect (forwardRef in React 19):**

```tsx
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
return <TextInput ref={ref} {...props} />
})
```

**Correct (ref as a regular prop):**

```tsx
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
return <TextInput ref={ref} {...props} />
}
```

**Incorrect (useContext in React 19):**

```tsx
const value = useContext(MyContext)
```

**Correct (use instead of useContext):**

```tsx
const value = use(MyContext)
```

`use()` can also be called conditionally, unlike `useContext()`.