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:

2 Practical

3 files:

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 store
    • useSelector 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

3.2 WRITE TO STORE

  • useDispatch hook gives us a function dispatch(somAction)
    • What do actions look like? { type: 'todos/todoAdded', payload: trimmedText } . They are just objects with a type:... field

4 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
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
  // 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) => {
        state.status = 'loading'
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle'
        state.value += action.payload
      })
  },
})

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 =
  (amount: number): AppThunk =>
  (dispatch, getState) => {
    const currentValue = selectCount(getState())
    if (currentValue % 2 === 1) {
      dispatch(incrementByAmount(amount))
    }
  }

export default counterSlice.reducer

5 nextjs SSR redux

  1. SSR getServerSideProps() fetches stuff from backend and fills the store
  2. SSR sends the store to the clientside as props
  3. clientside takes the props to initialize it’s own store
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>
  )
}