javascript - 如何在React中的事件发生后触发效果

标签 javascript reactjs events material-ui

我正在构建一个 React 应用程序,它基本上加载博客文章,并为每篇文章附加评论。

呈现博客文章时,也会获取该博客文章的评论。我还有一个允许提交评论的组件。

当单击提交按钮时,我希望评论刷新其数据源,并立即显示新评论。 我如何向我们的评论组件发送某种事件,告诉它发送另一个获取请求?

看来这个问题的核心是这样的:

如何惯用地将事件发送到其他将触发效果的 React 组件?

编辑 - 一般解决方案:

  1. 将状态提升到最近的组件
  2. 创建一个包含状态更新函数的回调函数。将此回调作为 prop 传递给将触发事件的组件。当事件发生时,在处理程序中运行回调。

Post.js

import React from 'react';
import {useState, useEffect, useContext} from 'react';
import Markdown from 'markdown-to-jsx';
import Container from '@material-ui/core/Container';
import Typography from '@material-ui/core/Typography';
import SendComment from './SendComment';
import Comments from './Comments';
import {POST_URL} from './urls';
import UserContext from './UserContext';
//import CommentListContainer from './CommentListContainer';

export default function Post(props) {
  const user = useContext(UserContext);

  const [post, setPost] = useState({
    content: '',
    comments: [],
  });

  useEffect(() => {
    const UNIQUE_POST_URL = [POST_URL, props.location.state.id].join('/');

    const fetchPost = async () => {
      const result = await fetch(UNIQUE_POST_URL);
      const json = await result.json();
      setPost(json);
    };
    fetchPost();
  }, [props.location.state.id]);

  return (
    <div>
      <Container>
        <Typography
          variant="h4"
          color="textPrimary"
          style={{textDecoration: 'underline'}}>
          {post.title}
        </Typography>
        <Markdown>{post.content}</Markdown>
        {post.content.length !== 0 && (
          <div>
            <Typography variant="h4">Comments</Typography>
            <SendComment user={user} posts_id={props.location.state.id} />
            <Comments user={user} posts_id={props.location.state.id} />
          </div>
        )}
      </Container>
    </div>
  );
}

SendComment.js 组件

import React from 'react';
import TextField from '@material-ui/core/TextField';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import {COMMENT_SUBMIT_URL} from './urls';

export default function SendComment(props) {
  async function handleSubmit(e) {
    const comment = document.querySelector('#comment');

    // Skip empty comments
    if (comment.value === '') {
      return;
    }

    async function sendComment(url) {
      try {
        const res = await fetch(url, {
          method: 'POST',
          body: JSON.stringify({
            comment: comment.value,
            users_id: props.user.users_id,
            posts_id: props.posts_id,
          }),
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'Accept-Language': 'en-US',
          },
        });
        comment.value = '';
        return res;
      } catch (e) {
        console.log(e);
      }
    }
    const res = await sendComment(COMMENT_SUBMIT_URL);
    if (res.ok) {
      // Reload our comment component !
      // Here is where we want to send our "event"
      // or whatever the solution is
    }
  }

  return (
    <Grid container justify="space-evenly" direction="row" alignItems="center">
      <Grid item xs={8}>
        <TextField
          id="comment"
          fullWidth
          multiline
          rowsMax="10"
          margin="normal"
          variant="filled"
        />
      </Grid>
      <Grid item xs={3}>
        <Button variant="contained" color="primary" onClick={handleSubmit}>
          Submit
        </Button>
      </Grid>
    </Grid>
  );
}

评论.js

import React from 'react';
import {useState, useEffect} from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Divider from '@material-ui/core/Divider';
import {timeAgo} from './utils';
import {COMMENT_URL} from './urls';

export default function Comments(props) {
  const [comments, setComments] = useState({
    objects: [],
  });

  useEffect(() => {
    async function getComments(posts_id) {
      const filter = JSON.stringify({
        filters: [{name: 'posts_id', op: 'equals', val: posts_id}],
      });

      try {
        COMMENT_URL.searchParams.set('q', filter);

        const res = await fetch(COMMENT_URL, {
          method: 'GET',
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
          },
        });
        const json = await res.json();
        setComments(json);
      } catch (e) {
        console.log(e);
      }
    }
    getComments(props.posts_id);
  }, [props.posts_id]);

  const commentList = comments.objects.map(comment => (
    <ListItem key={comment.id} alignItems="flex-start">
      <ListItemAvatar>
        <Avatar alt={comment.users.name} src={comment.users.picture} />
      </ListItemAvatar>
      <ListItemText
        primary={`${comment.users.name} - ${timeAgo(comment.created_at)}`}
        secondary={comment.comment}></ListItemText>
      <Divider />
    </ListItem>
  ));

  return <List>{commentList}</List>;
}

