0

So, currently I'm working on internship React (MERN) app, which is a simple to-do list with ability to create, delete and edit todos. I will post some code from it, but you also can look at the full code on GitHub: https://github.com/Wonderio619/magisale-internship-todo

The next task is connecting my app to MongoDB. I have some "boilerplate" code - I alredy set up connection with MongoDB, also have Express router with routes like get all todos list, send todo to database, update todo with id, get todo with id:

const express = require("express");
const router = express.Router();
let Todo = require('../models/model')

// get all todo list with id
router.get('/', function (req, res) {
  Todo.find()
    .then((todos) => res.json(todos))
    .catch((error) => res.send(error))
})

// send todo to database
router.post('/', function (req, res) {
  let todo = new Todo();
  todo.titleText = req.body.title;
  todo.todoText = req.body.body;

  todo.save(function (err) {
    if (err)
      res.send(err);
    res.send('Todo successfully added!');
  });
})

// get todo with id
router.get('/:todoId', function (req, res) {
  Todo.findById(req.params.todoId)
    .then(foundTodo => res.json(foundTodo))
    .catch(error => res.send(error));
})

// updates todo with id
router.put('/:todoId', function (req, res) {
  Todo.findOneAndUpdate({ _id: req.params.todoId }, req.body, { new: true })
    .then((todo) => res.json(todo))
    .catch((error) => res.send(error))
})

// deletes todo with id
router.delete('/:todoId', function (req, res) {
  Todo.remove({ _id: req.params.todoId })
    .then(() => res.json({ message: 'todo is deleted' }))
    .catch((error) => res.send(error))
})

module.exports = router;

These routes used when corresponding methods from todo app are called:

import React, { Component } from 'react';
import './ToDo.css';
import Logo from './assets/logo.png';
import ToDoItem from './components/ToDoItem';
import AppBar from './components/AppBar';
import Popover from './components/Popover';
import { connect } from 'react-redux';

class ToDo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [],
      title: '',
      todo: '',
    };
  };

  componentDidMount = () => {
    fetch("/api/todos")
      .then(data => data.json())
      .then(res => this.setState({ list: res.data }));
    console.log(this.state.list)
  };


  createNewToDoItem = () => {
    fetch("/api/todos", {
      method: "post",
      headers: new Headers({
        "Content-Type": "application/json"
      }),
      body: JSON.stringify({
        title: this.state.title,
        body: this.state.todo
      })
    })
      .catch(err => {
        console.error(err);
      });

    if (this.state.title !== '' & this.state.todo !== '') {
      this.props.createTodoItem(this.state.title, this.state.todo);
      this.setState({ title: '', todo: '' });
    }
  };

  handleTitleInput = e => {
    this.setState({
      title: e.target.value,
    });
  };

  handleTodoInput = e => {
    this.setState({
      todo: e.target.value,
    });
  };

  editItem = (i, updTitle, updToDo) => {
    const modifyURL = "/api/todos/" + i;
    fetch(modifyURL, {
      method: "put",
      headers: new Headers({
        "Content-Type": "application/json"
      }),
      body: JSON.stringify({
        title: updTitle,
        todo: updToDo
      })
    })
      .then(resp => {
        if (!resp.ok) {
          if (resp.status >= 400 && resp.status < 500) {
            return resp.json().then(data => {
              let error = { errorMessage: data.message };
              throw error;
            });
          } else {
            let error = {
              errorMessage: "Please try again later. Server is not online"
            };
            throw error;
          }
        }
        return resp.json();
      })
      .then(newTodo => {
        let arr = this.props.list;
        arr[i].title = updTitle;
        arr[i].todo = updToDo;
        this.setState({ updateList: true });
      });
  };

  deleteItem = indexToDelete => {
    const deleteURL = "/api/todos/" + indexToDelete;
    fetch(deleteURL, {
      method: "delete"
    })
      .then(resp => {
        if (!resp.ok) {
          if (resp.status >= 400 && resp.status < 500) {
            return resp.json().then(data => {
              let error = { errorMessage: data.message };
              throw error;
            });
          } else {
            let error = {
              errorMessage: "Please try again later. Server is not online"
            };
            throw error;
          }
        }
        return resp.json();
      })
      .then(() => {
        this.props.deleteTodoItem(indexToDelete);
      });
  };

  randId() {
    return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
  }

  eachToDo = (item, i) => {
    return <ToDoItem
      key={this.randId()}
      title={item.title}
      todo={item.todo}
      deleteItem={this.deleteItem.bind(this, i)}
      editItem={this.editItem.bind(this, i)}
    />
  };

  render() {
    const { list } = this.props;
    return (
      <div className="ToDo">
        <img className="Logo" src={Logo} alt="React logo" />
        <AppBar />
        <div className="ToDo-Container">

          <div className="ToDo-Content">
            {list.map(this.eachToDo)}
          </div>

          <div>
            <Popover
              toDoValue={this.state.todo}
              titleValue={this.state.title}
              titleOnChange={this.handleTitleInput}
              toDoOnChange={this.handleTodoInput}
              addHandler={this.createNewToDoItem}
            />
          </div>
        </div>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    list: state.list
  }
}

const mapDispatchToProps = dispatch => {
  return {
    deleteTodoItem: id => {
      dispatch({ type: "DELETE_TODO", id: id });
    },
    createTodoItem: (title, todo) => {
      dispatch({ type: "CREATE_TODO", title: title, todo: todo });
    }
  };
};

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

