These are short notes I took while watching Dan Abramov's course on Building React Applications with Idiomatic Redux
For a more thorough summary of the course I recommend going through this repo by Taylor Bell
- action creators as arrow functions can be used to reduce the amount of code
- use concise method notation inside
mapDispatchToPropssince it's shorter
- initial state may be passed inside
createStoreand will override the default value in the reducer - specifying the full state tree in
createStoreis discouraged as it breaks encapsulation - however, if it's only hydrating persisted data created by redux itself, then it's perfectly fine as encapsulation is still kept
- localStorage may be used to persist data
- setting and getting should be wrapped in try/catch since these operations might be blocked by the browser's privacy settings
- uuid may be used instead of a counter to avoid ids collision when re-running the app
node_uuidmay be used for thatthrottlefromlodashcan be used to ensure that we don't call the expensiveJSON.stringifytoo often
- extract
configureStoreand theRootcomponent from the app's entry point - this makes testing easier
React Router- standard stuff
- use
LinkfromReact Routerto control the part of the state that is kept in the URL
React Routerwill inject URL parameters in theprops.paramsobject- when using a router, backend should be configured to always serve the JS entry point so the frontend router can kick in
- since the router becomes the source of truth for this part of the state, we can remove the reducer/actions related with it
withRouterwill inject the router as well as other related props as params (it hides the manual context injection from you)- useful for using the router in deeply nested component
- requires v3.0
- use a hash inside
mapDispatchToPropsto map prop names to action creator functions
- place selectors in the same file as the corresponding reducer
- reducer will be the default export and selectors will be named exports
- the root reducer will also expose the selectors as named exports
- this way knowledge of shape of the state is limited to a single file
- we may use a hash map instead of an array to store objects
- combineReducers may be used multiple times
- as reducers grow it may be useful to refactor some parts into separate files
- this works nice when combining object spread operator and computer property operator
return {
...state
[action.id]: todo(state[action.id], action)
}- when creating the store, we can override the
dispatchfunction to add custom logic, such as logging - this is practically a manual way of writing a middleware
- demonstrating how to mock a backend API
- we cannot override lifecycle hooks in generated components (like the ones returned by calling
connect) - in order to override
componentDidMountwe use a higher-level component and pass it to connect - we also need to override
componentDidUpdatesince when props change the component isn't going to callcomponentDidMounta second time
- extracting the data fetching to a function inside the component
- adding
receiveTodosas an action creator that is called when the API call is successful - use
import * as actionsto namespace all the actions in a single object
- data fetching can be written as an action creator (
fetchTodos) - the asynchronous
fetchTodosmethod will return a promise and use the then method to pass the result through thereceiveTodosaction creator - however, by default, redux only supports the dispatching of plain object and cannot handle a promise object
- we can use the same technique from
LESSON 12to enhance the store with a dispatch function that can handle promises - taking the sync/async handling away from the components reduces their responsibility an knowledge which is a good thing!
- we recognized a repeated pattern of overwriting the
store.dispatchmethod in order to add custom functionality - this can be refactored to be a more strict interface of getting the store and the "next" dispatch method and returning an enhanced dispatch method
- this interface is known as a
middleware
store => next => enhancedDispatch- the middleware chain will be applied in reverse so the order of middlewares defined in the array will reflect the order of calling the dispatch methods returned by them
- redux ships with a utility function that applied middlewares when creating the store called
applyMiddleware - it may be passed as a 2nd or 3rd (optional) argument to the
createStoremethod. this argument is calledenhancer - many middlewares (including logger and promise support) are available as
npmpackages
- refactoring the reducers to deal with data that is fetched incrementally from the backend API
- the store now has a lookup table of
todosByIdand arrays ofidsByFilterfor every filter
- refactoring reducers to be more DRY
- reducers export selectors so the knowledge of the shape of the state tree is encapsulated
- adding a boolean flag to the lists to indicate an in-flight fetch operation
- as we add a new field and change the shape of the state tree, we now see the advantage of encapsulating access to the state tree inside the reducer that handles it
- introducing the
thunkmiddleware - this middleware lets us dispatch functions instead of objects to the store
- the function will get
dispatchas an argument and it is able to call it multiple times across the time span of an async operation - this technique is more powerful than the previously introduced promise middleware since it enables us to describe complex async flows while a promise only expresses the async value of a single operation
- a
thunkcan be used to avoid unnecessary network requests (if a requests is already being made, and hasn't returned yet) - in order to do so, the thunk needs access to the store. it can get it using the
getStatesecond argument provided to the thunk - as a convention, it's convenient to always return a promise even if the API hasn't been called
- when writing thunks, we can leverage the 2nd argument of the
thenmethod (the rejection handler) to dispatch an error action - for better consistency and clarity we can postfix async actions with
REQUEST/SUCCESS/FAILURE - we handle the error action in a combined reducer which is for setting and clearing the error message
- a reusable error message component with
errorMessage, onRetryprops is rendered whenever we get a truthy error property - using
catchinstead of using the rejection callback has a downside where it will also catch any errors thrown by reducers or components that are subscribed to the store
- demonstrates how to work against a mocked
addTodobackend endpoint
- the
normalizrutility can be used to normalize server responses - this helps reduce code and avoid writing special case handlers in reducers
- completes the app by connecting toggleTodo to a backend endpoint that updates the data on the server
- because we introduced
normalizrbefore, thebyIdreducer doesn't need to change - we still need to update the all/active/completed lists when the operation is successful