Quick Redux
Posted on October 2, 2021
Tags: javascript
\[ Click \overset{dispatch(action)}{\rightarrow} Slice \overset{reduce\ store}{\rightarrow} store'\] \[ \{reducerA, reducerB ...\} \in Slice \]
1 Use-case:
- Redux force rerenders when the store changes
2 Practical
3 files:
store.ts
generic preprocessor file where you define types and the universal store objectSlice.ts
psuedo-switch statement- Actions get caught by the Reducers in the Slice (pseudo-switch statement)
aside: Thunks = async actions
3 store
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../app/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer,
,
}
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
const SomeComponent = () => {
const count = useSelector((state: RootState) => state.counter.value)
const dispatch = useDispatch()
return(
<div>
<input type="button" onClick={()=>dispatch(increment())} value="sdasd" />
<div>{count}</div>
</div>
) }
3.1 READ TO STORE
useSelector
hook allows us to READ the things in the storeuseSelector
also behaves like useEffect: on each dispatch, it will check if it’s output is diff and will rerender if it is.- example: if some random action gets dispatched
state.counter.value
will be observed to see if value changes
- example: if some random action gets dispatched
3.2 WRITE TO STORE
useDispatch
hook gives us a functiondispatch(somAction)
- What do actions look like?
{ type: 'todos/todoAdded', payload: trimmedText }
. They are just objects with atype:...
field
- What do actions look like?
4 createSlice
- How do we build/define actions? using `createSlice({name: …})
- Example code defines 2 actions using a createSlice
name: "Counter"
withincrement
defines action{type: Counter/increment}
name: "Counter"
withdecrement
defines action{type: Counter/decrement}
- Example code defines 2 actions using a createSlice
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import type { AppState, AppThunk } from '../../app/store'
import { fetchCount } from './counterAPI'
export interface CounterState {
value: number
status: 'idle' | 'loading' | 'failed'
}
const initialState: CounterState = {
value: 0,
status: 'idle',
}
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
'counter/fetchCount',
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
}
)
export const counterSlice = createSlice({
name: 'counter',
,
initialState// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
.value += 1
state,
}decrement: (state) => {
.value -= 1
state,
}// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
.value += action.payload
state,
},
}// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder.addCase(incrementAsync.pending, (state) => {
.status = 'loading'
state
}).addCase(incrementAsync.fulfilled, (state, action) => {
.status = 'idle'
state.value += action.payload
state
}),
}
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state: AppState) => state.counter.value
// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
: number): AppThunk =>
(amount, getState) => {
(dispatchconst currentValue = selectCount(getState())
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount))
}
}
export default counterSlice.reducer
- A “slice” is a collection of Redux reducer logic and actions for a single feature in your app, typically defined together in a single file. The name comes from splitting up the root Redux state object into multiple “slices” of state.
5 nextjs SSR redux
- SSR getServerSideProps() fetches stuff from backend and fills the store
- SSR sends the store to the clientside as props
- clientside takes the props to initialize it’s own store
- very loose psuedocode
export function getServerSideProps() {
const reduxStore = initializeStore()//create empty store
const data = fetch data from backend
dispatch({data})
return { props: { initialReduxState: reduxStore.getState() } }
}
import { Provider } from 'react-redux'
import { useStore } from '../store'
export default function App({ Component, pageProps }) {
// here we restore the state created by `getServerSideProps`
// it's expected that all routes always return this prop
const store = useStore(pageProps.initialReduxState)
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
) }