Git Product home page Git Product logo

redux-delete-codealong's Introduction

Deleting Items with Redux

Objectives

With this lesson we will continue through our journey through Redux. By the end of this lesson, you will be able to:

  • Delete individual elements

Review and Goal

Throughout each code along in this section, notice that we are never updating the DOM directly. Instead, we use the Redux pattern to have our store hold and update our state, and we then have React display that state. We want to continue with this pattern here.

For this code along, we will continue to work on our Todo application. Our goal this time is to add a delete button next to each todo in the list. When a user clicks on the delete button for a todo, that todo should be removed from the store's state and the DOM should rerender to show the shortened list.

Deleting A Todo

To implement our delete button, we will create an event handler that dispatches a delete action when the button is clicked. Eventually, we'll need to figure out how to tell the store which todo to delete. For now, though, let's add in the button and get the handler for the button's click event set up.

Modifying our TodosContainer

Recall that we set up Todo as a presentational component and connected the TodosContainer to Redux. To stick with this organization, let's write a new mapDispatchToProps() function in TodosContainer to return a delete action:

// ./src/components/todos/TodosContainer.js
import React, { Component } from "react";
import { connect } from "react-redux";
import Todo from "./Todo";

class TodosContainer extends Component {
  renderTodos = () =>
    this.props.todos.map((todo, id) => <Todo key={id} text={todo} />);

  render() {
    return <div>{this.renderTodos()}</div>;
  }
}

