9. Redux and redux saga

This file was copied from the packages/webapp/src/stories/docs folder. Its accuracy as of August 8, 2024 has not been verified

| Resources | | --------- | | Redux official tutorial | | Redux saga doc | | Redux saga takeLeading | | Redux saga takeLatest | | Redux toolkit createEntityAdapter and state normalization | | Redux memorized selector |

1. Redux Data flow

Express endpoint api.litefarm.org/crop/31

{ crop_common_name: 'Apricot', crop_genus: 'Prunus', crop_group: 'Fruit and nuts', crop_id: 31, crop_photo_url: 'https://litefarm.nyc3.cdn.digitaloceanspaces.com/default_crop/v2/apricot.webp', crop_subgroup: 'Pome fruits and stone fruits', crop_translation_key: 'APRICOT', }

Copy

Redux Saga action getCropByIdSaga and toolkit action getCropByIdSuccess

getCropByIdSuccess normalizes and stores crop in Redux store

entitiesReducer: { cropEntitiesReducer:{ ids: [31], entities: { 31:{ crop_common_name: 'Apricot', crop_genus: 'Prunus', crop_group: 'Fruit and nuts', crop_id: 31, crop_photo_url: 'https://litefarm.nyc3.cdn.digitaloceanspaces.com/default_crop/v2/apricot.webp', crop_subgroup: 'Pome fruits and stone fruits', crop_translation_key: 'APRICOT'} } }

Copy

Consume store data through selector in a Container

Defined cropsSelector in cropSlice.js

export const cropsSelector = createSelector( [cropSelectors.selectAll, loginSelector], (crops, { farm_id }) => { return crops.filter((crop) => crop.farm_id === farm_id || !crop.farm_id); }, );

Copy

Consume data in container CropCatalogue.js

Copy

Copy

Everytime user login to a farm, the app would fetch all farm data in the background, normalize and cache everything in the store.

entitiesReducer acts as frontend database/database cache and selectors act as frontend database queries.

If user goes to cropCatalogue page, catalogue page will first render all crops in cropReducer(cache).

Copy

Then an useEffect hook would fetch all latest crops and store crops into cropReducer

Copy

If the value of useSelector(cropsSelector) changes, in other word, if a crop is modified by another user, useSelector will trigger a rerender, and catalogue page will update the modified crop.

2. Redux toolkit

cropSlice.js

Copy

3. Async calls with redux saga and axios

All saga functions involve post put patch delete should use takeLeading listener.

All saga functions that only involve get should use takeLatest listener. (example use case: get all crops from server to fill cropReducer as cache)

All saga functions that do not involve any async calls should use takeEvery listener. (example use case: increase counter on button click)

4. state normalization

5. selector

Most slices would have at least 4 selectors cropReducerSelector, cropEntitiesSelector, cropsSelector, and cropSelector

cropSlice.js

Copy

cropsSelector select all crops of a farm

Copy

cropSelectors are a list util selectors

cropSelector select a single crop

Copy

cropEntitiesSelector returns an object of key(crop_id) value(crop) pairs

Copy

Steps for a backend - frontend story from a data flow perspective

  • Create knex migration to create/modify tables in database

  • Create objection models

  • Endpoints and empty controllers

  • Backend jest unit testing

  • Implement controllers

  • Create authorization/validation middlewares

  • Run tests and make sure everything passes

  • Create pure frontend components in storybook

  • res.data normalizer, redux slice, redux selectors to hold and access the data

  • Redux saga for get/post/put/patch request

  • Container component to connect saga actions/selector with pure components

  • Connect Route component with container components with react router

  • Manual testing