Note that "list" array from state is not really used, bacause I have initial list state in Redux state here( it may be implemented bad, but it is anyway):

const initState = {
    list: [
        {
            title: 'Cup cleaning',
            todo: "Wash and take away the Kurzhiy's cup from WC"
        },
        {
            title: 'Smoking rollton',
            todo: 'Do some rollton and cigarettes'
        },
        {
            title: 'Curious dream',
            todo: 'Build a time machine'
        }
    ],
};

const rootReducer = (state = initState, action) => {

    switch (action.type) {

        case "DELETE_TODO":
            let newList = state.list.filter((todo, index) => action.id !== index)
            return {
                ...state,
                list: newList
            }

        case "CREATE_TODO":
            const title = action.title;
            const todo = action.todo;

            let createdList = [
                ...state.list,
                {
                    title,
                    todo
                }
            ]
            return {
                ...state,
                list: createdList
            }

        default:
            return state;

    }
}

export default rootReducer;

So, now I need some help - if I understand everything right, my list state should now be stored inside MongoDB database. But currently it is in Redux, how should I switch from current state implementation to MongoDB properly ?

Also I understand that my MongoDB implementation is far from perfection, I'm just newbie to this, but I need to solve following problems: 1) I tried to get all todos from database in ComponentDidMount method and save it in array, but console.log always show that array is empty smth definitely wrong there. 2) Also connection with database is not really set up, because in general I can only add todos to database, but delete or edit functions does not work, because I'm little stuck about how to implement this index stuff, should I use ObjectId property from MongoDB or should I pass indexes from my main component to database, and how ?

Also any global recommendations regarding proper mongodb implementaion and suggestions or fixes to my code will be greatly appreciated :)

13
  • 1
    console.log prints an empty array inside componentDidMount because you call before the fetch actually finished. Try to console.log res.data inside the second then callback Commented Oct 18, 2018 at 18:03
  • 1
    Also there is no such option as new for findOneAndUpdate. You probably need to use upsert Commented Oct 18, 2018 at 18:09
  • 1
    As far as you use redux I would suggest to dispatch an action like FETCH_TODOS_SUCCESS with the data you receive from your API and to save this data in your redux state. Commented Oct 18, 2018 at 18:49
  • 1
    Also, as far as I've observed the Todo models differ on the server and on client (client - {title: '', todo: ''}, server - {titleText: '', todoText: ''}) you need to map it inside reducer list: action.data.map({titleText, todoText} => ({title: titleText, todo: todoText}). Or you can just make it similar on both server and client Commented Oct 18, 2018 at 18:54
  • 1
    Also your edit/delete endpoints do not work because they expect req.params.todoId which you are not sending. You can generate it on todo creation using any random id generator npm package you wish - const todo = new Todo(); todo.todoId = randomIdGenerator() Commented Oct 18, 2018 at 19:02

1 Answer 1

1

It's not res.data but res that you should inject in your state. res.data is undefined so it won't update the state.list.

componentDidMount = () => {
    fetch("/api/todos")
      .then(data => data.json())
      .then(jsonData => {
        console.log('jsonData --', jsonData)
        console.log('jsonData.data is empty!', jsonData.data)
        this.setState({ list: jsonData })
      });
  };

1- To be able to update, you're sending an id. You may create id's in your db if that's the way you want to find your todos.

Please note that _id is different from id.

_id mongodb's ObjectId, it is not of type integer but of type ObjectId.

id is just a regular field that you created that is called id.

NB: Your req.params.todoId is an integer. While ObjectId is of type ObjectId! So you won't be able to query one with the wrong type.

var todoSchema = new Schema({
  id: Number,
  titleText: String,
  todoText: String
});

2- Get your todo and update it thanks to the id. If it does not exist, it will be created thanks to the upsert option. Don't forget to cast in order to match your schema. title: req.body.title won't work because you defined it as titleText in your schema.

// updates todo with id
router.put('/:todoId', function (req, res) {
  const data = {
    titleText: req.body.title,
    todoText: req.body.todo
  }


  Todo.findOneAndUpdate(
    { id:  req.params.todoId }, // the query
    { $set: data }, // things to update
    { upsert: true } // upsert option
    ).then((todo) => res.json(todo))
    .catch((error) => res.send(error))
})
Sign up to request clarification or add additional context in comments.

8 Comments

Asten, yes, it works now :) _id property also writes to the list, now I think should I use this id to implement deleting and editing features with db, or it is better to use some own id's ... P.S. Just saw your edit regarding id's, I will try your suggestions :)
Please refer to this question for id's. Personnaly I use my own that are incremental. I also use slugs, keys and so on.
@Wonderio619 I updated my answer, normally you got everything you need to reach the next step ;) Please don't forget to mark as answer and/or upvote if this solves your issue.
Asten, one more question ( I hope ) for you, same as for Volodymyr - so I can generate id's for newly created todos in database, but how I will send corresponding id's from my component functions ? I mean from editItem or deletedItem functions in ToDo class.
Everywhere you have defaultToDoValue, you need to add that id as well as an argument. Sorry but this change propagates everywhere in the app. I can't show you that just here. However, I can tell that your teacher provides a _id so I would use that approach at first. You'll have to change handleOnSave = event to handleOnSave = (event, todoID) probably.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.