Как отсортировать комментарии, чтобы показать самые новые комментарии сверху, а старые — внизу в Express или React

#node.js #reactjs #express

#node.js #reactjs #экспресс

Вопрос:

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

И где я должен использовать эту логику? в API (backend) или интерфейсе (React)? У комментария есть атрибут с именем createdAt новая дата вставляется в атрибут createdAt при каждом создании комментария.

Вот как я извлекаю событие с комментариями в моем серверной части Express

 router.get("/:id/eventcomments", async (req, res) => {
  const event = await Event.findById({ _id: req.params.id }).populate(
    "eventcomments"
  );
  res.json(event);
});
  

и вот модель схемы для комментариев.

 const eventcommentSchema = new mongoose.Schema({

description: {
    type: String,
    required: true
},
name: {
    type: String,
    required: true
},
createdAt: { 
    type: Date, 
    required: true, 
    default: Date.now
},
  event: { type: Schema.Types.ObjectId, ref: 'Event'}
})
  

Вот мой компонент

 export default function EventAndComments(props) {
    const EventComment = (props) => (
     <CardContent>
       <Typography variant="body2" color="textSecondary" component="p">
         {props.comment.name}
       </Typography>
       <Typography variant="body2" color="textSecondary" component="p">
        {props.comment.description}
       </Typography>
    </CardContent>
  );

  const theme = useTheme();
  const [events, setEventData] = useState([]);
  const [comments, setCommentData] = useState([]);

  const useStyles = makeStyles((theme) => ({
    root: {
      maxWidth: 550,
    },
    media: {
      height: 0,

      paddingTop: "86%", // 16:9
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
   },
   expand: {
     transform: "rotate(0deg)",
     marginLeft: "auto",
     transition: theme.transitions.create("transform", {
      duration: theme.transitions.duration.shortest,
     }),
   },
   expandOpen: {
    transform: "rotate(180deg)",
   },
   avatar: {
     backgroundColor: red[500],
   },
  }));

  const classes = useStyles();
  const [expanded, setExpanded] = React.useState(false);

  const handleExpandClick = () => {
    setExpanded(!expanded);
  };

  useEffect(() => {
    axios
    .get(
      "http://localhost:9000/events/"  
        props.match.params.id  
       "/eventcomments"
     )

     .then((response) => {
       setEventData(response.data);
    })

    .catch(function (error) {
      console.log(error);
    });
  }, []);
 const onPageLoad = () => {
  axios
  .get(
    "http://localhost:9000/events/"  
      props.match.params.id  
      "/eventcomments"
  )

  .then((response) => {
    setCommentData(response.data.eventcomments);
  })
  .catch(function (error) {
    console.log(error);
  });
};
useEffect(() => {
 onPageLoad();
}, []);


 const nowIso = new Date();
 const getTitle = (startDateTs, endDateTs) => {
  const now = Date.parse(nowIso);

  if (endDateTs <= now) {
    return "Started:"   " "   moment(startDateTs).format("LLLL");
  }

  if (startDateTs < now amp;amp; endDateTs > now) {
    return "Live:"   " "   moment(startDateTs).format("LLLL");
  }

    return "Starting:"   " "   moment(startDateTs).format("LLLL");
  };

  const getEnded = (startDateTs, endDateTs) => {
   const now = Date.parse(nowIso);

  if (endDateTs <= now) {
    return "Ended:"   " "   moment(startDateTs).format("LLLL");
  }

 if (startDateTs < now amp;amp; endDateTs > now) {
   return "Will End:"   " "   moment(startDateTs).format("LLLL");
 }

  return "Ends:"   " "   moment(startDateTs).format("LLLL");
};

const [eventDescription, setEventComment] = React.useState("");
const [name, setName] = React.useState("");

const handleChange = (parameter) => (event) => {
  if (parameter === "name") {
    setName(event.target.value);
  }
  if (parameter === "description") {
    setEventComment(event.target.value);
  }
};

const onSubmit = useCallback(
(e) => {
  e.preventDefault();

  axios
    .post(
      "http://localhost:9000/events/"  
        props.match.params.id  
        "/eventcomment",
      { name: name, description: eventDescription }
    )

    .then(function (response) {
      onPageLoad();
    })

    .catch(function (error) {
      console.log(error);
    });
  },
  [props.match.params.id, name, eventDescription]
 );

 let eventCommentList = comments.map((comment, k) => (
   <EventComment comment={comment} key={k} />
 ));

 return (
   <Grid
    container
    spacing={0}
    direction="column"
    alignItems="center"
    justify="center"
    style={{ minHeight: "100vh" }}
   >
   <Card className={classes.root}>
    <h3
      style={{
        background: "   #800000",
        color: "white",
        textAlign: "center",
      }}
      className={classes.cardheader}
    >
      {events.title}
    </h3>
    <CardHeader
      avatar={
        <Avatar aria-label="recipe" className={classes.avatar}>
          CB
        </Avatar>
      }
      action={
        <IconButton aria-label="settings">
          <MoreVertIcon />
        </IconButton>
      }
      title={getTitle(
        Date.parse(events.startingDate),
        Date.parse(events.closingDate)
      )}
      subheader={getEnded(
        Date.parse(events.startingDate),
        Date.parse(events.closingDate)
      )}
      style={{ background: "#DCDCDC" }}
    />
    <CardMedia
      className={classes.media}
      image={events.eventImage}
      title="Paella dish"
    />
    <CardContent>
      <Typography variant="body2" color="textSecondary" component="p">
        {events.description}
      </Typography>
    </CardContent>
  </Card>

  <form
    className={classes.root}
    noValidate
    autoComplete="off"
    onSubmit={onSubmit}
  >
    <FormControl>
      <InputLabel htmlFor="component-simple">Name</InputLabel>
      <Input
        id="component-simple"
        value={name}
        onChange={handleChange("name")}
        label="Name"
      />
    </FormControl>

    <FormControl variant="outlined">
      <InputLabel htmlFor="component-outlined">Description</InputLabel>
      <OutlinedInput
        id="component-outlined"
        value={eventDescription}
        onChange={handleChange("description")}
        label="Description"
      />
    </FormControl>
    <Button type="submit" fullWidth variant="contained" color="primary">
      Create Comment
    </Button>
  </form>
  <CardContent>{eventCommentList}</CardContent>
  </Grid>
 );
 }
}
  

