Skip to content

Commit 9e34843

Browse files
committed
api posting
1 parent d3c6dca commit 9e34843

File tree

7 files changed

+95
-8
lines changed

7 files changed

+95
-8
lines changed

Diff for: mobile/ReactNativeTutorial/app/api/index.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ const API_URL = __DEV__ ? 'http://localhost:3000/' : 'http://www.reactrails.com/
77
function* apiRequest(url, method, payload) {
88
let options = {
99
method,
10-
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json'}
10+
headers: {
11+
'Accept': 'application/json',
12+
'Content-Type': 'application/json',
13+
'X-Auth': 'tutorial_secret',
14+
}
1115
};
1216
if (payload) options = {...options, body: JSON.stringify(payload)};
1317
const response = yield call(fetch, `${API_URL}${url}`, options);
14-
const json = yield call(response.json.bind(response));
15-
return json;
18+
return yield call(response.json.bind(response));
1619
}
1720

1821
function* getRequest(url) {
@@ -29,3 +32,10 @@ export function* fetchComments() {
2932
const commentSchema = new Schema('comments');
3033
return normalize(camelizedResponse, { comments: arrayOf(commentSchema) });
3134
}
35+
36+
export function* postComment(payload) {
37+
const response = yield* postRequest('comments.json', { comment: payload });
38+
const camelizedResponse = _.mapKeys(_.camelCase, response);
39+
const commentSchema = new Schema('comments');
40+
return normalize(camelizedResponse, commentSchema);
41+
}

Diff for: mobile/ReactNativeTutorial/app/bundles/comments/components/Add/Add.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
import React from 'react';
33
import { View, Text } from 'react-native';
4-
import { FormLabel, FormInput } from 'react-native-elements'
4+
import { FormLabel, FormInput, Button } from 'react-native-elements'
55

66
import withAddProps from '../../hocs/withAddProps'
77

@@ -14,9 +14,16 @@ type PropsType = {
1414
const Add = (props: PropsType) => (
1515
<View style={styles.container}>
1616
<FormLabel>Author name</FormLabel>
17-
<FormInput onChangeText={(text) => {props.actions.updateForm({name: text})}}/>
17+
<FormInput onChangeText={(text) => {props.actions.updateForm({author: text})}}/>
1818
<FormLabel>Comment</FormLabel>
19-
<FormInput onChangeText={(text) => {props.actions.updateForm({comment: text})}}/>
19+
<FormInput onChangeText={(text) => {props.actions.updateForm({ text })}}/>
20+
<Button
21+
raised
22+
buttonStyle={styles.button}
23+
icon={{name: 'send'}}
24+
title='Send'
25+
onPress={props.actions.createComment}
26+
/>
2027
</View>
2128
);
2229

Diff for: mobile/ReactNativeTutorial/app/bundles/comments/components/Add/AddStyle.js

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { StyleSheet } from 'react-native';
33
const styles = StyleSheet.create({
44
container: {
55
},
6+
7+
button: {
8+
backgroundColor: "#4641B5",
9+
marginTop: 20,
10+
}
611
});
712

813
export default styles;

Diff for: mobile/ReactNativeTutorial/app/bundles/comments/sagas/index.js

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { Alert } from 'react-native';
22
import { takeLatest } from 'redux-saga';
3-
import { call, put } from 'redux-saga/effects';
3+
import { call, put, select } from 'redux-saga/effects';
4+
import { Actions as navigationActions } from 'react-native-router-flux';
5+
import { reduxUtils } from '../../../utils';
6+
import commentsStoreSelector from '../../../selectors/commentsStoreSelector';
7+
import commentFormSelector from '../../../selectors/commentFormSelector';
48

59
import { actions as reduxActions } from '../../../reducers'
610
import * as api from '../../../api';
711

812
const FETCH = 'SAGA:COMMENTS:FETCH';
13+
const CREATE_COMMENT = 'SAGA:COMMENTS:CREATE';
914
const UPDATE_FORM = 'SAGA:COMMENTS:UPDATE_FORM';
1015

16+
1117
function* fetchHandler() {
1218
try {
1319
const response = yield call(api.fetchComments);
14-
yield put(reduxActions.createComments(response.entities))
20+
yield put(reduxActions.createComments(response.entities.comments))
1521
} catch (e) {
1622
console.log(e);
1723
yield call(Alert.alert, 'Error', 'Could not connect to server', [{text: 'OK'}]);
@@ -22,6 +28,7 @@ function* fetchSaga () {
2228
yield* takeLatest(FETCH, fetchHandler);
2329
}
2430

31+
2532
function* updateFormHandler(action) {
2633
yield put(reduxActions.updateCommentForm(action.payload));
2734
}
@@ -30,15 +37,44 @@ function* updateFormSaga () {
3037
yield* takeLatest(UPDATE_FORM, updateFormHandler);
3138
}
3239

40+
41+
function* createCommentHandler() {
42+
const state = yield select();
43+
const commentsStore = commentsStoreSelector(state);
44+
const tempId = reduxUtils.getNewId(commentsStore);
45+
46+
const comment = commentFormSelector(state).merge({ id: tempId }).delete('meta');
47+
const reduxComment = {[tempId]: comment.toJS()};
48+
yield put(reduxActions.createComments(reduxComment));
49+
yield call(navigationActions.pop);
50+
try {
51+
const response = yield call(api.postComment, comment);
52+
yield put(reduxActions.createComments(response.entities.comments));
53+
yield put(reduxActions.resetCommentForm());
54+
} catch (e) {
55+
console.log(e);
56+
yield call(Alert.alert, 'Error', 'Could not post your comment', [{text: 'OK'}]);
57+
} finally {
58+
yield put(reduxActions.removeComment(tempId));
59+
}
60+
}
61+
62+
function* createCommentSaga () {
63+
yield* takeLatest(CREATE_COMMENT, createCommentHandler);
64+
}
65+
3366
export default [
3467
fetchSaga,
3568
updateFormSaga,
69+
createCommentSaga
3670
];
3771

3872
const fetch = () => ({ type: FETCH });
3973
const updateForm = (payload) => ({ type: UPDATE_FORM, payload });
74+
const createComment = (payload) => ({ type: CREATE_COMMENT, payload });
4075

4176
export const actions = {
4277
fetch,
4378
updateForm,
79+
createComment
4480
};

Diff for: mobile/ReactNativeTutorial/app/reducers/commentsStoreReducer.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { fromJS } from 'immutable';
22

33
const CREATE = 'COMMENTS_STORE:CREATE';
4+
const REMOVE = 'COMMENTS_STORE:REMOVE';
45

56
export const initialState = fromJS({
67
meta: {}
@@ -10,6 +11,11 @@ const create = (state, action) => {
1011
return state.merge(action.entities);
1112
};
1213

14+
const remove = (state, action) => {
15+
return state.delete(action.id);
16+
};
17+
18+
1319
export default (state, action) => {
1420
if (!state) {
1521
return initialState;
@@ -18,6 +24,8 @@ export default (state, action) => {
1824
switch (action.type) {
1925
case CREATE:
2026
return create(state, action);
27+
case REMOVE:
28+
return remove(state, action);
2129
default:
2230
return state;
2331
}
@@ -27,6 +35,11 @@ const createComments = (entities) => {
2735
return {type: CREATE, entities};
2836
};
2937

38+
const removeComment = (id) => {
39+
return {type: REMOVE, id };
40+
};
41+
3042
export const actions = {
3143
createComments,
44+
removeComment,
3245
};

Diff for: mobile/ReactNativeTutorial/app/utils/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as redux from './redux';
2+
3+
export const reduxUtils = redux;

Diff for: mobile/ReactNativeTutorial/app/utils/redux.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import _ from 'lodash/fp';
2+
3+
export const getNewId = (store) => {
4+
const tempPrefix = 'temp:';
5+
const tempRegex = new RegExp(`^${tempPrefix}`);
6+
const maxId = store
7+
.keySeq()
8+
.filter(key => tempRegex.test(key))
9+
.map(key => parseInt(key.substring(tempPrefix.length), 10))
10+
.max();
11+
const newId = _.isNil(maxId) ? 0 : maxId + 1;
12+
return `temp${newId}`;
13+
};

0 commit comments

Comments
 (0)