diff --git a/package-lock.json b/package-lock.json index f0346e2..0960083 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,7 +2,6 @@ "name": "bridge-redux-starter-homework", "version": "0.1.0", "lockfileVersion": 1, - "requires": true, "dependencies": { "abab": { "version": "1.0.4", @@ -8916,6 +8915,14 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -8948,14 +8955,6 @@ } } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", diff --git a/src/App.js b/src/App.js index f54312b..df65cbf 100644 --- a/src/App.js +++ b/src/App.js @@ -1,20 +1,42 @@ import React, {Component} from 'react'; import './App.css'; import {connect} from 'react-redux'; -import {addProduct} from './actions/index'; +import {addProduct, removeProduct, submitForm, updateProduct, updateSearchTerm} from './actions/index'; import Chance from 'chance'; +import { Form } from './components/Form'; +import { Product } from './components/Product'; + export const chance = Chance(); -const Product = (props) =>
{props.name}
; +const SearchBar = ({searchTerm, updateSearchTerm}) => { + return ( + { + updateSearchTerm(e.target.value); + } + } + /> + ); +}; + +const EnhancedProductList = ({searchTerm, filteredProducts, productList, remove}) => { + const products = searchTerm.length > 0 ? filteredProducts : productList; + return ( +
+ { + products.map(product => ) + } +
+ ); +}; const DaBest = ({name}) =>

The Best: {name}

