1

I'm new using react hooks and i was struggling with the useEffect hook due to the following:

I have a simple comment board to post comments and below a list of the comments posted, and i want every time a new comment is made the component reloads to show it.

enter image description here

These commments are got it from an API in the useEffect first parameter, the problem is the second parameter (dependecies array), if i don't put this parameter causes an infinite loop, if i put an empty array it avoids the infinite loop but instead my component doesn't render new comments posted, and if i put the variable state "comments" causes an infinite loop too. So navigating in the internet i found "the solution" that is simply as add another variable state to pass it to the dependencies array and is modified when i call the function "makecomponent" and this way it refreshs the state forcing my component to re render:

export const ForumApp = () => {
    const [comment, setComment] = useState("")
    const [comments, setComments] = useState([])
    const [count, setCount] = useState(0)
 
    useEffect(() => {
        const getComments = async () => {
            try {
                const query = await axios.get("http://127.0.0.1:8000/comments")
                setComments(query.data.comments)
            } catch (error) {
                console.log(error);
            }
        }
        getComments()
    }, [count])

    const handleEnter = (e: KeyboardEvent) => {
        if (e.key === ENTER && !e.shiftKey) {
            e.preventDefault();
            makeComment();
        }
    };

    const makeComment = async () => {
        try {
            await axios.post("http://127.0.0.1:8000/comments/create", {
                time: "1:29",
                content: comment,
            })

            alert("Comment posted!")
            setCount((prevCount) => prevCount + 1)
            setComment("")
        } catch (error) {
            console.log(error)
        }
    };

    return (
        <div className="forum-app">
            <div className="comment-board">
                <h1>Post your comment</h1>
                <textarea
                    placeholder="Write something to us"
                    onChange={(e) => setComment(e.target.value)}
                    onKeyDown={handleEnter}
                    value={comment}
                ></textarea>
                <br />
                <button onClick={makeComment}>Comment</button>
            </div>
            <ol>
                {
                    comments.map(({_id, content}) => (
                        <CommentItem key={_id} content={content}/>
                    ))
                }
            </ol>
        </div>
    )
}

So basically my question is: is this the "correct" way to do it or does exist another cleaner way without declare another variable and only doing it with what i have?

3 Answers 3

2

The other cleaner way is likely to use redux & redux-thunk to dispatch an action to do the asynchronous fetching. When the data is fetched and the redux store is updated then the connected components rerender with the latest state from the store.

This is a lot of new moving parts to add to an app if it isn't already using redux. The important aspect here is the necessary additional "flag" to trigger another data fetch.

I think a tweak I'd recommend for your code to make it a bit more straight forward is to actually define a fetchData state that is toggled on and off depending on the status of fetching data. This allows you to also display any "loading" state while the data is being fetched.

export const ForumApp = () => {
    const [comment, setComment] = useState("")
    const [comments, setComments] = useState([])
    const [fetchComments, setFetchComments] = useState(true) // <-- for initial mount call
 
    useEffect(() => {
        const getComments = async () => {
            try {
                const query = await axios.get("http://127.0.0.1:8000/comments")
                setComments(query.data.comments)
            } catch (error) {
                console.log(error);
            } finally {
              setFetchComments(false); // <-- signify fetching complete
            }
        }

        fetchComments && getComments(); // <-- fetch comments if true
    }, [fetchComments])

    const handleEnter = (e: KeyboardEvent) => {
        if (e.key === ENTER && !e.shiftKey) {
            e.preventDefault();
            makeComment();
        }
    };

    const makeComment = async () => {
        try {
            await axios.post("http://127.0.0.1:8000/comments/create", {
                time: "1:29",
                content: comment,
            })

            alert("Comment posted!")
            setFetchComments(true); // <-- signify start fetching
            setComment("")
        } catch (error) {
            console.log(error)
        }
    };

    return (
        <div className="forum-app">
            <div className="comment-board">
                <h1>Post your comment</h1>
                <textarea
                    placeholder="Write something to us"
                    onChange={(e) => setComment(e.target.value)}
                    onKeyDown={handleEnter}
                    value={comment}
                ></textarea>
                <br />
                <button onClick={makeComment}>Comment</button>
            </div>
            <ol>
                {
                    fetchComments 
                      ? "...fetching comments" // <-- loading state
                      : comments.map(({_id, content}) => (
                        <CommentItem key={_id} content={content}/>
                    ))
                }
            </ol>
        </div>
    )
}
Sign up to request clarification or add additional context in comments.