const mapStateToProps = (state) => {
  return {
    todos: state.todos,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    delete: (todoText) => dispatch({ type: "DELETE_TODO", payload: todoText }),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(TodosContainer);

Now, TodosContainer will have access to this.props.delete, which takes in the todo text as an argument and sends it as the action's payload.

Next, we need to pass this.props.delete down to Todo, so that each Todo component rendered will have access to our 'DELETE_TODO' action:

renderTodos = () =>
  this.props.todos.map((todo, id) => (
    <Todo delete={this.props.delete} key={id} text={todo} />
  ));

Modifying the Todo Component

Now that Todo is receiving this.props.delete, let's add our button and get it hooked up:

import React from "react";

const Todo = (props) => {
  return (
    <li>
      <span>{props.text}</span>
      <button>DELETE</button>
    </li>
  );
};

export default Todo;

When we click the button next to a given todo we want that specific todo to be removed, so let's add an onClick attribute to the delete button. Recall that, at the moment, our todos are just strings stored in an array. That's all we have to work with for now, but we'll change that a bit later.

To keep this component small, we can provide an anonymous function in-line:

<li>
  <span>{props.text}</span>
  <button onClick={() => props.delete(props.text)}>DELETE</button>
</li>

So, what is happening here? We're providing a definition for an anonymous function. Inside the definition, we're calling props.delete, and passing in props.text as the argument. When the delete button is clicked, back in our connected TodosContainer the value of props.text is passed into our dispatched action as the payload.

Let's boot up the app and check out what we've done so far. There is a console.log in our reducer that displays actions. If you create a todo then click the delete button, you should see an action logged in the console that has the todo's text content as the payload.

Ok, now we have the ability to dispatch an action to the reducer from each Todo!

Tell the Store Which Todo to Delete

Because our todos are stored as strings in an array, to delete a todo, we need to find a way to remove a specific string from an array. One option is to use filter. Let's add a second case to our manageTodo reducer; in it, we'll write a filter that returns every todo that doesn't match what is contained in action.payload:

export default function manageTodo(
  state = {
    todos: [],
  },
  action
) {
  console.log(action);
  switch (action.type) {
    case "ADD_TODO":
      return { todos: state.todos.concat(action.payload.text) };

    case "DELETE_TODO":
      return { todos: state.todos.filter((todo) => todo !== action.payload) };

    default:
      return state;
  }
}

In our browser, the delete button should now successfully cause todos to disappear!

There is a problem though. What if you have multiple todos with the same text? With this set up, every todo that matches action.payload will be filtered out.

To fix this, we can give our Todos specific IDs and filter on those instead.

Give each Todo an id

A Todo should have an id the moment it gets created. Since the Todo is created in our reducer when a CREATE_TODO action is dispatched, let's update that code so that it also adds an id.

// ./src/reducers/manageTodo.js
import uuid from "uuid";

export default function manageTodo(
  state = {
    todos: [],
  },
  action
) {
  console.log(action);
  switch (action.type) {
    case "ADD_TODO":
      const todo = {
        id: uuid(),
        text: action.payload.text,
      };
      return { todos: state.todos.concat(todo) };

    case "DELETE_TODO":
      return { todos: state.todos.filter((todo) => todo !== action.payload) };

    default:
      return state;
  }
}

We are using the uuid node package to generate an id each time a todo is created; don't forget to import it at the top of the file. Now, instead of just storing an array of strings in our store, we'll be storing an array of objects, each of which has an id and the todo text.

Next we need to update our TodosContainer and Todo component to finish hooking up our delete action.

Update TodosContainer

In TodosContainer, our renderTodos method will need to change a little:

renderTodos = () => {
  return this.props.todos.map((todo) => (
    <Todo delete={this.props.delete} key={todo.id} todo={todo} />
  ));
};

The change is small, but this setup is definitely better. Previously, key was based off the index provided by map which is not the best approach. Now it's using our randomly generated ID, and is less prone to errors in the virtual DOM.

The other change is that we are now passing the todo object as a prop rather than just the text. This will enable us to access both todo.id and todo.text in our Todo component.

Update the Todo Component

Next, we need to modify the Todo component to work with the todo object. To do this, we will render props.todo.text (instead of props.text) and pass props.todo.id (instead of props.text) as the argument to our delete function when the button is clicked:

import React from "react";

const Todo = (props) => {
  return (
    <li>
      <span>{props.todo.text}</span>
      <button onClick={() => props.delete(props.todo.id)}>DELETE</button>
    </li>
  );
};

export default Todo;

Now, when props.delete is called, an action is dispatched that contains the id as its payload rather than the text.

Updating DELETE_TODO in the Reducer

Finally, now that we're passing an id to props.delete, we need to make one more tiny change to our reducer:

case "DELETE_TODO":
  return { todos: state.todos.filter((todo) => todo.id !== action.payload)}

Now that todo is an object and we're passing the id as the payload, we need to match todo.id with the payload instead of todo.

With this final change, todo objects can be added and deleted, each with their own unique id!

Summary

Ok, so in this lesson we covered how to delete a specific Todo. To implement this, we gave each Todo a unique id, and then made sure we passed that id into each Todo component. Then we sent along that information when dispatching an action via props.delete. Finally, we had our reducer update the state by filtering out the Todo with the id that was passed as the action's payload.

redux-delete-codealong's People

Contributors

allisonkadel avatar cernanb avatar curiositypaths avatar dependabot[bot] avatar ihollander avatar jeffkatzy avatar kreopelle avatar lizbur10 avatar lukeghenco avatar maxwellbenton avatar rrcobb avatar sylwiavargas avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-delete-codealong's Issues

"time is" is two words

Need a space between "time" and "is" in the sentence below:

"Our goal this timeis to have a button next to each list element with the todo;...."

Thank you.

Suggested change to README at the very end

By the end of this lesson, we have changed the reducer to filter the todos by id instead of by text.

I think it would make sense, then, to update the TodosContainer's mapDispatchToProps function like this:

const mapDispatchToProps = dispatch => {
  return {
    delete: todoId => dispatch({type: 'DELETE_TODO', payload: todoId})
  }
}

This more accurately reflects what the payload now is.

console.log() missing from reducer on master

Hello!

While I was working on the redux-delete-codealong, I noticed that the console.log() the Readme references in the reducer is not present in master when the project is cloned down.

This pull request suggests adding the console.log to the initial src/reducers/manageTodo.js file.

#7

Additional feedback regarding this suggestion would be greatly appreciated!

Best wishes,
Kayla

Doesn't use UUID

In previous READMEs and labs, we were taught to use UUID to generate unique IDs. But why is this codealong using Math.random()*10000000000000000? Kind of seems backwards to me...

Structure of lab is off

@aturkewi @PeterBell @Lukeghenco Unlike most repos this lab requires users to cd into the redux-delete dir to be able to run the proper npm commands. Then you have to cd back up to the parent directory to push up the code and create a proper PR that learn will recognize.

People can simply follow along in the code but then they don't see anything in the browser or console. It should be changed as it may confuse people.

There are some typos in this codealong

  • Under the "Update todos container" heading it says to use todo.text but the example just show todo

  • Also under that same heading, there's a prop called "todo" that was previously called "text"

  • Under the "Give each todo an id" heading, in the code example there's a piece that says "action.payload" that should say "action.payload.text"

  • For the final Todo container, it should says {props.todo} in the span tag in order to render the todo in the list

  • There must be other errors because I couldn't get it to work with the provided instructions

manageTodo.js AND Todo.js aren't aligned with reading instructions

First in the section of the reading titled GIVE EACH TODO AN ID the instructions tell the reader to update the manageTodo.js file with the provided code. When this repository is forked and cloned, that file is exactly the same as the provided code. I don't know what there is to update.

In the following section titled PASS THIS ID AS A PROPERTY TO EACH TODO COMPONENT the part on updating the Todo.js file provides code to update the handleOnClick function with:

handleOnClick() {
this.props.store.dispatch({
type: 'DELETE_TODO',
id: this.props.id,
});
}

This is confusing and I deem to be incorrect. I have written this block of code as the reading provides but in the Chrome console, when I click the delete button, I receive an Uncaught TypeError with 'props' undefined. Therefore to get this working I use my previous code of:

handleOnClick = () => {
this.props.store.dispatch({
type: 'DELETE_TODO',
id: this.props.id,
});
}

Lastly, when I do click the delete button the todos are supposed to be gone, mine are still present in the browser.

Typo

In the first sentence of the section "GIVE EACH TODO AN ID," it should be "A Todo should have an id the moment it gets created."

Out of order

The past several code-alongs have been repeated material already learned / independently coded in actual labs (in this case Cooking with Redux.)

No issue

Made a mistake and thought there was an issue and there wasn't.

'this' in handleOnClick() not working

When I code handleOnClick() as handleOnClick() { (as shown in the ReadMe example), instead of handleOnClick = () => {, this comes in as null instead of as a valid object reference.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.