Настройка правильной реализации MongoDB в приложении React

Итак, в настоящее время я работаю над приложением для стажировки React (MERN), которое представляет собой простой список дел с возможностью создавать, удалять и редактировать задачи. Я опубликую из него код, но вы также можете посмотреть полный код на GitHub: https://github.com/Wonderio619/magisale-internship-todo

Следующая задача - подключить мое приложение к MongoDB. У меня есть некоторый "шаблонный" код - я уже установил соединение с MongoDB, также есть маршрутизатор Express с такими маршрутами, как получение списка всех задач, отправка задачи в базу данных, обновление задачи с идентификатором, получение задачи с идентификатором:

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;

Эти маршруты используются при вызове соответствующих методов из приложения todo:

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.info(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);

Обратите внимание, что массив "list" из состояния на самом деле не используется, потому что здесь у меня есть начальное состояние списка в состоянии Redux (это может быть реализовано плохо, но это все равно):

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;

Итак, теперь мне нужна помощь - если я все правильно понимаю, состояние моего списка теперь должно храниться в базе данных MongoDB. Но в настоящее время он находится в Redux, как мне правильно переключиться с реализации текущего состояния на MongoDB?

Также я понимаю, что моя реализация MongoDB далека от совершенства, я новичок в этом, но мне нужно решить следующие проблемы: 1) Я попытался получить все задачи из базы данных в методе ComponentDidMount и сохранить их в массиве, но console.info всегда показывает, что массив пуст, что-то определенно неправильно. 2) Кроме того, соединение с базой данных на самом деле не настроено, потому что в целом я могу только добавлять задачи в базу данных, но функции удаления или редактирования не работают, потому что я немного застрял в том, как реализовать этот индексный материал, должен ли я использовать ObjectId свойство из MongoDB или мне следует передавать индексы из основного компонента в базу данных и как?

Также будут очень признательны любые глобальные рекомендации относительно правильной реализации mongodb и предложения или исправления в моем коде :)

console.info печатает пустой массив внутри componentDidMount, потому что вы вызываете его до фактического завершения выборки. Попробуйте console.info res.data внутри второго, затем обратный вызов

Volodymyr 18.10.2018 20:03

Также нет такой опции, как new для findOneAndUpdate. Вероятно, вам нужно использовать upsert

Volodymyr 18.10.2018 20:09

Владимир, хорошо, с console.info (res.data) внутри второй, тогда я получил массив с нужными значениями из базы данных, но что мне с ними делать дальше? Должен ли я передать их как-то в состояние? Похоже, я не могу просто добавить их в массив без разбора ...

Wonderio619 18.10.2018 20:35

Что касается использования redux, я бы посоветовал отправить такое действие, как FETCH_TODOS_SUCCESS, с данными, которые вы получаете из своего API, и сохранить эти данные в своем состоянии redux.

Volodymyr 18.10.2018 20:49

Кроме того, насколько я заметил, модели Todo различаются на сервере и на клиенте (клиент - {title: '', todo: ''}, сервер - {titleText: '', todoText: ''}), вам необходимо сопоставить их внутри редуктора list: action.data.map({titleText, todoText} => ({title: titleText, todo: todoText}). Или вы можете просто сделать его похожим как на сервере, так и на клиенте

Volodymyr 18.10.2018 20:54

Владимир, звучит хорошо, попробую. Я также думаю, могу ли я передать свой собственный идентификатор вместо свойства ObjectId mongodb?

Wonderio619 18.10.2018 20:56

Также ваши конечные точки редактирования / удаления не работают, потому что они ожидают req.params.todoId, который вы не отправляете. Вы можете сгенерировать его при создании задачи, используя любой пакет npm генератора случайных идентификаторов, который вы хотите - const todo = new Todo(); todo.todoId = randomIdGenerator()

Volodymyr 18.10.2018 21:02

Ваш запрос на удаление будет выглядеть примерно так - Todo.remove({ todoId: req.params.todoId }). Вы также можете установить todoId в качестве индекса для вашей схемы - todoId: {type: String, index: true}

Volodymyr 18.10.2018 21:10

Владимир, большое спасибо, со всеми предложениями от вас и Астена Миса мне будет намного проще правильно настроить базу данных)

Wonderio619 18.10.2018 21:15

Владимир, поэтому я могу сгенерировать идентификаторы для вновь созданных задач в базе данных, но как я отправлю соответствующие идентификаторы из моих функций компонента? Я имею в виду из функций editItem или deletedItem в классе ToDo. И что значит установить в качестве индекса для моей схемы?

Wonderio619 18.10.2018 23:24

про индексы - docs.mongodb.com/manual/indexes. Для удаления - передайте todoId как агрумент к this.deleteItem - deleteItem = {this.deleteItem.bind(this, item.todoId)}

Volodymyr 18.10.2018 23:35