Я добавил все свои коды.

Комментарии:

1. Вы можете сделать заказ на клиенте или сервере, сервер будет предпочтительнее, поскольку результаты могут быть кэшированы. поиск в mongodb sort

2. Я использовал Mongo DB sort, но это не сработало. @Samuel

3. @Chuckwuma — что вы имеете в виду, сортировка MongoDB не работает? Пожалуйста, по крайней мере, изучите документы и попытайтесь их разрешить — SO не является обучающим сервисом.

Ответ №1:

Вы можете sort использовать такую функциональность в mongoose

 const event = await Event.findById({ _id: req.params.id }).populate("eventcomments").sort({createdAt:-1});
  

Подробнее об этом вы можете прочитать здесь

Обновить

Попробуйте передать это как опцию

 const event = await Event.findById({ _id: req.params.id },null,{sort:{createdAt:-1}}).populate("eventcomments")
  

Комментарии:

1. Я пробовал этот пример раньше, но он не сработал, я также пробовал ваш код, но он тоже не сработал. @Shivam

2. это все еще не работает, я действительно не знаю почему. @Shivam

3. Команда сортировки применяется к запрашиваемой коллекции. Вы пытаетесь отсортировать вложенные документы в другой коллекции. Смотрите мой ответ для получения более подробной информации.

Ответ №2:

Строго говоря, вам на самом деле не нужно поле createdAt. Идентификаторы объектов MongoDB кодируют время создания объекта. Из документов MongoDB:

сортировка по полю _id, в котором хранятся значения ObjectId, примерно эквивалентна сортировке по времени создания.

Смотрите: https://docs.mongodb.com/v3.0/reference/bson-types/#objectid

Вы также можете извлечь временную метку из ObjectId с помощью ObjectId.getTimestamp().

Однако, если вы пытаетесь отсортировать вложенные документы, вы не сможете сделать это в запросе к родительскому. Вам придется использовать конвейер агрегации, поскольку команда sort будет сортировать ваши события, а не ваши комментарии.

Однако в вашем случае использования вам было бы лучше сохранить весь документ комментариев в документе события. Каждый раз, когда кто-то публикует комментарий, он должен попадать в массив комментариев к событию, где он будет находиться уже отсортированным. Это избавит ваш сервер от необходимости выполнять ТОННУ поисковых запросов и вносить изменения в документ каждый раз, когда кто-то запрашивает событие. То, как вы все настроили сейчас, ваша база данных будет получать запросы на чтение (1 * количество комментариев) каждый раз, когда кто-то просматривает событие. Затем эти комментарии нужно будет вставить в документ вашего события. Это нормально в системах реляционных баз данных; Это то, для чего они предназначены. Это не так хорошо работает в базах данных NoSQL.

Это называется шаблоном встроенного документа. И если вы хотите использовать MongoDB, то это очень хорошая идея ознакомиться с ним. Вот подробная ссылка, где вы можете найти больше информации об этом шаблоне и его полезности в системе NoSQL:

https://docs.mongodb.com/manual/tutorial/model-embedded-one-to-many-relationships-between-documents/

Комментарии:

1. Я обязательно изучу это, чтобы узнать больше о том, как сделать это лучше. @Charles

Ответ №3:

Мне удалось заставить это работать с этим кодом.

 router.get("/:id/eventcomments", async (req, res) => {
  Event.findById({ _id: req.params.id })
  .populate("eventcomments", "_id name description createdAt", 
    null, {
    sort: { createdAt: -1 },
  })
 .exec(function (error, results) {
   res.json(results);
 });
});
  

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