create food item save button wired up to backend

This commit is contained in:
Bart Akeley 2018-02-11 09:53:53 -06:00
parent 4e934d9b6b
commit 54f5bf2893
9 changed files with 87 additions and 52 deletions

View file

@ -17,6 +17,7 @@ export const fetchRequest = async ({
method,
headers: {
'Content-Type': 'application/json',
...AuthManager.getAuthHeader(),
...headers,
},
body: JSON.stringify(body),

View file

@ -1,8 +1,8 @@
// @flow
import { memoize } from 'ramda';
import FilterRecord from '../records/FilterRecord';
import { BASE_URL } from '../constants/AppConstants';
import FoodItemRecord from '../records/FoodItemRecord';
import { fetchRequest } from './FetchApi';
export type FoodItemsFilter = {
radius?: number,
@ -39,12 +39,10 @@ export const getFoodItems = memoize(
const { orderby, categories, radius } = filter;
try {
return fetch(`http://${BASE_URL}/fooditems`, {
return fetchRequest({
endpoint: '/fooditems',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
body: {
lat,
lng,
orderby,
@ -52,14 +50,12 @@ export const getFoodItems = memoize(
...(categories ? { categories } : {}),
radius,
},
}),
})
.then(res => res.json())
.then(json => ({
...json,
loading: false,
error: null,
}));
},
}).then(json => ({
...json,
loading: false,
error: null,
}));
} catch (error) {
console.error(error); // eslint-disable-line no-console
return {
@ -73,10 +69,11 @@ export const getFoodItems = memoize(
}
);
export const createFoodItem = (foodItem: FoodItemRecord) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(foodItem);
}, 1000);
export const createFoodItem = async (foodItem: FoodItemRecord) => {
const res = await fetchRequest({
endpoint: '/addfooditem',
method: 'POST',
body: foodItem,
});
return res;
};

View file

@ -1,7 +1,6 @@
// @flow
import type { Quantity } from '../constants/QuantityConstants';
import type { QuantityResponse } from '../constants/QuantityConstants';
import AuthManager from '../AuthManager';
import { fetchRequest } from './FetchApi';
export const setQuantity = ({
@ -14,7 +13,6 @@ export const setQuantity = ({
return fetchRequest({
method: 'POST',
endpoint: '/quantity',
headers: { ...AuthManager.getAuthHeader() },
body: {
foodItemId,
quantity,

View file

@ -1,9 +1,10 @@
// @flow
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { withCreateFoodItem } from '../enhancers/createFoodItemEnhancers';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import { withCreateFoodItemState } from '../enhancers/createFoodItemEnhancers';
import { withReplaceRoute } from '../enhancers/routeEnhancers';
import { compose, onlyUpdateForKeys, withHandlers } from 'recompose';
import { routeWithTitle } from '../helpers/RouteHelpers';
const FoodItemSaveBtn = ({
saveFoodItem,
@ -27,9 +28,20 @@ const FoodItemSaveBtn = ({
};
export default compose(
withCreateFoodItem,
mapProps(({ saveFoodItem, loading }) => ({
saveFoodItem,
loading,
}))
withCreateFoodItemState,
withReplaceRoute,
withHandlers({
saveFoodItem: ({ saveFoodItem, setLoading, setError, replaceRoute }) => async () => {
try {
setLoading(true);
const { id, name } = await saveFoodItem();
replaceRoute(routeWithTitle(`/foodItem/${id || ''}`, name));
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
},
}),
onlyUpdateForKeys(['loading'])
)(FoodItemSaveBtn);

View file

@ -1,31 +1,27 @@
// @flow
import mapPropsStream from 'recompose/mapPropsStream';
import CreateFoodItem$, { emitter } from '../streams/CreateFoodItemStream';
import FoodItemRecord from '../records/FoodItemRecord';
import CreateFoodItem$, { emitter as emitCreateItemState } from '../streams/CreateFoodItemStream';
import { emitter as emitFoodItemsState } from '../streams/FoodItemsStream';
import { createFoodItem } from '../apis/FoodItemsApi';
import FoodItemRecord, { createFoodItem as buildFoodItem } from '../records/FoodItemRecord';
// todo real implementation somewhere
const isFoodItemValid = () => true;
export const withCreateFoodItem = mapPropsStream(props$ => {
export const withCreateFoodItemState = mapPropsStream(props$ => {
return props$.combineLatest(CreateFoodItem$, (props, state) => {
const { foodItem, loading, error } = state;
const setFoodItem = (foodItem: FoodItemRecord) => emitter({ ...state, foodItem });
const setLoading = (loading: boolean) => emitter({ ...state, loading });
const setError = (error: Error) => emitter({ ...state, error });
const setFoodItem = (foodItem: FoodItemRecord) => emitCreateItemState({ ...state, foodItem });
const setLoading = (loading: boolean) => emitCreateItemState({ ...state, loading });
const setError = (error: Error) => emitCreateItemState({ ...state, error });
const saveFoodItem = async () => {
if (isFoodItemValid(foodItem)) {
try {
setLoading(true);
await createFoodItem(foodItem);
} catch (error) {
// todo else surface a toast notification
setError(error);
} finally {
setLoading(false);
}
const res = await createFoodItem(foodItem);
const createdFoodItem = buildFoodItem(res[0]);
emitFoodItemsState(createdFoodItem);
return createdFoodItem;
}
};
@ -36,6 +32,8 @@ export const withCreateFoodItem = mapPropsStream(props$ => {
error,
setFoodItem,
saveFoodItem,
setLoading,
setError,
};
});
});

View file

@ -1,5 +1,5 @@
// @flow
import { getContext, mapProps } from 'recompose';
import { getContext, withProps, compose } from 'recompose';
import { shape, func, string } from 'prop-types';
import { path } from 'ramda';
import { getSearch } from '../helpers/RouteHelpers';
@ -21,16 +21,23 @@ export const withRouterContext = getContext({
}).isRequired,
});
export const withViewMode = mapProps((props: Object) => {
export const withViewMode = withProps((props: Object) => {
return {
...props,
viewMode: getSearch(props).viewMode,
};
});
export const withPushRoute = mapProps((props: Object) => {
export const withPushRoute = withProps((props: Object) => {
return {
...props,
pushRoute: path(['router', 'history', 'push'], props),
};
});
export const withReplaceRoute = compose(
withRouterContext,
withProps((props: Object) => {
return {
replaceRoute: path(['router', 'history', 'replace'], props),
};
})
);

View file

@ -12,7 +12,7 @@ import { compose, branch, withState, withHandlers, renderComponent, mapProps } f
import RNGooglePlaces from 'react-native-google-places';
import CategoryPicker from '../components/CategoryPicker';
import { ImageThumb, ImagePicker } from '../components/ImagePicker';
import { withCreateFoodItem } from '../enhancers/createFoodItemEnhancers';
import { withCreateFoodItemState } from '../enhancers/createFoodItemEnhancers';
import Spinner from 'react-native-loading-spinner-overlay';
type GooglePlaceObject = {
@ -165,7 +165,7 @@ const toggleNameModal = ({ nameModalOpen, setNameModalOpen }) => () =>
setNameModalOpen(!nameModalOpen);
export default compose(
withCreateFoodItem,
withCreateFoodItemState,
withState('place', 'setPlace', new PlaceRecord()),
withState('nameModalOpen', 'setNameModalOpen', false),
withState('imagePreview', 'setImagePreview', -1),

View file

@ -1,4 +1,5 @@
//@flow
import { ReplaySubject } from 'rxjs';
import FoodItemRecord, { createFoodItem } from '../records/FoodItemRecord';
import { setById } from '../helpers/ImmutableHelpers';
import { Map } from 'immutable';
@ -9,11 +10,31 @@ import FilterRecord from '../records/FilterRecord';
import Quantity$ from './QuantityStream';
import type { QuantityFragment } from '../constants/QuantityConstants';
export default location$
const foodItemSubject: ReplaySubject<FoodItemRecord> = new ReplaySubject();
export function emitter(val?: ?FoodItemRecord) {
foodItemSubject.next(val);
}
emitter(null);
const manualUpdate$ = foodItemSubject.scan(
(foodItemMap: Map<string, FoodItemRecord>, foodItem: FoodItemRecord) => {
return foodItem ? foodItemMap.set(foodItem.id, foodItem) : foodItemMap;
},
Map()
);
const fetchedFoodItems$ = location$
.combineLatest(Filter$)
.mergeMap(([loc, filter]: [Position, FilterRecord]) => getFoodItems({ loc, filter }))
.map(({ fooditems = [] }: FoodItemsForLocation) => {
return fooditems.map(createFoodItem).reduce(setById, new Map());
});
export default fetchedFoodItems$
.combineLatest(manualUpdate$, (foodItemMap: Map<string, FoodItemRecord>, manualUpdates) => {
return foodItemMap.mergeDeep(manualUpdates);
})
.combineLatest(
Quantity$,

View file

@ -9,6 +9,7 @@ export default Observable.create((obs: Observer<Position>): Observable<Position>
},
{
enableHighAccuracy: false,
maximumAge: 10000,
timeout: 1000,
}
);