; -const AdderButton = ({add}) => +const AdderButton = ({add}) => ; class App extends Component { - - constructor(props) { super(props); } @@ -30,14 +52,16 @@ class App extends Component { } render() { - const {productList, add, whoIsTheBest} = this.props; + const {whoIsTheBest, productList, searchTerm, filteredProducts, add, remove, updateSearchTerm} = this.props; debugger; return (
+ - {productList.map(product => )} - + +
+
); } @@ -49,15 +73,21 @@ class App extends Component { const mapStateToProps = state => { return { productList: state.products.productList, + searchTerm: state.products.searchTerm, whoIsTheBest: 'Della', // an example of how to derive state in the mapStateToProps function - this is a specific 'subset' of the full list lowStockProducts: state.products.productList.filter(prod => prod.stock && prod.stock < 4), + filteredProducts: state.products.productList.filter(prod => prod.name.toLowerCase().includes(state.products.searchTerm.toLowerCase())), } }; const mapDispatchToProps = { add: addProduct, + remove: removeProduct, + submitForm, + updateProduct, + updateSearchTerm }; export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/src/actions/index.js b/src/actions/index.js index 79e204e..b1f8c36 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,9 +1,12 @@ export const ACTION_TYPES = { addProduct: 'ADD_PRODUCTS', + removeProduct: 'REMOVE_PRODUCTS', + submitForm: 'SUBMIT_FORM', + updateProduct: 'UPDATE_PRODUCT', + updateSearchTerm: 'UPDATE_SEARCHTERM', }; export function addProduct(product) { - debugger; return { type: ACTION_TYPES.addProduct, payload: { @@ -11,3 +14,31 @@ export function addProduct(product) { } } } + +export function removeProduct(productId) { + return { + type: ACTION_TYPES.removeProduct, + payload: productId + } +} + +export function submitForm() { + return { + type: ACTION_TYPES.submitForm + } +} + +export function updateProduct(label, val) { + return { + type: ACTION_TYPES.updateProduct, + label, + val + } +} + +export function updateSearchTerm(term) { + return { + type: ACTION_TYPES.updateSearchTerm, + payload: term + } +} diff --git a/src/components/Form.js b/src/components/Form.js new file mode 100644 index 0000000..9382577 --- /dev/null +++ b/src/components/Form.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export const Form = ({submitForm, updateProduct}) => { + return ( +
+
Name: {updateProduct('name', e.target.value)}}/>
+
Department: {updateProduct('department', e.target.value)}}/>
+
Price: {updateProduct('price', e.target.value)}}/>
+
Stock: {updateProduct('stock', e.target.value)}}/>
+
Type: {updateProduct('type', e.target.value)}}/>
+ +
+ ) +}; diff --git a/src/components/Product.js b/src/components/Product.js new file mode 100644 index 0000000..5d63df0 --- /dev/null +++ b/src/components/Product.js @@ -0,0 +1,15 @@ +import React from 'react'; + +export const Product = ({id, name, department, price, stock, type, remove}) => { + debugger; + return ( +
+

{name}

+
Department: {department}
+
Price: {price}
+
Stock: {stock}
+
Type: {type}
+ +
+ ); +}; diff --git a/src/reducers/index.js b/src/reducers/index.js index 5cb12f1..7e9f7a1 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,20 +1,35 @@ import {combineReducers} from 'redux'; -import {generateProducts} from '../utils/data'; +import {generateProducts, chance} from '../utils/data'; import {ACTION_TYPES} from '../actions'; - // you'll notice I set my initial state below on line 13 to equal this object! This is a common pattern const INITIAL_STATE = { - productList: generateProducts(10), - searchString: '' // hint for optional homework + productList: generateProducts(10), + searchTerm: '', + product: { + id: chance.guid(), + name: '', + department: '', + price: 0, + stock: 0, + type: '', + } }; -export const products = (state = INITIAL_STATE, {type, payload}) => { +export const products = (state = INITIAL_STATE, {type, payload, label, val}) => { switch (type) { case ACTION_TYPES.addProduct: // using object spread, I am saying - I want to return the old state, except change the productList property return {...state, productList: state.productList.concat(payload.product)}; + case ACTION_TYPES.removeProduct: + return {...state, productList: state.productList.filter(prod => prod.id !== payload)}; + case ACTION_TYPES.submitForm: + return {...state, productList: state.productList.concat(state.product)}; + case ACTION_TYPES.updateProduct: + return {...state, product: {...state.product, [label]: val}}; + case ACTION_TYPES.updateSearchTerm: + return {...state, searchTerm: payload}; } return state; }; diff --git a/src/reducers/index.test.js b/src/reducers/index.test.js index dd671fd..0ce0476 100644 --- a/src/reducers/index.test.js +++ b/src/reducers/index.test.js @@ -2,6 +2,9 @@ import { ACTION_TYPES } from '../actions'; import { products } from './index'; it('adds a product on ADD_PRODUCT', () => { + const intialState = { + productList: [] + }; const action = { type: ACTION_TYPES.addProduct, payload: { @@ -10,7 +13,54 @@ import { products } from './index'; } } }; - expect(products([], action)).toEqual([{ - name: 'Sofa', - }]); + expect(products(intialState, action).productList).toEqual([{name: 'Sofa'}]); + }); + + it('remove a product on REMOVE_PRODUCT', () => { + const intialState = { + productList: [{ + id: '123', + name: 'table', + }] + }; + const action = { + type: ACTION_TYPES.removeProduct, + payload: '123' + }; + expect(products(intialState, action).productList).toEqual([]); + }); + + it('add a product through Form on SUBMIT_FORM', () => { + const intialState = { + productList: [], + product: { + name: 'table' + } + } + const action = { + type: ACTION_TYPES.submitForm + }; + expect(products(intialState, action).productList).toEqual([{name: 'table'}]); + }); + + it('update product info on UPDATE_PRODUCT', () => { + const initialState = { + product: { + name: '' + } + }; + const action = { + type: ACTION_TYPES.updateProduct, + label: 'name', + val: 'table' + }; + expect(products(initialState, action).product.name).toEqual('table'); + }); + + it('update search term on UPDATE_SEARCHTERM', () => { + const action = { + type: ACTION_TYPES.updateSearchTerm, + payload: 'chair' + }; + expect(products({searchTerm: ''}, action).searchTerm).toEqual('chair'); });