Simple Webshop



Goals
-
Building a simple webshop to proof that I understand the concepts of react.js and redux
-
Applying best practices from tutorials and communities.
-
Add an advanced element into the application like redux-saga, to show that I can enhance react application in a scalable and maintainable fashion
Break The UI Into A Component Hierarchy
Simple happy application flow

ActionTypes
ActionCreators
export const ADD_PRODUCT_TO_CART = 'ADD_PRODUCT_TO_CART';
export const REMOVE_PRODUCT_FROM_CART = 'REMOVE_PRODUCT_FROM_CART';
export const OPEN_CART_LIST_DRAWER = 'OPEN_CART_LIST_DRAWER';
export const CLOSE_CART_LIST_DRAWER = 'CLOSE_CART_LIST_DRAWER';
export const SET_PRODUCTS = 'SET_PRODUCTS';
export const REMOVE_PRODUCT = 'REMOVE_PRODUCT';
export const ADD_PRODUCT = 'ADD_PRODUCT';
export const SET_VIEW_PRODUCT_ID = 'SET_VIEW_PRODUCT_ID';
export const SET_SELECTED_PRODUCT = 'SET_SELECTED_PRODUCT';
import {
ADD_PRODUCT_TO_CART, CLOSE_CART_LIST_DRAWER, OPEN_CART_LIST_DRAWER, SET_PRODUCTS,
SET_SELECTED_PRODUCT, SET_VIEW_PRODUCT_ID
} from "./actionTypes";
import {change} from 'redux-form';
export const initShopProducts = (products) => {
return {type: SET_PRODUCTS, products}
};
export const selectProductToView = (selectedProduct) => {
return {type: SET_SELECTED_PRODUCT, selectedProduct}
};
export const selectProductIdToView = (productId) => {
return {type: SET_VIEW_PRODUCT_ID, productId};
};
export const addProductToCart =(product) => {
return {type: ADD_PRODUCT_TO_CART, product};
};
export const openCartListDrawer = () => {
return {type: OPEN_CART_LIST_DRAWER}
};
export const closeCartListDrawer = () => {
return {type: CLOSE_CART_LIST_DRAWER}
};
export const setLatForAddress = (lat) => {
return change('deliveryAddress', 'lat', lat);
};
export const setLngForAddress = (lng) => {
return change('deliveryAddress', 'lng', lng);
};
export const fillInStreet = (street) => {
return change('deliveryAddress', 'street', street);
};
export const fillInCity = (city) => {
return change('deliveryAddress', 'city', city);
};
export const fillInAddress = (data) => {
return change('deliveryAddress', 'address', data);
};
Shop reducers
import {ADD_PRODUCT, REMOVE_PRODUCT, SET_PRODUCTS, SET_SELECTED_PRODUCT, SET_VIEW_PRODUCT_ID} from "../actionTypes";
const defaultState = {
products: [],
selectedProduct: {
image: null,
description: null,
title: null,
price: null
}
};
export default function shop(state = defaultState, action) {
switch (action.type) {
case SET_PRODUCTS:
return {...state, products: action.products}
case REMOVE_PRODUCT:
return Object.assign({}, state, {
products: [
...state.products.filter(product => product.id !== action.product.id)
]
})
case ADD_PRODUCT:
return Object.assign({}, state, {
products: [...state.products, action.product]
})
case SET_VIEW_PRODUCT_ID:
return Object.assign({}, state, {
productId: action.productId
})
case SET_SELECTED_PRODUCT:
return Object.assign({}, state, {
selectedProduct: action.selectedProduct
})
default:
return state
}
}
Cart Reducers
import {products} from '../MockApi';
import {sample} from "lodash";
import {
ADD_PRODUCT_TO_CART, CLOSE_CART_LIST_DRAWER, OPEN_CART_LIST_DRAWER,
REMOVE_PRODUCT_FROM_CART
} from "../actionTypes";
const defaultState = {
products: [sample(products)]
};
export default function (state = defaultState, action) {
switch (action.type) {
case ADD_PRODUCT_TO_CART:
return {
...state,
products: [...state.products.map((product) => {
product.animated = false;
return product;
}), {...action.product, animated: true}]
};
case REMOVE_PRODUCT_FROM_CART:
return {
...state,
products: [...state.products.filter(product => product.id !== action.product.id)]
};
case OPEN_CART_LIST_DRAWER:
return {
...state,
cartListDrawerOpened: true
};
case CLOSE_CART_LIST_DRAWER:
return {
...state,
cartListDrawerOpened: false
};
default:
return state
}
}

export function* getAllProducts() {
const products = yield call(Api.getAllProducts);
yield put(initShopProducts(products));
}
export default function* rootSaga() {
yield fork(getAllProducts);
yield fork(watchViewProductDetail);
yield fork(watchCompleteAddressInput);
}
export function* watchViewProductDetail() {
while (true) {
const {productId} = yield take(SET_VIEW_PRODUCT_ID);
const selectedProduct = yield call(Api.getProduct, parseInt(productId));
yield put(selectProductToView(selectedProduct));
}
}
export default function* rootSaga() {
yield fork(getAllProducts);
yield fork(watchViewProductDetail);
yield fork(watchCompleteAddressInput);
}

function* watchCompleteAddressInput() {
while (true) {
const {meta} = yield take(formActionTypes.CHANGE);
if (meta.field === 'address') {
const {zipcode, housenumber, street, city, address} =
yield select((state) => state.form['deliveryAddress'].values);
if (zipcode && street && housenumber && city && address) {
try {
const {data} = yield call(Api.getGeoCodeByAddress, `${street},${housenumber},${city}`);
const lat = data.results[0]['geometry'].location.lat;
const lng = data.results[0]['geometry'].location.lng;
yield put(setLatForAddress(lat));
yield put(setLngForAddress(lng));
} catch (e) {
}
}
}
}
}
export default function* rootSaga() {
yield fork(getAllProducts);
yield fork(watchViewProductDetail);
yield fork(watchCompleteAddressInput);
}

End of the example showcase
Next challenge would be
- Unit Testing
- Use LocalStorage to store cart session
- Remove product from cart
- Change quantity cart items
Simple Webshop with react.js and redux saga
By tlimpanont
Simple Webshop with react.js and redux saga
- 1,772