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