此代码当前有效,但是新评论仅在页面重新加载时显示,而不是在提交后立即显示。

最佳答案

我认为您无法在没有任何额外逻辑的情况下发送此类事件。

我看到的最简单的解决方案如下:一旦您拥有 SendCommentComments 的父组件 (Post),你可以将所有逻辑移入其中。您可以向其传递一个回调,该回调将在用户按下按钮时触发,而不是将评论保存在 SendComment 中。然后评论将被发送到 Post 内的服务器。

要显示评论,您也可以在 Post 中获取它们,然后将其作为 props 传递给 Comments 。这样您就可以轻松更新评论,并且当用户提交新评论时不需要额外的请求。

也更喜欢使用受控组件(您在 SendComment 中有一个不受控的文本字段)

代码看起来像这样:

Post.js

export default function Post(props) {
  const user = useContext(UserContext);

  const [content, setContent] = useState('')
  const [title, setTitle] = useState('')
  const [comments, setComments] = useState([])

  const onNewComment = useCallback((text) => {
    // I'm not sure about your comment structure on server. 
    // So here you need to create an object that your `Comments` component 
    // will be able to display and then do `setComments(comments.concat(comment))` down below
    const comment = { 
      comment: text,
      users_id: user.users_id,
      posts_id: props.location.state.id,
    };
    async function sendComment(url) {
      try {
        const res = await fetch(url, {
          method: 'POST',
          body: JSON.stringify(comment),
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'Accept-Language': 'en-US',
          },
        });
        return res;
      } catch (e) {
        console.log(e);
      }
    }
    const res = await sendComment(COMMENT_SUBMIT_URL);
    if (res.ok) {
      setComments(comments.concat(comment));
    }
  }, [comments]);

  useEffect(() => {
    const UNIQUE_POST_URL = [POST_URL, props.location.state.id].join('/');

    const fetchPost = async () => {
      const result = await fetch(UNIQUE_POST_URL);
      const { content, comments, title } = await result.json();
      setContent(content);
      setComments(comments);
      setTitle(title);
    };
    fetchPost();
  }, [props.location.state.id]);

  return (
    <div>
      <Container>
        <Typography
          variant="h4"
          color="textPrimary"
          style={{textDecoration: 'underline'}}>
          {title}
        </Typography>
        <Markdown>{content}</Markdown>
        {content.length !== 0 && (
          <div>
            <Typography variant="h4">Comments</Typography>
            <SendComment user={user} onNewComment={onNewComment} />
            <Comments user={user} comments={comments} />
          </div>
        )}
      </Container>
    </div>
  );
}

SendComment.js

export default function SendComment(props) {
  const [text, setText] = useState('');
  const handleSubmit = useCallback(() => {
    // Skip empty comments
    if (comment.value === '') {
      return;
    }
    if(props.onNewComment) {
      props.onNewComment(text);
      setText('');
    }
  }, [props.onNewComment, text]);

  return (
    <Grid container justify="space-evenly" direction="row" alignItems="center">
      <Grid item xs={8}>
        <TextField
          id="comment"
          onChange={setText}
          fullWidth
          multiline
          rowsMax="10"
          margin="normal"
          variant="filled"
        />
      </Grid>
      <Grid item xs={3}>
        <Button variant="contained" color="primary" onClick={handleSubmit}>
          Submit
        </Button>
      </Grid>
    </Grid>
  );
}

评论.js

export default function Comments(props) {
  const commentList = props.comments.map(comment => (
    <ListItem key={comment.id} alignItems="flex-start">
      <ListItemAvatar>
        <Avatar alt={comment.users.name} src={comment.users.picture} />
      </ListItemAvatar>
      <ListItemText
        primary={`${comment.users.name} - ${timeAgo(comment.created_at)}`}
        secondary={comment.comment}></ListItemText>
      <Divider />
    </ListItem>
  ));

  return <List>{commentList}</List>;
}

UPD:更改了一些代码以在 Post.js 中显示内容和标题

关于javascript - 如何在React中的事件发生后触发效果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57455172/

相关文章:

javascript - 如何在单击时使用ajax在动态创建的div中加载内容

javascript - 启用/禁用由用户输入确定的复选框

javascript - 为什么输入类型 ='file' 不调度事件?

python - turtle 图形按键事件不重复

javascript - React TypeError : teams.映射不是函数

javascript - AngularJS:事件回调不会在谷歌地图原生 map 标记上触发

JavaScript innerHTML 包括 php

javascript - 如何根据 json 属性将项目绑定(bind)到 Controller

javascript - 当 props 和 state 没有改变时 React PureComponent 更新

reactjs - 在React应用程序中使用全局配置对象