The apiGetEntity helper action can be used to get a single entity from
the API. Examples of this include:
- Get a user profile
- Get a recipe
- Get a post
The utility will handle the following:
- Executing the API call
- Store the response entity in the correct location in the
entitiesReducer - Add metadata to the entity like which API call it came from and when it was requested. This can later be used for caching and debugging, for example.
- optional Don't perform the request if we already have the data (caching)
- optional Merge the resulting entity with existing entities
- optional Automatically update an
entityViewReducersuch that the new data is immediately displayed on the page.
Example: getting a post
As with the apiRequest actions, we don't use this action directly.
Instead, we create a wrapper action creator for each endpoint. The following example gets a
COMPONENT entity from the API and uses the slug as the id of the component.
import { POST } from 'common/src/app/data/entityTypes';
export const GET_POST_DETAIL = 'communityActions/GET_POST_DETAIL';
/**
* Gets a single post entity from the API
* @param id {string} The ID of the post to get
*/
export const getPostDetail = id => apiGetEntity(
GET_POST_DETAIL,
GATEWAY_COMMUNITY_AUTH,
`/posts/${id}`,
POST,
id,
{
requestData: {
includeLastLike: true,
},
}
);
Please note the following:
- First, create a constant string that represents the action type for the resulting redux action.
This action type is passed to the first argument of
apiGetEntity()can be seen in Redux devtools when the request starts and finishes. - As a convention we usually give the same name to the action type as the action creator. The action type is typed in UPPER_CASE and the action creator in camelCase.
- We don't inline the entity type parameter (
"post"). Instead, we use one of the constants defined in theentityTypesmodule (POST). - It is a good practice to document the action creator parameters using JSDoc annotations.
- Because this is a
GETrequest, theincludeLastLikeparameter will be added to the query parameters of the request.
Example: executing the call on component initialization
We can use the withInitAction util to execute the above call on component initialization. See
Page Initialization for more info.
const addInitAction = withInitAction(
['params'],
({ params: { postId } }, dispatch) => {
...
return dispatch(getPostDetail(postId));
},
);
export default compose(
addInitAction
)(CommunityDetail);
Please note:
- On API actions, the
dispatch()function returns a Promise. Always return that promise towithInitAction - If we add
withInitActionto a component that is not mounted as a route, we need to callprepareComponent(). See Page Initialization
Example: using the response data
The entity in the API response is automatically stored in the entities reducer. This reducer
is a two-dimensional map of entities: first mapped by type, then by id. How we access the
entity depends on the scenario:
- If we display an entity to the user and it is dynamic which entity it is, we use the
entityViewReducer. For example: we display a post but it depends on the URL parameters which post we display. - If we want to use the data in some other way, we can read it directly from the Redux state.
1 Using entityViewReducer
The following example expands on the previous examples by adding a entityViewReducer to
display the post on the page.
// create the entityViewReducer in reducers/viewReducer/pagesReducer/communityDetailReducer.js
import combineReducersNamed from 'common/src/app/util/reducers/combineReducersNamed';
import entityViewReducer from 'common/src/app/reducers/view/entityViewReducer';
export default combineReducersNamed({
post: entityViewReducer,
});
import { makeEntitySelector } from 'common/src/app/reducers/view/entityViewReducer';
...
// Use makeEntitySelector to get the post data to the component
const connector = connect(
() => {
const entitySelector = makeEntitySelector();
return state => ({
post: entitySelector(state, state.view.pages.communityDetail.post),
});
},
);
...
export default compose(
connector, addInitAction
)(CommunityDetail);
2 Read from state
const connector = connect(
(state) => {
// always check if the entity type ("components") exists first
const footerData = state.entities.components && state.entities.components.footer;
if (!footerData) {
return {
isLoading: true,
}
}
// do something with the data
}
)(MyComponent);