mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-13 11:45:56 +00:00
chore: publish from staged
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Context File Template
|
||||
|
||||
Standard template for a new context module. Copy and fill in the name.
|
||||
|
||||
## Template
|
||||
|
||||
```jsx
|
||||
// src/contexts/[Name]Context.js
|
||||
import React from 'react';
|
||||
|
||||
// ─── 1. Default Value ───────────────────────────────────────────────────────
|
||||
// Shape must match what the provider will pass as `value`
|
||||
// Used when a consumer renders outside any provider (edge case protection)
|
||||
const defaultValue = {
|
||||
// fill in the shape
|
||||
};
|
||||
|
||||
// ─── 2. Create Context ──────────────────────────────────────────────────────
|
||||
export const [Name]Context = React.createContext(defaultValue);
|
||||
|
||||
// ─── 3. Display Name (for React DevTools) ───────────────────────────────────
|
||||
[Name]Context.displayName = '[Name]Context';
|
||||
|
||||
// ─── 4. Optional: Custom Hook (strongly recommended) ────────────────────────
|
||||
// Provides a clean import path and a helpful error if used outside provider
|
||||
export function use[Name]() {
|
||||
const context = React.useContext([Name]Context);
|
||||
if (context === defaultValue) {
|
||||
// Only throw if defaultValue is a sentinel - skip if a real default makes sense
|
||||
// throw new Error('use[Name] must be used inside a [Name]Provider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
```
|
||||
|
||||
## Filled Example - AuthContext
|
||||
|
||||
```jsx
|
||||
// src/contexts/AuthContext.js
|
||||
import React from 'react';
|
||||
|
||||
const defaultValue = {
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
login: () => Promise.resolve(),
|
||||
logout: () => {},
|
||||
};
|
||||
|
||||
export const AuthContext = React.createContext(defaultValue);
|
||||
AuthContext.displayName = 'AuthContext';
|
||||
|
||||
export function useAuth() {
|
||||
return React.useContext(AuthContext);
|
||||
}
|
||||
```
|
||||
|
||||
## Filled Example - ThemeContext
|
||||
|
||||
```jsx
|
||||
// src/contexts/ThemeContext.js
|
||||
import React from 'react';
|
||||
|
||||
const defaultValue = {
|
||||
theme: 'light',
|
||||
toggleTheme: () => {},
|
||||
};
|
||||
|
||||
export const ThemeContext = React.createContext(defaultValue);
|
||||
ThemeContext.displayName = 'ThemeContext';
|
||||
|
||||
export function useTheme() {
|
||||
return React.useContext(ThemeContext);
|
||||
}
|
||||
```
|
||||
|
||||
## Where to Put Context Files
|
||||
|
||||
```
|
||||
src/
|
||||
contexts/ ← preferred: dedicated folder
|
||||
AuthContext.js
|
||||
ThemeContext.js
|
||||
```
|
||||
|
||||
Alternative acceptable locations:
|
||||
|
||||
```
|
||||
src/context/ ← singular is also fine
|
||||
src/store/contexts/ ← if co-located with state management
|
||||
```
|
||||
|
||||
Do NOT put context files inside a component folder - contexts are cross-cutting and shouldn't be owned by any one component.
|
||||
|
||||
## Provider Placement in the App
|
||||
|
||||
Context providers wrap the components that need access. Place as low in the tree as possible, not always at root:
|
||||
|
||||
```jsx
|
||||
// App.js
|
||||
import { ThemeProvider } from './ThemeProvider';
|
||||
import { AuthProvider } from './AuthProvider';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
// Auth wraps everything - login state is needed everywhere
|
||||
<AuthProvider>
|
||||
{/* Theme wraps only the UI shell - not needed in pure data providers */}
|
||||
<ThemeProvider>
|
||||
<Router>
|
||||
<AppShell />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
# Multiple Legacy Contexts - Migration Reference
|
||||
|
||||
## Identifying Multiple Contexts
|
||||
|
||||
A React 16/17 codebase often has several legacy contexts used for different concerns:
|
||||
|
||||
```bash
|
||||
# Find distinct context names used in childContextTypes
|
||||
grep -rn "childContextTypes" src/ --include="*.js" --include="*.jsx" | grep -v "\.test\."
|
||||
# Each hit is a separate context to migrate
|
||||
```
|
||||
|
||||
Common patterns in class-heavy codebases:
|
||||
|
||||
- **Theme context** - dark/light mode, color palette
|
||||
- **Auth context** - current user, login/logout functions
|
||||
- **Router context** - current route, navigation (if using older react-router)
|
||||
- **Store context** - Redux store, dispatch (if using older connect patterns)
|
||||
- **Locale/i18n context** - language, translation function
|
||||
- **Toast/notification context** - show/hide notifications
|
||||
|
||||
---
|
||||
|
||||
## Migration Order
|
||||
|
||||
Migrate contexts one at a time. Each is an independent migration:
|
||||
|
||||
```
|
||||
For each legacy context:
|
||||
1. Create src/contexts/[Name]Context.js
|
||||
2. Update the provider
|
||||
3. Update all consumers
|
||||
4. Run the app - verify no warning for this context
|
||||
5. Move to the next context
|
||||
```
|
||||
|
||||
Do not migrate all providers first then all consumers - it leaves the app in a broken intermediate state.
|
||||
|
||||
---
|
||||
|
||||
## Multiple Contexts in the Same Provider
|
||||
|
||||
Some apps combined multiple contexts in one provider component:
|
||||
|
||||
```jsx
|
||||
// Before - one provider exports multiple context values:
|
||||
class AppProvider extends React.Component {
|
||||
static childContextTypes = {
|
||||
theme: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
locale: PropTypes.string,
|
||||
notifications: PropTypes.array,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
theme: this.state.theme,
|
||||
user: this.state.user,
|
||||
locale: this.state.locale,
|
||||
notifications: this.state.notifications,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Migration approach - split into separate contexts:**
|
||||
|
||||
```jsx
|
||||
// src/contexts/ThemeContext.js
|
||||
export const ThemeContext = React.createContext('light');
|
||||
|
||||
// src/contexts/AuthContext.js
|
||||
export const AuthContext = React.createContext({ user: null, login: () => {}, logout: () => {} });
|
||||
|
||||
// src/contexts/LocaleContext.js
|
||||
export const LocaleContext = React.createContext('en');
|
||||
|
||||
// src/contexts/NotificationContext.js
|
||||
export const NotificationContext = React.createContext([]);
|
||||
```
|
||||
|
||||
```jsx
|
||||
// AppProvider.js - now wraps with multiple providers
|
||||
import { ThemeContext } from './contexts/ThemeContext';
|
||||
import { AuthContext } from './contexts/AuthContext';
|
||||
import { LocaleContext } from './contexts/LocaleContext';
|
||||
import { NotificationContext } from './contexts/NotificationContext';
|
||||
|
||||
class AppProvider extends React.Component {
|
||||
render() {
|
||||
const { theme, user, locale, notifications } = this.state;
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<AuthContext.Provider value={{ user, login: this.login, logout: this.logout }}>
|
||||
<LocaleContext.Provider value={locale}>
|
||||
<NotificationContext.Provider value={notifications}>
|
||||
{this.props.children}
|
||||
</NotificationContext.Provider>
|
||||
</LocaleContext.Provider>
|
||||
</AuthContext.Provider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consumer With Multiple Contexts (Class Component)
|
||||
|
||||
Class components can only use ONE `static contextType`. For multiple, use `Consumer` render props or convert to a function component.
|
||||
|
||||
### Option A - Render Props (keep as class component)
|
||||
|
||||
```jsx
|
||||
import { ThemeContext } from '../contexts/ThemeContext';
|
||||
import { AuthContext } from '../contexts/AuthContext';
|
||||
|
||||
class UserPanel extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ThemeContext.Consumer>
|
||||
{(theme) => (
|
||||
<AuthContext.Consumer>
|
||||
{({ user, logout }) => (
|
||||
<div className={`panel panel-${theme}`}>
|
||||
<span>{user?.name}</span>
|
||||
<button onClick={logout}>Sign out</button>
|
||||
</div>
|
||||
)}
|
||||
</AuthContext.Consumer>
|
||||
)}
|
||||
</ThemeContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option B - Convert to Function Component (preferred)
|
||||
|
||||
```jsx
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from '../contexts/ThemeContext';
|
||||
import { AuthContext } from '../contexts/AuthContext';
|
||||
|
||||
function UserPanel() {
|
||||
const theme = useContext(ThemeContext);
|
||||
const { user, logout } = useContext(AuthContext);
|
||||
|
||||
return (
|
||||
<div className={`panel panel-${theme}`}>
|
||||
<span>{user?.name}</span>
|
||||
<button onClick={logout}>Sign out</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
If converting to a function component is out of scope for this migration sprint - use Option A. If the class component is simple (mostly just render), Option B is worth the minor rewrite.
|
||||
|
||||
---
|
||||
|
||||
## Context File Naming Conventions
|
||||
|
||||
Use consistent naming across the codebase:
|
||||
|
||||
```
|
||||
src/
|
||||
contexts/
|
||||
ThemeContext.js → exports: ThemeContext, ThemeProvider (optional)
|
||||
AuthContext.js → exports: AuthContext, AuthProvider (optional)
|
||||
LocaleContext.js → exports: LocaleContext
|
||||
```
|
||||
|
||||
Each file exports the context object. The provider can stay in its original file and just import the context.
|
||||
|
||||
---
|
||||
|
||||
## Verification After All Contexts Migrated
|
||||
|
||||
```bash
|
||||
# Should return zero hits for legacy context patterns
|
||||
echo "=== childContextTypes ==="
|
||||
grep -rn "childContextTypes" src/ --include="*.js" --include="*.jsx" | grep -v "\.test\." | wc -l
|
||||
|
||||
echo "=== contextTypes (legacy) ==="
|
||||
grep -rn "^\s*static contextTypes\s*=\|contextTypes\.propTypes" src/ --include="*.js" | grep -v "\.test\." | wc -l
|
||||
|
||||
echo "=== getChildContext ==="
|
||||
grep -rn "getChildContext" src/ --include="*.js" --include="*.jsx" | grep -v "\.test\." | wc -l
|
||||
|
||||
echo "All three should be 0"
|
||||
```
|
||||
|
||||
Note: `static contextType` (singular) is the MODERN API - that's correct. Only `contextTypes` (plural) is legacy.
|
||||
@@ -0,0 +1,224 @@
|
||||
# Single Context Migration - Complete Before/After
|
||||
|
||||
## Full Example: ThemeContext
|
||||
|
||||
This covers the most common pattern - one context with one provider and multiple consumers.
|
||||
|
||||
---
|
||||
|
||||
### Step 1 - Before State (Legacy)
|
||||
|
||||
**ThemeProvider.js (provider):**
|
||||
|
||||
```jsx
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ThemeProvider extends React.Component {
|
||||
static childContextTypes = {
|
||||
theme: PropTypes.string,
|
||||
toggleTheme: PropTypes.func,
|
||||
};
|
||||
|
||||
state = { theme: 'light' };
|
||||
|
||||
toggleTheme = () => {
|
||||
this.setState(s => ({ theme: s.theme === 'light' ? 'dark' : 'light' }));
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
theme: this.state.theme,
|
||||
toggleTheme: this.toggleTheme,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ThemedButton.js (class consumer):**
|
||||
|
||||
```jsx
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ThemedButton extends React.Component {
|
||||
static contextTypes = {
|
||||
theme: PropTypes.string,
|
||||
toggleTheme: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { theme, toggleTheme } = this.context;
|
||||
return (
|
||||
<button className={`btn btn-${theme}`} onClick={toggleTheme}>
|
||||
Toggle Theme
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ThemedHeader.js (function consumer - if any):**
|
||||
|
||||
```jsx
|
||||
// Function components couldn't use legacy context cleanly
|
||||
// They had to use a class wrapper or render prop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2 - Create Context File
|
||||
|
||||
**src/contexts/ThemeContext.js (new file):**
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
||||
// Default value matches the shape of getChildContext() return
|
||||
export const ThemeContext = React.createContext({
|
||||
theme: 'light',
|
||||
toggleTheme: () => {},
|
||||
});
|
||||
|
||||
// Named export for the context - both provider and consumers import from here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3 - Update Provider
|
||||
|
||||
**ThemeProvider.js (after):**
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { ThemeContext } from '../contexts/ThemeContext';
|
||||
|
||||
class ThemeProvider extends React.Component {
|
||||
state = { theme: 'light' };
|
||||
|
||||
toggleTheme = () => {
|
||||
this.setState(s => ({ theme: s.theme === 'light' ? 'dark' : 'light' }));
|
||||
};
|
||||
|
||||
render() {
|
||||
// React 19 JSX shorthand: <ThemeContext value={...}>
|
||||
// React 18: <ThemeContext.Provider value={...}>
|
||||
return (
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
theme: this.state.theme,
|
||||
toggleTheme: this.toggleTheme,
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemeProvider;
|
||||
```
|
||||
|
||||
> **React 19 note:** In React 19 you can write `<ThemeContext value={...}>` directly (no `.Provider`). For React 18.3.1 use `<ThemeContext.Provider value={...}>`.
|
||||
|
||||
---
|
||||
|
||||
### Step 4 - Update Class Consumer
|
||||
|
||||
**ThemedButton.js (after):**
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { ThemeContext } from '../contexts/ThemeContext';
|
||||
|
||||
class ThemedButton extends React.Component {
|
||||
// singular contextType (not contextTypes)
|
||||
static contextType = ThemeContext;
|
||||
|
||||
render() {
|
||||
const { theme, toggleTheme } = this.context;
|
||||
return (
|
||||
<button className={`btn btn-${theme}`} onClick={toggleTheme}>
|
||||
Toggle Theme
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemedButton;
|
||||
```
|
||||
|
||||
**Key differences from legacy:**
|
||||
|
||||
- `static contextType` (singular) not `contextTypes` (plural)
|
||||
- No PropTypes declaration needed
|
||||
- `this.context` is the full value object (not a partial - whatever you passed to `value`)
|
||||
- Only ONE context per class component via `contextType` - use `Context.Consumer` render prop for multiple
|
||||
|
||||
---
|
||||
|
||||
### Step 5 - Update Function Consumer
|
||||
|
||||
**ThemedHeader.js (after - now straightforward with hooks):**
|
||||
|
||||
```jsx
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from '../contexts/ThemeContext';
|
||||
|
||||
function ThemedHeader({ title }) {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return <h1 className={`header-${theme}`}>{title}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 6 - Multiple Contexts in One Class Component
|
||||
|
||||
If a class component consumed more than one legacy context, it gets complex. Class components can only have one `static contextType`. For multiple contexts, use the render prop form:
|
||||
|
||||
```jsx
|
||||
import { ThemeContext } from '../contexts/ThemeContext';
|
||||
import { AuthContext } from '../contexts/AuthContext';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ThemeContext.Consumer>
|
||||
{({ theme }) => (
|
||||
<AuthContext.Consumer>
|
||||
{({ user }) => (
|
||||
<div className={`dashboard-${theme}`}>
|
||||
Welcome, {user.name}
|
||||
</div>
|
||||
)}
|
||||
</AuthContext.Consumer>
|
||||
)}
|
||||
</ThemeContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or consider migrating the class component to a function component to use `useContext` cleanly.
|
||||
|
||||
---
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
After migrating one context:
|
||||
|
||||
```bash
|
||||
# Provider - no legacy context exports remain
|
||||
grep -n "childContextTypes\|getChildContext" src/ThemeProvider.js
|
||||
|
||||
# Consumers - no legacy context consumption remains
|
||||
grep -rn "contextTypes\s*=" src/ --include="*.js" --include="*.jsx" | grep -v "ThemeContext\|\.test\."
|
||||
|
||||
# this.context usage - confirm it reads from contextType not legacy
|
||||
grep -rn "this\.context\." src/ --include="*.js" | grep -v "\.test\."
|
||||
```
|
||||
|
||||
Each should return zero hits for the migrated context.
|
||||
Reference in New Issue
Block a user