简体   繁体   中英

How can I update component in React after adding a new item into the database, using react hooks?

I want to update the component after adding a new comment which gets stored into the backend and updates the previously displayed component with the new one which has a new comment.

Here is the Page.js which loads a single post/article and its comments:

 import React ,{useState, useContext, useEffect}from 'react'; import {useHistory,Redirect, useParams} from 'react-router-dom' import axios from 'axios' import '../CSS/Page.css' import CreateComment from './CreateComment'; const Page=()=>{ const [show,setShow]=useState({ commentBox:false }) const [post,setPost] = useState( {id:'', username:'', title:'',body:'',date:'',comments:[{}] }) let {postTitle}=useParams() useEffect(() => { axios.get(`http://localhost:2000/apiEndpoint/singlePost/${postTitle}`,{withCredentials:true},{ headers: { 'Content-Type': 'application/json' } }).then((res)=>{ console.log(res.data) const postS=res.data setPost({...post,id:postS._id, username:postS.username, title:postS.title, body:postS.body,comments: postS.comments}) return; }).catch((err)=>{ console.log([err]) }) },[]); const handleCommentButton=(e)=>{ setShow({ ...show, commentBox:!show.commentBox }); } return ( <div className='postContainer'> <div className='singlePostcontainer'> <div className='singlePost' > <h1>{post.title}</h1> <hr/> <p>{post.body} </p> {post.id} <hr/> <h5>Comments:<button className='btnCom' onClick={handleCommentButton}>{show.commentBox?'Close':'Add Comment'}</button></h5> {show.commentBox?(<CreateComment post={post} />):''} {post.comments.map(comment=>{ const commentID=comment._id return( <div className='comment' key={commentID}> <h3>{comment.body}</h3> <h6>By: {comment.creater}</h6> </div> ) })} </div> </div> </div> ) } export default Page

Here is the CreateComment.js component which has a form and post request to the database:

 import React, { Component,useState, useEffect, lazy } from 'react'; import Cookies from 'js-cookie'; import { Link, Redirect } from 'react-router-dom'; const axios = require('axios').default; const CreateComment=(props)=>{ var commentStr={ body:'' } const [comment, setComment] = useState(commentStr); const handleSubmitA=(e)=>{ e.preventDefault() console.log('This is the id:',props.post.id) axios.post(`http://localhost:2000/apiEndpoint/CREATE/comment/${props.post.id}`,{ body:comment.body }, {withCredentials:true},{ headers: { 'Content-Type': 'application/json' }}).then(res=>{ console.log(res); }) } const handleChangeA=(e)=>{ const {name,value}=e.target setComment({ ...comment, [name]: value }); } return( <div className='commentContainer'> <form onSubmit={handleSubmitA}> <label>Enter Comment</label> <textarea name="body" onChange={handleChangeA}></textarea> <button>Submit</button> </form> </div> ) } export default CreateComment

I'm able to add component successfully and it is also posting the comment to the backend database also. But it only shows on the page if I press reload.

I removed the empty array from inside of useEffect hook. It gives me an infinite loop but does the work correctly. However as that is not a good practice and it takes up local storage resources, how can I perform the same task but without the infinite loop?

You might have a stale closure. https://dmitripavlutin.com/react-hooks-stale-closures/

Try this:

setPost(prevState => ({
            ...prevState,
            id: postS._id,
            username: postS.username,
            title: postS.title,
            body: postS.body,
            comments: postS.comments
        }))

You are actually not updating the post after posting a comment.

Simple

An easy solution would be to have <CreateComment /> accept a callback that can signal the parent that a new comment is available. The parent could then decide what to do with that information. You could trigger a refetch of the post to get all other comments and state updates that might have been summited while the user was working on their comment.

const CreateComment = (props) => {
  const onSubmit = (e) => {
    e.preventDefault();
    console.log('This is the id:', props.post.id)
    axios.post(`http://localhost:2000/apiEndpoint/CREATE/comment/${props.post.id}`, {
      body: comment.body
    }, {
      withCredentials: true
    }, {
      headers: {
        'Content-Type': 'application/json'
      }
    }).then(res => {
      props.onCommentCreated();
    });
  };
};

If your api returns a comment, you could instead use props.onCommentCreated(res.data) to pass the comment up to the parent. You would then not need to refetch since the parent would just push it into their comments state slice.

Fancy

If you want to get really fancy, you might consider completely removing the logic of handling posts/comments from the components and stick it all into a hook. This makes your logic reusable. Its also easier to reason about since the usePost hook has a very well defined api and all of the state wrangling is happening behind the scene. I didnt test this at all so you will have to adjust this if you go this route.

const EMPTY_POST = {
  id: '',
  username: '',
  title: '',
  body: '',
  date: '',
  comments: [{}]
}

const getPost = (title) => {
  return axios.get(`http://localhost:2000/apiEndpoint/singlePost/${title}`, {
    withCredentials: true
  }, {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => res.data);
};

const postComment = (id, content) => {
  return axios.post(`http://localhost:2000/apiEndpoint/CREATE/comment/${id}`, {
    body: content
  }, {
    withCredentials: true
  }, {
    headers: {
      'Content-Type': 'application/json'
    }
  })
}

const usePost = () => {
  const [post, setPost] = useState(EMPTY_POST);
  const {
    title
  } = useParams();

  const findPost = (title) => {
    getPost(title)
      .then((res) => {
        const postS = res.data;

        setPost({
          ...post,
          id: postS._id,
          username: postS.username,
          title: postS.title,
          body: postS.body,
          comments: postS.comments
        })
      })
      .catch((e) => {
        console.log(":(", e);
      });
  };

  useEffect(() => {
    findPost(title)
  }, []);

  const createComment = (id, comment) => {
    postComment(id, comment)
      .then(() => findPosts(title)); // refetch?
      
    postComment(id, comment)
        .then((result) => {
          setPost({ ...post, comments: [...post.comments, result]}));
        }); // just include it in.
  }

  return {
    post,
    createComment
  };
}

So now that your post logic is in a single hook, you could just use that as in your components. Here is an abbreviated version that shows a possible implementation.

const Page = () => {
  const { post, createComment } = usePost();

  return (
    ...
    {show.commentBox && <CreateComment post={post} onSubmit={createComment } />}
    ...
};

const CreateComment = (props) => {
  const { post, onSubmit } = props;
  const [comment, setComment] = useState(commentStr);

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(post.id, comment);
  };

  return (...);
};

Note

As an aside, setting state in a promise without checking for the component mount state will give you warnings in the console if the component dismounts while a pending promise is active that would trigger a state updated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM