-
Notifications
You must be signed in to change notification settings - Fork 0
Redux
Audience: Developer
This guide covers the structure and workflows of our Redux store.
We divide the store into the following slices:
userData
siteData
customerData
inventoryData
-
isLoading
: TODO figure out whether the application is loading *** -
lastUpdated
: date and time when user data was last updated. This is only updated on the initial login (throughrefreshUserData
called inloginUser
). -
isOnline
: a boolean for whether or not the user is connected to a network. This is updated through thecheckOnline
function inuserData
. Read more about offline workflows here. TODO ADD LINK -
users
: an entity adapter for all users linked to the current site. -
currentUserId
: the id of the current user (user who is signed in).
-
products
is the list of all products pulled from Airtable (not filtered by site). -
sitesInventory
: map between site ids andSiteInventoryData
, which is an interface ininventoryDataSlice.ts
with 3 entity adapters:-
siteInventory
: all inventory related to the site -
purchaseRequests
: all purchase requests related to inventory from the site -
inventoryUpdates
: all inventory updates related to inventory from the site
-
-
currentInventoryId
: id of the inventory record, used for viewing individual inventory profile pages throughInventoryProfile
screen (/inventory/item) -
currentPurchaseRequestId
: id referencing the 'current' purchase request, which is used for viewing individual purchase requests on thePurchaseRequest
screen (inventory/purchase-requests/purchase-request
)
-
sitesCustomers
: map between site ids andSiteCustomerData
, which is a an interface incustomerDataSlice.ts
with 3 entity adapters:-
customers
: all customers related to the site -
payments
: all payments linked to customers from the site -
meterReadings
: all meter readings linked to customers from the site
-
-
currentCustomerId
: id referencing the 'current' customer, which is used for viewing individual customer profiles through theCustomerProfile
(customers/customer)
-
isLoading
: TODO figure out -
currentSiteId
: id of the site that the user is currently viewing (which can be toggled using the dropdown on theHome
screen (/home) -
sites
: a map between site ids and site records, which is limited to the sites that the user has access to. Note that this is not an entity adapter.
We use selectors and hooks to access data from the Redux store. Redux recommends using the React-Redux hooks API as the default approach over the connect
API, as it is "simpler and works better with TypeSript" (Redux Docs)
Each slice listed in the previous section has several selectors dedicated to retrieving various data from the store.
For data based on entity adapters, we import a set of default selectors through getSelectors
(see Redux docs).
// inventoryDataSlice.ts
export const {
selectEntities: selectAllCurrentSiteInventory,
selectAll: selectAllCurrentSiteInventoryArray,
selectById: selectCurrentSiteInventoryById,
selectIds: selectCurrentSiteInventoryIds,
} = siteInventoryAdapter.getSelectors(
(state: RootState) => state.inventoryData.sitesInventory[state.siteData.currentSiteId].siteInventory,
);
We can use these selectors to build other selectors using createSelector
(learn more in Redux docs), which maximizes the performance benefits of using selectors since selectors are designed so that a selector is not recomputed unless one of its arguments changes. Here are some example applications of custom selectors based on the data in current___Id
values in the store.
// inventoryData.ts
// -- Custom selectors for current inventory and purchase request --
// Select the value for currentInventoryId directly from the store
export const selectCurrentInventoryId = (state: RootState): string => state.inventoryData.currentInventoryId;
// Select the value for currentPurchaseRequestId directly from the store
export const selectCurrentPurchaseRequestId = (state: RootState): string =>
state.inventoryData.currentPurchaseRequestId;
// Select the inventory record corresponding to the current inventory item (based on currentInventoryId)
export const selectCurrentInventory = createSelector(
selectCurrentInventoryId,
store.getState,
(currentInventoryId, state) => selectCurrentSiteInventoryById(state, currentInventoryId),
);
// Select the product corresponding to the current inventory item (based on currentInventoryId)
export const selectCurrentInventoryProduct = createSelector(
selectCurrentInventoryId,
store.getState,
(inventoryId, state) => selectProductByInventoryId(state, inventoryId),
);
Usage Example
// CreatePurchaseRequest.tsx
...
import { useSelector } from 'react-redux';
import { selectCurrentInventoryProduct } from '../../lib/redux/inventoryData';
...
const product = useSelector(selectCurrentInventoryProduct);
...
Sometimes we need selectors that can take in arguments. For example, the following selector is used to retrieve a product based on an inventory id parameter.
// inventoryData.ts
const getInventoryId = (_: RootState, inventoryId: string) => inventoryId;
...
export const selectProductByInventoryId = createSelector(getInventoryId, store.getState, (inventoryId, state) =>
selectProductById(state, selectProductIdByInventoryId(state, inventoryId) || ''),
);
Usage example:
// PurchaseRequest.tsx
import { useSelector } from 'react-redux';
import { RootState } from '../../lib/redux/store';
import { EMPTY_PRODUCT } from '../../lib/redux/inventoryDataSlice';
import { selectProductByInventoryId } from '../../lib/redux/inventoryData';
...
const product = useSelector((state: RootState) => selectProductByInventoryId(state, purchaseRequest.inventoryId)) || EMPTY_PRODUCT;
- Creating a New User and Assigning them a Site
- Adding or Updating or Deleting an Airtable Column or Table
- Testing Translations in a Production Environment