‚ôĽÔłŹ

Redux

Overview

  • Redux uses a "one-way data flow" app structure
    • State describes the condition of the app at a point in time, and UI is rendered based on that state
    • When something happens in the app:
      1. The UI dispatches an action
      2. The store runs the reducers, and the state is updated based on what occurred
      3. The store notifies the UI that the state has changed
    • The UI re-renders based on the new state
  • Concepts
    • Actions¬†are plain objects with a¬†type¬†field, and describe "what happened" in the app
    • Actions also contain a payload that can be accessed in reducers when calculating new state
    • Reducers¬†are functions that calculate a new state value based on previous state + an action (state,action)=>{}
    • A Redux¬†store¬†runs the root reducer whenever an action is¬†dispatched
  • Usage
    • Config
      • A redux store can be configured via the Redux Toolkit¬†configureStore¬†API
        • configureStore¬†accepts a¬†reducer¬†function as a named argument
        • Wrapping the app with¬†<Provider store={store}>¬†enables all components to use the store
        • Global state should go in the Redux store, local state should stay in React components
    • Slices
      • Redux Toolkit's¬†createSlice¬†API generates action creators and action types for each individual reducer function you provide
        • A "slice" contains the reducer logic and actions related to a specific feature / section of the Redux state
    • Reducers
      • Should only calculate a new state value based on the¬†state¬†and¬†action¬†arguments
      • Must make¬†immutable updates¬†by copying the existing state
      • Cannot contain any asynchronous logic or other "side effects"
      • Redux Toolkit's¬†createSlice¬†API uses Immer to allow "mutating" immutable updates
    • Thunks
      • For asynchronous logic (AJAX calls)
      • Thunks receive¬†dispatch¬†and¬†getState¬†as arguments
      • Redux Toolkit enables the¬†redux-thunk¬†middleware by default
    • Component State / useSelector
      • React components read data from the store with the useSelector hook
      • Selector functions receive the whole¬†state¬†object, and should return a value
      • ūüí°
        Selectors will re-run whenever the Redux store is updated, and if the data they return has changed, the component will re-render
    • Dispatching actions / useDispatch
      • createSlice¬†will generate action creator functions for each reducer we add to a slice
      • Call¬†dispatch(someActionCreator())¬†in a component to dispatch an action
      • Reducers will run, check to see if this action is relevant, and return new state if appropriate
      • ūüí°
        Temporary data like form input values should be kept as React component state. Dispatch a Redux action to update the store when the user is done with the form.
    • Using Data
      • Components should extract the smallest amount of data they need to render themselves
      • ‚Ä£
        Components can combine values from props, state, and the Redux store to determine what UI they need to render.
        • They can read multiple pieces of data from the store, and reshape the data as needed for display.
      • Any component can dispatch actions to cause state updates
      • Redux action creators can prepare action objects with the right contents
        1. ‚Ä£
          createSlice and createAction can accept a "prepare callback" that returns the action payload
          postAdded: {
              reducer(state, action) {
                  state.posts.push(action.payload)
              },
              prepare(title, content, userId) {
                  return {
                      payload: {
                          id: nanoid(),
                          date: new Date().toISOString(),
                          title,
                          content,
                          user: userId,
                      },
                  }
              },
          },
        2. Unique IDs and other random values should be put in the action, not calculated in the reducer
        3. ūüí°
          Reducers should contain the actual state update logic
          • Reducers can contain whatever logic is needed to calculate the next state
          • Action objects should contain just enough info to describe what happened
    • Async / More on Thunks
      • Redux requires plugins/middleware to enable async logic
        • The standard async middleware is called redux-thunk, which is included in Redux Toolkit
      • Redux Toolkit has a¬†createAsyncThunk¬†API that dispatches these actions for you
        • createAsyncThunk¬†accepts two arguments:
          • A string that will be used as the prefix for the generated action types
          • A "payload creator" callback that should return a¬†Promise, and generates¬†pending/fulfilled/rejected¬†action types automatically
        • Generated action creators like¬†fetchPosts¬†dispatch those actions based on the¬†Promise¬†you return
        • ‚Ä£
          You can listen for these action types in createSlice using the extraReducers field, and update the state in reducers based on those actions.
          extraReducers: {
                  [fetchPosts.pending]: (state, action) => {
                      state.status = 'loading'
                  },
          				[fetchPosts.fulfilled]: (state, action) => {
                      state.status = 'succeeded'
                      // Add any fetched posts to the array
                      state.posts = state.posts.concat(action.payload)
                  },
          }
        • Action creators can be used to automatically fill in the keys of the¬†extraReducers¬†object so the slice knows what actions to listen for.