Владимир, я скоро поговорю со своим наставником, но все еще думаю об этом индексе часами, теперь у меня есть значение ObjectId в моем списке из базы данных mongodb, поэтому, возможно, мне нужно как-то изменить поведение всего приложения, чтобы использовать это значение ObjectId в качестве индекса, так что у меня могут быть одинаковые индексы для работы с базой данных и для самого реагирующего приложения ... Или, если следовать вашему совету, передать todoId в качестве аргумента this.deleteItem, где todoId должен быть инициализирован в этом случае?

Wonderio619 19.10.2018 11:41

А как насчет изменения моей функции eachToDo следующим образом: eachToDo = (item, i) => {i = item._id; return <ToDoItem key = {this.randId ()} title = {item.title} todo = {item.todo} deleteItem = {this.deleteItem.bind (this, i)} editItem = {this.editItem.bind (this , i)} />}; Это действительно дает результат - правильный идентификатор теперь отправляется в db, но удаление в db фактически не работает, даже в приложении реакции.

Wonderio619 19.10.2018 12:58
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
13
155
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это не res.data, а res, которые вы должны ввести в свое состояние. res.data не определен, поэтому он не будет обновлять state.list.

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

1- Чтобы иметь возможность обновлять, вы отправляете идентификатор. Вы можете создать идентификаторы в своей базе данных, если хотите найти свои задачи таким образом.

Обратите внимание, что _id отличается от id.

Идентификатор объекта _id mongodb имеет не целочисленный тип, а тип ObjectId.

id - это просто созданное вами обычное поле, которое называется id.

NB: Ваш req.params.todoId - целое число. Хотя ObjectId имеет тип ObjectId! Таким образом, вы не сможете запросить запрос неправильного типа.

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

2- Получите задание и обновите его благодаря идентификатору. Если его нет, он будет создан благодаря опции опровергать. Не забудьте привести приведение, чтобы оно соответствовало вашей схеме. title: req.body.title не будет работать, потому что вы определили его как titleText в своей схеме.

// 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))
})

Астен, да, теперь работает :) Свойство _id тоже записывает в список, теперь я думаю, стоит ли использовать этот идентификатор для реализации функций удаления и редактирования с помощью db, или лучше использовать какой-то собственный идентификатор ... P.S. Только что видел ваши изменения относительно идентификаторов, я попробую ваши предложения :)

Wonderio619 18.10.2018 21:11

Пожалуйста, обратитесь к этот вопрос за идентификаторами. Лично я использую свои собственные, которые являются инкрементными. Еще я использую пули, ключи и прочее.

aquiseb 18.10.2018 21:15

@ Wonderio619 Я обновил свой ответ, обычно у вас есть все необходимое для перехода к следующему шагу;) Пожалуйста, не забудьте отметить как ответ и / или проголосовать, если это решит вашу проблему.

aquiseb 18.10.2018 21:22

Астен, еще один вопрос (надеюсь) к вам, как и к Владимиру - чтобы я мог сгенерировать идентификаторы для вновь созданных задач в базе данных, но как я отправлю соответствующие идентификаторы из моих функций компонента? Я имею в виду из функций editItem или deletedItem в классе ToDo.

Wonderio619 18.10.2018 23:31

Везде, где у вас есть defaultToDoValue, вам нужно добавить этот идентификатор, а также аргумент. Извините, но это изменение распространяется повсюду в приложении. Я не могу показать вам это только здесь. Однако я могу сказать, что ваш учитель предоставляет _id, поэтому я сначала воспользуюсь этим подходом. Возможно, вам придется сменить handleOnSave = event на handleOnSave = (event, todoID).

aquiseb 18.10.2018 23:58

Астен, я все еще думаю об этом, теперь на самом деле у меня есть значение ObjectId в моем списке из базы данных mongodb, поэтому, возможно, мне нужно как-то изменить поведение всего приложения, чтобы использовать это значение ObjectId в качестве индекса, чтобы я мог иметь равные индексы для работы с базой данных и для реагировать на само приложение ...

Wonderio619 19.10.2018 10:09

А как насчет изменения моей функции eachToDo следующим образом: eachToDo = (item, i) => {i = item._id; return <ToDoItem key = {this.randId ()} title = {item.title} todo = {item.todo} deleteItem = {this.deleteItem.bind (this, i)} editItem = {this.editItem.bind (this , i)} />}; Это действительно дает результат - правильный идентификатор теперь отправляется в db, но удаление в db фактически не работает, даже в приложении реакции.

Wonderio619 19.10.2018 12:57

@ Wonderio619, вы можете создать новый подробный вопрос по этому конкретному вопросу, потому что мы выходим за рамки этого. Вы можете отправить ссылку сюда, и я найду ее

aquiseb 19.10.2018 20:32

Другие вопросы по теме