Skip to content
Danny Liu edited this page Sep 10, 2019 · 9 revisions

Redux Store Structure

TAPP uses redux as a state manager for the frontend. The redux store that keeps track of the application state has two main objects: ui and model. Below is a screenshot of what the redux store looks like:

model: This object contains data that is fetched from the api backend. Each child within this object holds data from a single model class and contains the field _modelData, which is a list of the fetched data. This allows all of the api data to reside in one location, so that if there are multiple components that all deal with the same mode class, they don’t need to individually manage their own api data. For example, below is an image where the the redux store holds 2 sessions fetched from the backend:

ui: This object contains data that manages the ui state of various components. Each child within this object holds the ui data for a single connected component/view. Generally, data that is not from the api backend but is necessary for rendering the ui is stored in this store. Things that are stored here can be search box queries and rows that are selected in a table.

Reducers and Actions

Redux stores keep a record of the state of our application. Whenever a change occurs, we need to update the store to represent the new state. Redux stores can only be updated through actions and reducers. Actions are objects that contain a string field type, which describes what the action is, and an object field payload, which contains information about how the state has changed. Reducers are functions that takes an action and a state as arguments, and returns a new state. Whenever a user performs an action that changes the model or the ui, such as creating a new position or expanding a ui element, we need to dispatch an action so that the reducer can generate a new state.

The actions and reducers for the model store is handled slightly differently from the actions and reducers for the ui store. All of the actions and reducers that manage api data is stored in src/api/. Within each of the files in the actions directory, we define a set of action generator functions with actionFactory and a set of dispatchers. Action generators are functions that return an action in a standardized format. Dispatchers are functions that perform some logic, and then dispatches an action based on the result. Let’s take a look at the action file applicant.js:

// actions
const fetchApplicantsSuccess = actionFactory(FETCH_APPLICANTS_SUCCESS);
const fetchOneApplicantSuccess = actionFactory(FETCH_ONE_APPLICANT_SUCCESS);
const upsertOneApplicantSuccess = actionFactory(UPSERT_ONE_APPLICANT_SUCCESS);
const deleteOneApplicantSuccess = actionFactory(DELETE_ONE_APPLICANT_SUCCESS);

// dispatchers
export const fetchApplicants = validatedApiDispatcher({
    name: "fetchApplicants",
    description: "Fetch applicants",
    onErrorDispatch: e => fetchError(e.toString()),
    dispatcher: () => async (dispatch, getState) => {
        const { id: activeSessionId } = getState().model.sessions.activeSession;
        const data = await apiGET(`/sessions/${activeSessionId}/applicants`);
        dispatch(fetchApplicantsSuccess(data));
    }
});

Here, we see a list of action generator functions defined on lines 2-5. fetchApplicantsSuccess for example is a function that that takes api response data and returns an action of type “FETCH_APPLICANTS_SUCCESS”. The dispatcher fetchApplicants is the function that actually performs the api call that fetches the applicant data from the backend, and then passes this data into the fetchApplicantsSuccess action generator. The returned action is then dispatched to the reducer that manages the applicants store in the model store.
In the reducers directory, each file defines a reducer for each of the model stores. The reducer receives the actions dispatched by the dispatchers in the actions files, and returns a state with an updated _modelData field that reflects the action. The reducers are then exported and imported by the rootReducer. The rootReducer aggregates all of the reducers from both the model store and the ui store, and sets up the final complete redux store.

Clone this wiki locally