{"id":18997438,"url":"https://github.com/vedanthb/tip-calculator-redux","last_synced_at":"2026-06-18T01:31:48.576Z","repository":{"id":118803072,"uuid":"443721385","full_name":"VedanthB/tip-calculator-redux","owner":"VedanthB","description":"tip calculator app to practice redux fundaments ","archived":false,"fork":false,"pushed_at":"2022-01-02T17:04:14.000Z","size":564,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-21T12:35:14.870Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VedanthB.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-02T08:55:53.000Z","updated_at":"2022-01-02T17:04:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"9bbb1752-c56f-4774-a0f8-d0e971da1f5a","html_url":"https://github.com/VedanthB/tip-calculator-redux","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/VedanthB/tip-calculator-redux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VedanthB%2Ftip-calculator-redux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VedanthB%2Ftip-calculator-redux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VedanthB%2Ftip-calculator-redux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VedanthB%2Ftip-calculator-redux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VedanthB","download_url":"https://codeload.github.com/VedanthB/tip-calculator-redux/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VedanthB%2Ftip-calculator-redux/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34472822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T17:40:50.930Z","updated_at":"2026-06-18T01:31:48.559Z","avatar_url":"https://github.com/VedanthB.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tip Calculator\n\nA sample React and Redux application for teaching React and Redux.\n\nPart of the [Redux Fundamentals](https://stevekinney.github.io/redux-fundamentals) course for [Frontend Masters](https://frontendmasters.com).\n\n## Building a Tip Calculator in Redux\n\nLet's start by creating some initial state for our application.\n\n```js\nconst initialState = [\n  { uuid: 1, name: 'Tofu Roast', price: 14, quantity: 1 },\n  { uuid: 2, name: 'Vegan Ham', price: 12, quantity: 1 }\n]\n```\n\nWe'll also start with the world's simplest reducer again.\n\n```ts\nexport const reducer = (state = initialState, action) =\u003e {\n  return state;\n};\n```\n\nOkay, we mostly have everything we need to create a store. Let's hook this up to our React application.\n\nIn `index.js`, let's pull in everything we need.\n\n```js\nimport { Provider } from 'react-redux';\nimport { createStore } from 'redux';\nimport { reducer } from './reducer';\n```\n\nNext we'll create our store.\n\n```js\nconst store = createStore(reducer);\n```\n\nSo far, so good. Now, just like with the Context API, we need to wrap our application in a `Provider`.\n\n```js\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cTheme.Provider theme=\"default\"\u003e\n      \u003cReact.StrictMode\u003e\n        \u003cApplication /\u003e\n      \u003c/React.StrictMode\u003e\n    \u003c/Theme.Provider\u003e\n  \u003c/Provider\u003e,\n  document.getElementById('root')\n);\n```\n\n## Hooking It Up to the Redux Dev Tools\n\n`createStore` takes a second argument for `enhancers` and/or middleware.\n\nWe can add in a line to hook up our Redux store to the developer tools.\n\n```js\nconst store = createStore(\n  reducer,\n  window.__REDUX_DEVTOOLS_EXTENSION__ \u0026\u0026 window.__REDUX_DEVTOOLS_EXTENSION__()\n);\n```\n\nIf we look at the tools, we'll see the following that Redux fired an `@@INIT` event. This went through the reducer and then populated the state with our `initialState`.\n\n## Hooking the State Up a Component\n\nThis is very cool. But, we still have our hard-coded menu items.\n\nLet's fix this. We're going to start by using `connect`, but we'll play around with some other approaches in a bit.\n\n```js\nimport { connect } from 'react-redux';\n```\n\n`connect` takes a bunch of arguments, but we'll start by just focusing on two of them. `mapStateToProps` and `mapDispatchToProps`.\n\nWe'll copy over some of the logic from `Calculator.js` and move it into our new `MenuItems.js`.\n\n```js\nconst MenuItems = ({ items }) =\u003e {\n  return (\n    \u003c\u003e\n      {items.map((item) =\u003e (\n        \u003cMenuItem {...item} key={item.uuid} /\u003e\n      ))}\n    \u003c/\u003e\n  );\n};\n```\n\n`MenuItems` takes a prop called `items`. It would be cool to figure out how to map the items in our Redux store to the props that `MenuItems` takes, right? Maybe, we should make a function called `mapStateToProps`.\n\n```js\nconst mapStateToProps = (state) =\u003e {\n  // …\n};\n```\n\nThis is just a function where Redux is going to pass in our entire state tree and we can pluck off what we want from it and map it to the props of the component that we're working with.\n\n```js\nconst mapStateToProps = (state) =\u003e {\n  return {\n    items: state\n  };\n};\n\nexport const ConnectedMenuItems = connect(mapStateToProps)(MenuItems);\n```\n\nYou'll notice two sets of parentheses. `connect` actually returns a function that takes a component as an argument. This allows you to reuse your logic to hook it up to multiple components.\n\nNow, we can update our `Calculator` component.\n\n```js\nconst Calculator = () =\u003e {\n  return (\n    \u003cCard\u003e\n      \u003cNewItemForm /\u003e\n      \u003cStack orientation=\"vertical\" spacing=\"space60\"\u003e\n        \u003cConnectedMenuItems /\u003e // 👈\n      \u003c/Stack\u003e\n      \u003cTipSelect /\u003e\n      \u003cStack orientation=\"vertical\" spacing=\"space30\"\u003e\n        \u003cSummaryLine title=\"Subtotal\"\u003e$0.00\u003c/SummaryLine\u003e\n        \u003cSummaryLine title=\"Tax\"\u003e$0.00\u003c/SummaryLine\u003e\n        \u003cSummaryLine title=\"Tip Amount\"\u003e$0.00\u003c/SummaryLine\u003e\n        \u003cSummaryLine title=\"Total\"\u003e$0.00\u003c/SummaryLine\u003e\n      \u003c/Stack\u003e\n    \u003c/Card\u003e\n  );\n};\n```\n\n## Dispatching from the UI\n\nIn order to update the state—and subsequently the UI, we're going to need to do a few things.\n\n- We need an action to dispatch.\n- We need the reducer to deal with that action.\n- We need the `NewItemForm` to dispatch that action.\n\nWe'll use the aciton creator pattern to format our action for us in `reducer.js`.\n\n```js\nexport const ADD_NEW_ITEM = 'ADD_NEW_ITEM';\n\nexport const addNewItem = (name, price) =\u003e {\n  return {\n    type: ADD_NEW_ITEM,\n    payload: {\n      uuid: Date.now(),\n      name,\n      price,\n      quantity: 1\n    }\n  };\n};\n```\n\nNext, we'll update the reducer.\n\n```js\nexport const reducer = (state = initialState, action) =\u003e {\n  if (action.type === ADD_NEW_ITEM) {\n    return [...state, action.payload];\n  }\n\n  return state;\n};\n```\n\nLet's try out firing an action from the developer tools.\n\n```js\n{\n  type: 'ADD_NEW_ITEM',\n  payload: { uuid: 3, name: 'Braised Seitan', price: 12, quantity: 1 }\n}\n```\n\nCool, we're most of the way there. Now we just need to wire that up with the `NewItemForm` and we're good to go.\n\nWe can't just require the action creator in the component because it's just a function that returns an object and it doesn't know anything about `dispatch`.\n\nWhat we want to do is pass in an `onSubmit` prop, which the component is already expecting that is bound to Redux's `dispatch`.\n\nLet's start with the simplest possible version:\n\n```js\nimport { connect } from 'react-redux';\nimport { NewItemForm } from './NewItemForm';\n\nexport const ConnectedNewItemForm = connect()(NewItemForm);\n```\n\nConnect components received `dispatch` out of the box. So, now we can do something like this:\n\n```js\nexport const NewItemForm = ({ onSubmit, dispatch }) =\u003e {\n  // …\n\n  const handleSubmit = (event) =\u003e {\n    event.preventDefault();\n\n    if (typeof onSubmit === 'function') {\n      onSubmit(event, { name, price });\n    }\n\n    dispatch(addNewItem(name, price));\n\n    setName('');\n    setPrice(0);\n  };\n\n  // …\n};\n```\n\n(We'll also want to swap out `NewItemForm` for `ConnectedNewItemForm` in `Calculator.js`.)\n\nThis approach is a bit flawed. It ties our presentational component to Redux, which is less than optimal. It doesn't create a clear API contract. `NewItemForm` can literally dispatch anything it wants. We can do better.\n\nJust like we can format our state to the props of a presentation component. We can do that with `dispatch` as well.\n\n```js\nimport { connect } from 'react-redux';\nimport { NewItemForm } from './NewItemForm';\nimport { addNewItem } from './reducer';\n\nconst mapDispatchToProps = (dispatch) =\u003e {\n  return {\n    onSubmit: (name, price) =\u003e dispatch(addNewItem(name, price))\n  };\n};\n\nexport const ConnectedNewItemForm = connect(\n  null,\n  mapDispatchToProps\n)(NewItemForm);\n```\n\nWe can rip out that fun stuff we did with `dispatch` and put the component back to the way we found it.\n\nLet's say we had a whole bunch of actions. We probably don't need to call each one with `dispatch` by hand. We can use `bindActionCreators` in order to take an object of action creators and spit out an object with all of those aciton creators bound to `dispatch`.\n\n```js\nconst mapDispatchToProps = (dispatch) =\u003e {\n  return bindActionCreators(\n    {\n      onSubmit: addNewItem\n    },\n    dispatch\n  );\n};\n```\n\nFor simple things, we can also use a simpler syntax.\n\n```js\nconst mapDispatchToProps = {\n  onSubmit: addNewItem\n};\n```\n\nIf `connect` receives an object, it will automatically pass it to `bindActionCreators` and pass it through to the component.\n\n## Removing an Item\n\n**Exercise**: Can you wire up the button to remove an item from state?\n\nThere are a few ways that we can tackle this. Let's start by creating an action creator.\n\n```js\nexport const REMOVE_ITEM = 'REMOVE_ITEM';\n\nexport const removeItem = (uuid) =\u003e {\n  return {\n    type: REMOVE_ITEM,\n    payload: {\n      uuid\n    }\n  };\n};\n```\n\nAnd then we need to add some logic to the reducer.\n\n```js\nif (action.type === REMOVE_ITEM) {\n  return state.filter(item =\u003e item.uuid !== action.payload.uuid);\n}\n```\n\nThat doesn't make me feel great, but here we are.\n\nNow, this is where it's going to get a bit tricky. Our application isn't well set up for this.\n\nWe could do something like this in `MenuItems.js`.\n\n```js\nconst MenuItems = ({ items, dispatch }) =\u003e {\n  return (\n    \u003c\u003e\n      {items.map((item) =\u003e {\n        const remove = () =\u003e dispatch(removeItem(item.uuid));\n        return \u003cMenuItem {...item} key={item.uuid} removeItem={remove} /\u003e;\n      })}\n    \u003c/\u003e\n  );\n};\n```\n\nThis will work, but it has the same problems as before.\n\nThere is another approach. React Redux comes with a `useDispatch` hook that you can pull out from wherever you want.\n\nInside of `MenuItem`, we can do the following:\n\n```js\nconst dispatch = useDispatch();\n\n// …\n\n\u003cButton\n  variant=\"destructive_secondary\"\n  size=\"small\"\n  onClick={() =\u003e dispatch(removeItem(uuid))}\n\u003e\n  Remove\n\u003c/Button\u003e\n```\n\nAgain, not bad, but it's really tying our state management to our view layer again. (Granted, this is a general complain about hooks.)\n\n**Exercise**: Can you implement changing the quantity and the price of an item?\n\n### Solution\n\nHere are the action creators:\n\n```js\nexport const updatePrice = (uuid, price) =\u003e {\n  return {\n    type: UPDATE_PRICE,\n    payload: {\n      uuid,\n      price\n    }\n  };\n};\n\nexport const updateQuantity = (uuid, quantity) =\u003e {\n  return {\n    type: UPDATE_QUANTITY,\n    payload: {\n      uuid,\n      price: quantity\n    }\n  };\n};\n```\n\nIn the reducer:\n\n```js\nif (action.type === UPDATE_PRICE) {\n  return state.map((item) =\u003e {\n    if (item.uuid !== action.payload.uuid) return item;\n    return { ...item, price: action.payload.price };\n  });\n}\n\nif (action.type === UPDATE_QUANTITY) {\n  return state.map((item) =\u003e {\n    if (item.uuid !== action.payload.uuid) return item;\n    return { ...item, price: action.payload.quantity };\n  });\n}\n```\n\nIn the component:\n\n```js\n\u003cBox padding=\"space20\"\u003e\n  \u003cLabel htmlFor={`${uuid}-price`}\u003ePrice\u003c/Label\u003e\n  \u003cInput\n    id={`${uuid}-price`}\n    insertBefore={\u003cdiv\u003e$\u003c/div\u003e}\n    value={price}\n    onChange={(event) =\u003e dispatch(updatePrice(+event.target.value))}\n  /\u003e\n\u003c/Box\u003e\n\u003cBox padding=\"space20\"\u003e\n  \u003cLabel htmlFor={`${uuid}-quantity`}\u003eQuantity\u003c/Label\u003e\n  \u003cInput\n    id={`${uuid}-quantity`}\n    value={quantity}\n    onChange={(event) =\u003e dispatch(updateQuantity(+event.target.value))}\n  /\u003e\n\u003c/Box\u003e\n```\n\n### Bonus\n\nWe could use `bindActionCreators` with the `dispatch` we got from the `useDispatch` hook.\n\n```js\nconst actions = bindActionCreators(\n  {\n    removeItem,\n    updatePrice,\n    updateQuantity\n  },\n  dispatch\n);\n```\n\n## Refactoring Our Menu Items\n\nCan we get that syntax where we get some of those nice perks of separating our data from our view layer? (Spoiler alert: Yes.)\n\n```js\nimport { connect } from 'react-redux';\nimport { bindActionCreators } from 'redux';\nimport MenuItem from './MenuItem';\nimport { removeItem, updatePrice, updateQuantity } from './reducer';\n\nconst mapStateToProps = (state, ownProps) =\u003e {\n  const item = state.find((item) =\u003e item.uuid === ownProps.uuid);\n\n  return { ...item };\n};\n\nconst mapDispatchToProps = (dispatch, ownProps) =\u003e {\n  return bindActionCreators(\n    {\n      updatePrice(price) {\n        updatePrice(ownProps.uuid, price);\n      },\n      updateQuantity(quantity) {\n        updateQuantity(ownProps.uuid, quantity);\n      },\n      remove() {\n        removeItem(ownProps.uuid);\n      }\n    },\n    dispatch\n  );\n};\n\nexport const ConnectedMenuItem = connect(\n  mapStateToProps,\n  mapDispatchToProps\n)(MenuItem);\n```\n\nAn exercise to the reader could be to write another function that passed in that `uuid` as the first argument to all of those functions.\n\n## Deriving Data\n\nSo, what do we do about the total price? Let's talk about what we're not going to do.\n\nWe're not going to store computable data in our store and then try to keep everything up to date. We're going to use `mapStateToProps` to derive whatever data we need whenever we need it.\n\n```js\nconst mapStateToProps = (state, ownProps) =\u003e {\n  const item = state.find((item) =\u003e item.uuid === ownProps.uuid);\n\n  item.total = item.price * item.quantity;\n\n  return { ...item };\n};\n```\n\nThis will work, but you'll notice that when state change, it's recomputing _everything_.\n\nWhat if we could figure out if things have changed that matter for this component and then only rerender in those situations?\n\n## Using Selectors\n\n```js\nconst getMenuItem = (state, props) =\u003e {\n  return state.find((item) =\u003e item.uuid === props.uuid)\n};\n\nconst menuItem = createSelector([getMenuItem], (item) =\u003e {\n  item.total = item.price * item.quantity;\n  return item;\n});\n\nconst mapStateToProps = (state, ownProps) =\u003e {\n  return { ...menuItem(state, ownProps) };\n};\n```\n\nThis is still not great. The array changes every time.\n\n## Restructing State\n\nAll this stuff we're doing with arrays isn't great. We're scanning through an array every time to find every menu item. That's fine in this silly example, but you can see how it might now scale.\n\nHash maps make a lot more sense. (Hash maps are just objects in JavaScript.)\n\nThe flatter you can keep your state and the more you can use objects, the happier you'll be.\n\n```js\nexport const newInitialState = {\n  items: {\n    1: {\n      name: 'Tofu Roast',\n      price: 14,\n      quantity: 1\n    },\n    2: {\n      name: 'Vegan Ham',\n      price: 12,\n      quantity: 1\n    }\n  },\n  itemIds: [1, 2]\n};\n```\n\nThis has a bunch of advantages. You can now edit an individual menu item without re-rendering the whole list. You can also access one of them without filtering through an array.\n\nIn `MenuItems`:\n\n```js\nconst MenuItems = ({ items, dispatch }) =\u003e {\n  return (\n    \u003c\u003e\n      {items.map((uuid) =\u003e {\n        return \u003cConnectedMenuItem uuid={uuid} key={uuid} /\u003e;\n      })}\n    \u003c/\u003e\n  );\n};\n\nconst mapStateToProps = (state) =\u003e {\n  return {\n    items: state.itemIds\n  };\n};\n```\n\nWe're going to refactor this, but let's do this for now in `ConnectedMenuItem` just to stop the crashing.\n\n```js\nconst getMenuItem = (state, props) =\u003e {\n  return state.items[props.uuid];\n};\n```\n\nThen, in `reducer.js`, we'll do the following to the structure of our state.\n\n```js\nexport const newInitialState = {\n  items: {\n    1: {\n      name: 'Tofu Roast',\n      price: 14,\n      quantity: 1\n    },\n    2: {\n      name: 'Vegan Ham',\n      price: 12,\n      quantity: 1\n    }\n  },\n  itemIds: [1, 2]\n};\n```\n\nNone of our actions should need to change, but our reducer will need to get a little more complex.\n\n**Exercise**: I'll do `ADD_NEW_ITEM` and `UPDATE_QUANTITY`, you do `UPDATE_PRICE` and `UPDATE_QUANTITY`.\n\n```js\nexport const reducer = (state = newInitialState, action) =\u003e {\n  if (action.type === ADD_NEW_ITEM) {\n    return {\n      items: {\n        ...state.items,\n        [action.payload.uuid]: action.payload\n      },\n      itemIds: [...state.itemIds, action.payload.uuid]\n    };\n  }\n\n  if (action.type === REMOVE_ITEM) {\n    const items = omit(state.items, action.payload.uuid);\n    const itemIds = remove(state.itemIds, (id) =\u003e id !== action.payload.uuid);\n\n    return { items, itemIds };\n  }\n\n  if (action.type === UPDATE_PRICE) {\n    const items = { ...state.items };\n    const target = items[action.payload.uuid];\n\n    items[action.payload.uuid] = {\n      ...target,\n      price: action.payload.price\n    };\n\n    return { ...state, items };\n  }\n\n  if (action.type === UPDATE_QUANTITY) {\n    const items = { ...state.items };\n    const target = items[action.payload.uuid];\n\n    items[action.payload.uuid] = {\n      ...target,\n      quantity: action.payload.quantity\n    };\n\n    return { ...state, items };\n  }\n\n  return state;\n};\n```\n\n## Simplifying Things with Immer\n\nImmer gives us a copy of the object to mutate and then figures out the changes it needs to make. This allows us a much simplier syntax for updating our state.\n\n```js\nif (action.type === UPDATE_PRICE) {\n  return produce(state, (draftState) =\u003e {\n    draftState.items[action.payload.uuid].price = action.payload.price;\n  });\n}\n```\n\n**Exercise**: Can you implement `REMOVE_ITEM`?\n\n### Refactoring the Entire Reducer\n\nWe can use this pattern for the entire reducer.\n\n```js\nexport const reducer = produce((state = newInitialState, action) =\u003e {\n  if (action.type === ADD_NEW_ITEM) {\n    state.items[action.payload.uuid] = action.payload;\n    state.itemIds.push(action.payload.uuid);\n  }\n\n  if (action.type === REMOVE_ITEM) {\n    delete state.items[action.payload.uuid];\n    state.itemIds = remove(state.itemIds, (id) =\u003e id !== action.payload.uuid);\n  }\n\n  if (action.type === UPDATE_PRICE) {\n    state.items[action.payload.uuid].price = action.payload.price;\n  }\n\n  if (action.type === UPDATE_QUANTITY) {\n    state.items[action.payload.uuid].quantity = action.payload.quantity;\n  }\n\n  return state;\n}, newInitialState);\n```\n\n## Breaking Apart the Reducer\n\nOne of the cool things in Redux is that all actions flow through all of the reducers. It can be helpful to break apart your reducers so that you can deal with things indpendently.\n\n```js\nexport const itemReducer = produce((state = newInitialState.items, action) =\u003e {\n  if (action.type === ADD_NEW_ITEM) {\n    state[action.payload.uuid] = action.payload;\n  }\n\n  if (action.type === REMOVE_ITEM) {\n    delete state[action.payload.uuid];\n  }\n\n  if (action.type === UPDATE_PRICE) {\n    state[action.payload.uuid].price = action.payload.price;\n  }\n\n  if (action.type === UPDATE_QUANTITY) {\n    state[action.payload.uuid].quantity = action.payload.quantity;\n  }\n\n  return state;\n}, newInitialState.items);\n\nexport const itemIdReducer = produce(\n  (state = newInitialState.itemIds, action) =\u003e {\n    if (action.type === ADD_NEW_ITEM) {\n      state.push(action.payload.uuid);\n    }\n\n    if (action.type === REMOVE_ITEM) {\n      state = remove(state, (id) =\u003e id !== action.payload.uuid);\n    }\n\n    return state;\n  },\n  newInitialState.itemIds\n);\n\nexport const reducer = combineReducers({\n  items: itemReducer,\n  itemIds: itemIdReducer\n});\n```\n\n## Adding a Tip Reducer\n\nThe reducer is pretty simple in this case.\n\n```js\nconst tipReducer = (amount = 15, action) =\u003e {\n  if (action.type === UPDATE_TIP) {\n    return action.payload;\n  }\n\n  return amount;\n};\n```\n\nAnd then we can add it to our state tree.\n\n```js\nexport const reducer = combineReducers({\n  items: itemReducer,\n  itemIds: itemIdReducer,\n  tip: tipReducer\n});\n```\n\nOur action creator is pretty straight forward as well.\n\n```js\nconst updateTip = (amount) =\u003e {\n  return {\n    type: UPDATE_TIP,\n    payload: +amount\n  };\n};\n```\n\nHooking it up to the component is pretty simple too.\n\n```js\nconst mapStateToProps = (state) =\u003e {\n  return { amount: state.tip };\n};\n\nconst mapDispatchToProps = { updateTip };\n\nexport const TipSelect = connect(\n  mapStateToProps,\n  mapDispatchToProps\n)(({ amount, updateTip }) =\u003e {\nreturn (\n  \u003cBox marginY=\"space80\"\u003e\n    \u003cLabel htmlFor=\"tip-amount\"\u003eHow much would you like to tip?\u003c/Label\u003e\n    \u003cSelect\n      id=\"tip-amount\"\n      value={amount}\n      onChange={(event) =\u003e updateTip(event.target.value)}\n    \u003e\n      \u003cOption value=\"15\"\u003e15%\u003c/Option\u003e\n      \u003cOption value=\"20\"\u003e20%\u003c/Option\u003e\n      \u003cOption value=\"25\"\u003e25%\u003c/Option\u003e\n    \u003c/Select\u003e\n  \u003c/Box\u003e\n);\n});\n```\n\nWe can even update with Reselect if we wanted to—even though it's a little ridiculous.\n\n```js\nconst getTip = (state) =\u003e {\n  return state.tip;\n};\n\nconst tipPercentage = createSelector([getTip], (tip) =\u003e {\n  return tip;\n});\n\nconst mapStateToProps = (state) =\u003e {\n  return { amount: tipPercentage(state) };\n};\n```\n\n## Homework\n\nMap the state to the final calculations at the bottom. You should be using selectors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvedanthb%2Ftip-calculator-redux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvedanthb%2Ftip-calculator-redux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvedanthb%2Ftip-calculator-redux/lists"}