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

时间:2019-08-12 02:50:34

标签: javascript reactjs events material-ui

我正在构建一个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>
  );
}

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

该代码当前有效,但是新注释仅在重新加载页面上显示,提交后不会立即显示。

3 个答案:

答案 0 :(得分:1)

这里有个主意:

您应该在“帖子”中对状态变量发表评论。

const[comments, setComments] = useState([]);这样。

您可以在SendComment中接收名为onCommentSent的道具。 在您的代码中,发送注释时,您将执行onCommentSent();

因此,在“帖子”中,发送评论后,您将重新加载评论数据并使用comments将其设置为setComments(newData)。 重新加载状态后,评论将重新提取。


一个更好执行的想法是不要在每个评论POST请求上检索所有评论,您可以知道状态下一次获取服务器的信息,可以动态更新状态变量comments中的数据。

希望有帮助!

答案 1 :(得分:1)

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

我看到的最简单的解决方案如下:一旦您同时拥有PostSendComment的父组件(Comments),就可以将所有逻辑移入其中。除了将注释保存在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>
  );
}

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

Comments.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中显示内容和标题

答案 2 :(得分:0)

我已经创建了一个可行的解决方案,但是它显然很丑陋,并且绝对让我觉得自己是蛮力的,而不是像React那样做:

我提取了将注释提取到其自身函数中的逻辑。在效果期间以及handleSubmit函数内部调用此函数。

Comments组件现在是SendComment的子组件(从组织的角度来看这毫无意义)

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

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

  useEffect(() => {
    getComments(props.posts_id);
  }, [props.posts_id]);

  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) {
      getComments(props.posts_id);
    }
  }

  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>
      <Grid container justify="space-left">
        <Grid item justify="flex-start">
          <Comments comments={comments} />
        </Grid>
      </Grid>
    </>
  );
}