10 Comments

So the cleanest way to do it is to use redux? or at least is what you recommend? I had been avoiding it but i guess it's time to learn it. Thanks for the code, for the moment is what i was lookin' for.
@AbrahamArreola I suppose in this case "clean" is a bit subjective. Personally I think either could be considered "clean". Using the way you had and in my answer it is clean in the sense that all the logic is contained in a single unit, is easy to read, and is a simple common pattern. Redux is a clean event driven pattern which allows you to abstract away and modularize common functions such as fetching data asynchronously and using a common app state. It sort of all depends on your app's needs and requirements. Cheers and good luck.
@Draw Reese Hello again, currently i learned and implemented redux to my app, but now i have a similar question: do i need also an aditional flag to trigger the fetch or can i achieve this with what i have? I tried passing the dispatch in the depency array but it didn't work. Cheers!
@AbrahamArreola If you've implemented redux into your app then the pattern here is to use asynchronous actions via a middleware (like redux-thunk). The fetch logic is moved to the asynchronous action. You wouldn't need use any extra flags to trigger the action as you can directly dispatch an asynchronous action (from a button click for example) to your store. If you need more assistance feel free to ask another question here on SO and (at) me here with a link to it, you'll at least possibly get more eyes on it more quickly.
i've implemented async actions with redux-thunk too and i know i can dispatch any action to the store, but with this i want to achieve the same thing i asked in this post: list all comments when component displays and update the list when a new comment is made. I actually did it putting the dispatch in the useEffect function but the component doesn't re render when i put a new comment. And now my question is, do i still needing a flag to control the data fetch or can i achieve this only with the redux methods?
|
0

i prefer to user and function as generator and give state as parameters then return my map component to make readable code


by this way you don't need to understand JSX to understand logic of code and more flexible for debug i think

export const ForumApp = () => {
const [comment, setComment] = useState("")
const [comments, setComments] = useState([])
const [count, setCount] = useState(0)

useEffect(() => {
    const getComments = async () => {
        try {
            const query = await axios.get("http://127.0.0.1:8000/comments")
            setComments(query.data.comments)
        } catch (error) {
            console.log(error);
        }
    }
    getComments()
}, [count])

const handleEnter = (e: KeyboardEvent) => {
    if (e.key === ENTER && !e.shiftKey) {
        e.preventDefault();
        makeComment();
    }
};

const makeComment = async () => {
    try {
        await axios.post("http://127.0.0.1:8000/comments/create", {
            time: "1:29",
            content: comment,
        })

        alert("Comment posted!")
        setCount((prevCount) => prevCount + 1)
        setComment("")
    } catch (error) {
        console.log(error)
    }
};
const commentGenerator = (data) => (data.map(({ _id, content }) => <CommentItem key={_id} content={content} />))

return (
    <div className="forum-app">
        <div className="comment-board">
            <h1>Post your comment</h1>
            <textarea
                placeholder="Write something to us"
                onChange={(e) => setComment(e.target.value)}
                onKeyDown={handleEnter}
                value={comment}
            ></textarea>
            <br />
            <button onClick={makeComment}>Comment</button>
        </div>
        <ol>
            {commentGenerator(comments)}
        </ol>
    </div>
)

}

Comments

0

so if you want to ad loading for it and even better you want have some lazy load component for lazy load to get better performance at loading page you can use react lazy and suspense to fetch data and load comment component when you data fetch successfully by this approach you don't any state or variable and also you can loading component and it's automatically Handel changing state of loading and load list of component.

import { Suspense, lazy } from 'react';

const CommentItem = lazy(() => import('./CommentItem'));

export const ForumApp = () => {

const getComments = async () => {
    try {
        const query = await axios.get("http://127.0.0.1:8000/comments")
        return query.data.comments.map((commentItem) => <CommentItem {...commentItem} />)
    } catch (error) {
        console.log(error);
        return []
    }
}


return (
    <div className="forum-app">
        <div className="comment-board">
            <h1>Post your comment</h1>
            <textarea
                placeholder="Write something to us"
                onChange={(e) => setComment(e.target.value)}
                onKeyDown={handleEnter}
                value={comment}
            ></textarea>
            <br />
            <button onClick={makeComment}>Comment</button>
        </div>
        <ol>
            <Suspense fallback={<span> loading comments...</span>}>
                {getComments()}
            </Suspense>
        </ol>
    </div>
)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.