Add settings page for managing forecasting API key and URL via UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cae411eae7
commit
1c411e402e
19809 changed files with 1962608 additions and 97 deletions
269
frontend/node_modules/@reduxjs/toolkit/skills/evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps/SKILL.md
generated
vendored
Normal file
269
frontend/node_modules/@reduxjs/toolkit/skills/evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps/SKILL.md
generated
vendored
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
---
|
||||
name: evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps
|
||||
description: >
|
||||
Use this when debugging duplicate requests, stale cache behavior, broad
|
||||
subscriptions, selector churn, serializability warnings, or other Redux
|
||||
Toolkit and RTK Query bugs. Covers a practical event -> reducer -> selector ->
|
||||
render debugging loop plus RTK Query cache interpretation.
|
||||
type: lifecycle
|
||||
library: "@reduxjs/toolkit"
|
||||
library_version: "2.11.2"
|
||||
requires:
|
||||
- build-modern-redux-apps/redux-dataflow
|
||||
sources:
|
||||
- "reduxjs/redux:docs/style-guide/style-guide.md"
|
||||
- "reduxjs/redux:docs/tutorials/essentials/part-5-async-logic.md"
|
||||
- "reduxjs/redux:docs/tutorials/essentials/part-8-rtk-query-advanced.md"
|
||||
- "reduxjs/redux-toolkit:docs/usage/usage-guide.md"
|
||||
---
|
||||
|
||||
# Debug Redux Toolkit Apps
|
||||
|
||||
## Setup
|
||||
|
||||
```ts
|
||||
import { configureStore, createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
type Post = { id: string; title: string }
|
||||
|
||||
export const fetchPosts = createAsyncThunk(
|
||||
'posts/fetchPosts',
|
||||
async () => {
|
||||
const response = await fetch('/api/posts')
|
||||
return (await response.json()) as Post[]
|
||||
},
|
||||
{
|
||||
condition(_arg, { getState }) {
|
||||
const state = getState() as RootState
|
||||
return state.posts.status === 'idle'
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const postsSlice = createSlice({
|
||||
name: 'posts',
|
||||
initialState: {
|
||||
items: [] as Post[],
|
||||
status: 'idle' as 'idle' | 'pending' | 'succeeded' | 'failed',
|
||||
},
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchPosts.pending, (state) => {
|
||||
state.status = 'pending'
|
||||
})
|
||||
.addCase(fetchPosts.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded'
|
||||
state.items = action.payload
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
posts: postsSlice.reducer,
|
||||
},
|
||||
})
|
||||
|
||||
type RootState = ReturnType<typeof store.getState>
|
||||
```
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Debug in order: action -> reducer -> selector -> render
|
||||
|
||||
```ts
|
||||
const selectPosts = (state: RootState) => state.posts.items
|
||||
const selectPostsStatus = (state: RootState) => state.posts.status
|
||||
|
||||
store.dispatch(fetchPosts())
|
||||
|
||||
console.log(selectPostsStatus(store.getState()))
|
||||
console.log(selectPosts(store.getState()))
|
||||
```
|
||||
|
||||
If a component looks wrong, first verify the action fired, then the reducer state, then the selector result, then the render boundary.
|
||||
|
||||
### Narrow subscriptions at the usage site
|
||||
|
||||
```tsx
|
||||
import { useAppSelector } from '../../app/hooks'
|
||||
|
||||
export function PostsList() {
|
||||
const posts = useAppSelector((state) => state.posts.items)
|
||||
const status = useAppSelector((state) => state.posts.status)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{status}</div>
|
||||
<div>{posts.length}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
React-Redux behaves best when components select only the values they render and do it as close to usage as possible.
|
||||
|
||||
### Interpret RTK Query invalidation correctly
|
||||
|
||||
```ts
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
type Post = { id: string; title: string }
|
||||
|
||||
const api = createApi({
|
||||
reducerPath: 'api',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
|
||||
tagTypes: ['Post'],
|
||||
endpoints: (build) => ({
|
||||
getPosts: build.query<Post[], void>({
|
||||
query: () => 'posts',
|
||||
providesTags: ['Post'],
|
||||
}),
|
||||
updatePost: build.mutation<Post, Pick<Post, 'id' | 'title'>>({
|
||||
query: ({ id, title }) => ({
|
||||
url: `posts/${id}`,
|
||||
method: 'PATCH',
|
||||
body: { title },
|
||||
}),
|
||||
invalidatesTags: ['Post'],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
If invalidation did not visibly refetch, check whether anything was still subscribed to that cache entry.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### HIGH Dispatching fetch thunks from effects without a thunk-level guard
|
||||
|
||||
Wrong:
|
||||
|
||||
```tsx
|
||||
import { useEffect } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from '../../app/hooks'
|
||||
|
||||
function PostsPage() {
|
||||
const dispatch = useAppDispatch()
|
||||
const postStatus = useAppSelector((state) => state.posts.status)
|
||||
|
||||
useEffect(() => {
|
||||
if (postStatus === 'idle') {
|
||||
dispatch(fetchPosts())
|
||||
}
|
||||
}, [dispatch, postStatus])
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```ts
|
||||
export const fetchPosts = createAsyncThunk(
|
||||
'posts/fetchPosts',
|
||||
async () => {
|
||||
const response = await fetch('/api/posts')
|
||||
return (await response.json()) as Post[]
|
||||
},
|
||||
{
|
||||
condition(_arg, { getState }) {
|
||||
const state = getState() as RootState
|
||||
return state.posts.status === 'idle'
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
React StrictMode can run effects twice in development, so the guard belongs in the thunk as well as the component.
|
||||
|
||||
Source: reduxjs/redux:docs/tutorials/essentials/part-5-async-logic.md
|
||||
|
||||
### HIGH Ignoring serializable-state warnings
|
||||
|
||||
Wrong:
|
||||
|
||||
```ts
|
||||
const initialState = {
|
||||
lastSeen: new Date(),
|
||||
pendingIds: new Set<string>(),
|
||||
}
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```ts
|
||||
const initialState = {
|
||||
lastSeenIso: new Date().toISOString(),
|
||||
pendingIds: [] as string[],
|
||||
}
|
||||
```
|
||||
|
||||
Non-serializable values break DevTools, replay, persistence, and equality assumptions in subtle ways.
|
||||
|
||||
Source: reduxjs/redux:docs/style-guide/style-guide.md
|
||||
|
||||
### HIGH Selecting broad state in parents and threading props
|
||||
|
||||
Wrong:
|
||||
|
||||
```tsx
|
||||
import { useAppSelector } from '../../app/hooks'
|
||||
|
||||
function PostsPage() {
|
||||
const postsState = useAppSelector((state) => state.posts)
|
||||
return <PostsList items={postsState.items} status={postsState.status} />
|
||||
}
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```tsx
|
||||
import { useAppSelector } from '../../app/hooks'
|
||||
|
||||
function PostsList() {
|
||||
const items = useAppSelector((state) => state.posts.items)
|
||||
const status = useAppSelector((state) => state.posts.status)
|
||||
return (
|
||||
<div>
|
||||
<div>{status}</div>
|
||||
<div>{items.length}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Selecting whole slices high in the tree widens the subscription surface and pushes rerenders through props.
|
||||
|
||||
Source: reduxjs/redux:docs/style-guide/style-guide.md
|
||||
|
||||
### MEDIUM Returning unstable objects from query selection logic
|
||||
|
||||
Wrong:
|
||||
|
||||
```tsx
|
||||
import { api } from '../../services/api'
|
||||
|
||||
const result = api.useGetPostsQuery(undefined, {
|
||||
selectFromResult: ({ data = [] }) => ({
|
||||
posts: [...data],
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```tsx
|
||||
import { api } from '../../services/api'
|
||||
|
||||
const result = api.useGetPostsQuery(undefined, {
|
||||
selectFromResult: ({ data = [] }) => ({
|
||||
posts: data,
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
New object and array references defeat memoization and make components rerender even when the underlying cached data did not change.
|
||||
|
||||
Source: reduxjs/redux:docs/tutorials/essentials/part-8-rtk-query-advanced.md
|
||||
226
frontend/node_modules/@reduxjs/toolkit/skills/evolve-and-diagnose-redux-apps/migrate-to-modern-redux/SKILL.md
generated
vendored
Normal file
226
frontend/node_modules/@reduxjs/toolkit/skills/evolve-and-diagnose-redux-apps/migrate-to-modern-redux/SKILL.md
generated
vendored
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
---
|
||||
name: evolve-and-diagnose-redux-apps/migrate-to-modern-redux
|
||||
description: >
|
||||
Use this when moving a legacy Redux codebase to current RTK patterns. Covers
|
||||
replacing createStore with configureStore, migrating touched reducers to
|
||||
createSlice, codemod-assisted RTK 2 updates, and replacing server-data stacks
|
||||
with RTK Query instead of writing new legacy Redux code.
|
||||
type: lifecycle
|
||||
library: "@reduxjs/toolkit"
|
||||
library_version: "2.11.2"
|
||||
requires:
|
||||
- build-modern-redux-apps/modern-redux
|
||||
sources:
|
||||
- "reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx"
|
||||
- "reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md"
|
||||
- "reduxjs/redux-toolkit:packages/rtk-codemods/README.md"
|
||||
- "reduxjs/redux:docs/style-guide/style-guide.md"
|
||||
---
|
||||
|
||||
# Migrate To Modern Redux
|
||||
|
||||
## Setup
|
||||
|
||||
```ts
|
||||
// before
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
|
||||
const postsReducer = (state = [] as { id: string; title: string }[]) => state
|
||||
const usersReducer = (state = [] as { id: string; name: string }[]) => state
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
posts: postsReducer,
|
||||
users: usersReducer,
|
||||
})
|
||||
|
||||
export const legacyStore = createStore(rootReducer, applyMiddleware(thunk))
|
||||
|
||||
// after
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
const postsReducer = (state = [] as { id: string; title: string }[]) => state
|
||||
const usersReducer = (state = [] as { id: string; name: string }[]) => state
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
posts: postsReducer,
|
||||
users: usersReducer,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Replace the store setup first
|
||||
|
||||
```ts
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
const postsReducer = (state = [] as { id: string; title: string }[]) => state
|
||||
const usersReducer = (state = [] as { id: string; name: string }[]) => state
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
posts: postsReducer,
|
||||
users: usersReducer,
|
||||
},
|
||||
})
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
```
|
||||
|
||||
This is the one migration step that can happen immediately while old reducers continue to work.
|
||||
|
||||
### Migrate reducers as you touch them
|
||||
|
||||
```ts
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
type TodosState = {
|
||||
items: { id: string; text: string; completed: boolean }[]
|
||||
}
|
||||
|
||||
const initialState: TodosState = {
|
||||
items: [],
|
||||
}
|
||||
|
||||
export const todosSlice = createSlice({
|
||||
name: 'todos',
|
||||
initialState,
|
||||
reducers: {
|
||||
todoAdded(state, action: { payload: { id: string; text: string } }) {
|
||||
state.items.push({ ...action.payload, completed: false })
|
||||
},
|
||||
todoToggled(state, action: { payload: { id: string } }) {
|
||||
const todo = state.items.find((item) => item.id === action.payload.id)
|
||||
if (todo) {
|
||||
todo.completed = !todo.completed
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Once a reducer needs editing, migrate that reducer instead of adding more legacy code to it.
|
||||
|
||||
### Use codemods for mechanical updates
|
||||
|
||||
```bash
|
||||
npx @reduxjs/rtk-codemods createSliceBuilder src/features/posts/postsSlice.ts
|
||||
npx @reduxjs/rtk-codemods createReducerBuilder src/features/posts/postsReducer.ts
|
||||
```
|
||||
|
||||
Use codemods for repetitive RTK API migrations, then review the result and finish the semantic cleanup by hand.
|
||||
|
||||
### Replace legacy server-data stacks with RTK Query
|
||||
|
||||
```ts
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
type Todo = { id: string; text: string }
|
||||
|
||||
export const api = createApi({
|
||||
reducerPath: 'api',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
|
||||
endpoints: (build) => ({
|
||||
getTodos: build.query<Todo[], void>({
|
||||
query: () => 'todos',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
When the old code is just request status plus fetched data, migrate toward RTK Query instead of carrying the thunk stack forward forever.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### HIGH Attempting a big-bang rewrite
|
||||
|
||||
Wrong:
|
||||
|
||||
```ts
|
||||
// Replace every reducer, every connected component, and every async flow
|
||||
// in one branch before shipping anything.
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```ts
|
||||
// 1) Switch createStore to configureStore
|
||||
// 2) Migrate one touched reducer to createSlice
|
||||
// 3) Convert touched connected components to hooks
|
||||
// 4) Repeat without introducing new legacy Redux code
|
||||
```
|
||||
|
||||
Modern Redux migration is incremental, but once the store is modernized new work should stop adding legacy patterns.
|
||||
|
||||
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
|
||||
|
||||
### CRITICAL Carrying removed RTK 2 config forms forward
|
||||
|
||||
Wrong:
|
||||
|
||||
```ts
|
||||
configureStore({
|
||||
reducer,
|
||||
middleware: [logger],
|
||||
})
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```ts
|
||||
configureStore({
|
||||
reducer,
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(logger),
|
||||
})
|
||||
```
|
||||
|
||||
RTK 2 removed array middleware configuration and other older builder forms that agents trained on RTK 1.x still emit.
|
||||
|
||||
Source: reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md
|
||||
|
||||
### HIGH Preserving hand-written fetch state by default
|
||||
|
||||
Wrong:
|
||||
|
||||
```ts
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
|
||||
type Todo = { id: string; text: string }
|
||||
|
||||
const initialState = {
|
||||
items: [] as Todo[],
|
||||
status: 'idle' as 'idle' | 'pending' | 'failed',
|
||||
}
|
||||
|
||||
export const fetchTodos = createAsyncThunk('todos/fetch', async () => {
|
||||
const response = await fetch('/api/todos')
|
||||
return (await response.json()) as Todo[]
|
||||
})
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```ts
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
type Todo = { id: string; text: string }
|
||||
|
||||
const api = createApi({
|
||||
reducerPath: 'api',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
|
||||
endpoints: (build) => ({
|
||||
getTodos: build.query<Todo[], void>({
|
||||
query: () => 'todos',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
If the feature is really server cache, keep the migration moving toward RTK Query instead of rebuilding the old loading-flag architecture in new APIs.
|
||||
|
||||
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
|
||||
Loading…
Add table
Add a link
Reference in a new issue