如何在 React 中的事件之后触发效果

卢克·帕福德

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

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

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

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

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

编辑 - 一般解决方案:

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

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>;
}

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

亚历克斯·查辛

我不认为你可以在没有任何额外逻辑的情况下发送此类事件。

我看到的最简单的方法是:只要你有一个父组件(Post两个)SendCommentComments,你可以将所有的逻辑进去。SendComment您可以向它传递一个回调,而不是将评论保存在 中,该回调将在用户按下按钮时触发。然后将评论发送到服务器内部Post

要显示评论,您也可以获取它们Post,然后将其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>
  );
}

发送评论.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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在React中触发onBlur事件?

打开的窗口关闭后如何在React中触发事件

如何在React中的滚动事件上触发功能

React:当子组件中发生onClick事件时如何在父组件中触发事件

如何在React中的循环内触发'this'

如何在React / JSX中触发onSelectStart?

如何在React中动态附加事件?

当React中的值被代码更改时,如何在输入上触发onChange事件?

如何在react-testing-library中的单选按钮上触发更改事件?

如何确定哪个组件在React中触发事件处理程序?

如何在React Native中禁用图像淡入效果?

如何在react组件中应用渐变动画效果

React:如何在事件处理程序中调用react钩子?

如何在 JS 触发的事件上应用 CSS 过渡效果?

React:如何在React中重定向?

如何在 React 中触发兄弟组件的功能?

如何在React的子功能组件中触发动作?

如何在Ionic React 5中触发反向重定向?

您如何在React中触发文件下载?

useEffect 依赖触发器如何在 React 中工作?

如何在 React 中触发多个组件的函数?

如何在React中的回调上触发handleSubmit

如何在 react componentDidUpdate 方法中触发 redux 动作?

如何在react js中触发组件水平滚动的功能

如何在带有react-testing-library的react-select组件上触发change事件?

如何在React中安排useEffect中的事件

如何在React中的const中添加事件处理程序?

如何创建由事件触发的降雨效果?

如何在 React 的滚动事件中获取列表中心的元素?