Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// multiple entities returned gets normalized
// https://github.com/shakacode/react-webpack-rails-tutorial/blob/08081f07c7d2686facf2e98d956d50dfbef83678/mobile/ReactNativeTutorial/app/api/index.js
export const fetchComments = async () => {
const response = await getRequest('comments.json');
const camelizedResponse = _.mapKeys(_.camelCase, response);
const { entities } = normalize(camelizedResponse, { comments: commentsSchema });
return entities;
};
// single entity is simply returned, with no wrapper
export const postComment = async (payload) => {
const response = await postRequest('comments.json', { comment: payload });
return _.mapKeys(_.camelCase, response);
};
////////////////////////////////
// these are the "thunks" which are functions encompassing the business logic
// https://github.com/shakacode/react-webpack-rails-tutorial/blob/08081f07c7d2686facf2e98d956d50dfbef83678/mobile/ReactNativeTutorial/app/bundles/comments/thunks/index.js
///////////////////////////////
export const fetch = () =>
async function fetchCommentsThunk(dispatch, _getState, call) {
dispatch(reduxActions.setLoadingComments(true));
let response;
try {
response = await call(api.fetchComments);
} catch (e) {
call(Alert.alert, 'Error', 'Could not connect to server', [{ text: 'OK' }]);
return;
} finally {
dispatch(reduxActions.setLoadingComments(false));
}
dispatch(reduxActions.createComments(response.entities.comments));
};
export const createComment = () =>
async function createCommentThunk(dispatch, getState, call) {
const state = getState();
const commentsStore = commentsStoreSelector(state);
const tempId = reduxUtils.getNewId(commentsStore);
const comment = commentFormSelector(state).merge({ id: tempId }).delete('meta');
// Optimistically add the temp comment. The ID is configured to be greater than all existing ones,
// so it sorts correctly.
const tempReduxComments = { [tempId]: comment.toJS() };
dispatch(reduxActions.mergeComments(tempReduxComments));
call(navigationActions.pop);
let response;
try {
response = await call(api.postComment, comment);
} catch (e) {
call(Alert.alert, 'Error', 'Could not post your comment', [{ text: 'OK' }]);
// In the error case, we remove the temp comment. Alternatively, we could retry.
dispatch(reduxActions.removeComment(tempId));
return;
}
// In the success case, we replace the temp comment
dispatch(reduxActions.replaceTempComment(tempId, response));
dispatch(reduxActions.resetCommentForm());
};
///////////////////////////////////////////
// reducers and actions
// https://github.com/shakacode/react-webpack-rails-tutorial/blob/08081f07c7d2686facf2e98d956d50dfbef83678/client/app/bundles/comments/reducers/commentsReducer.js
///////////////////////////////////////////
const merge = (state, action) => state.merge(action.comments);
const remove = (state, action) => state.delete(action.id);
// BIG debate between me and Alexey is if we have this reducer
const replaceTemp = (state, action) => {
// Could be written in single operation to not copy whole structure of store.
const newComment = action.comment;
state.delete(action.tempCommentId).merge({newComment.id: newComment});
}
// no debate on this one
const setLoading = (state, action) => state.setIn(['meta', 'loading'], action.loading);
export default (state = initialState, action) => {
switch (action.type) {
case MERGE:
return merge(state, action);
case REPLACE_TEMP:
return replaceTemp(state, action);
case REMOVE:
return remove(state, action);
case SET_LOADING:
return setLoading(state, action);
default:
return state;
}
};
const mergeComments = (comments) => ({ type: MERGE, comments });
const removeComment = (id) => ({ type: REMOVE, id });
const setLoadingComments = (loading) => ({ type: SET_LOADING, loading });
// BIG QUESTION
// SHOULD WE HAVE AN EXTRA ACTION? Better or worse
const replaceTempComment = (tempCommentId, comment) => ({type: REPLACE_TEMP, tempCommentId, comment);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment