1

trying to load youtube comments into a infinite load component (using a npm for it)

the mess happens due to the fact the infinite load component is child of parent Accordion component (from react-bootstrap), and what I'm trying to achieve is fetching with useSWR only if Accordion gets clicked (opened).

What I tried is to use useSWR conditional, so that only fetches when state "show" is true, which is being set inside function:

const showComments = () => {
    setShow(true)
    if (comments) {
      setCommChunks(_.chunk(comments.comm, 10))
      setCommList(commChunks[counter])
    }
  }

called on Accordion.Toggle onClick event.

But what happens is I can only show the comments after I click the Accordion twice, why is that?

My code is:

import { useState, useEffect } from 'react'
import { Row, Col, Button, Accordion } from 'react-bootstrap'
import * as _ from 'lodash'
import useSWR from 'swr'
import { MdUnfoldMore } from 'react-icons/md'
import InfiniteScroll from "react-infinite-scroll-component"
import Comments from './Comments'

const siteurl = process.env.NEXT_PUBLIC_SITE_URL

export default function VideoComments({ video }){
    
  const [show, setShow] = useState(false)
  const [counter, setCounter] = useState(0)
  const [commList, setCommList] = useState(null)
  const [commChunks, setCommChunks] = useState([])

  const showComments = () => {
    setShow(true)
    if (comments) {
      setCommChunks(_.chunk(comments.comm, 10))
      setCommList(commChunks[counter])
    }
  }

  const fetcher = (...args) => fetch(...args).then(res => res.json())
  const { data: comments, error } = useSWR(show ? `${siteurl}/api/c/${video.id}` : null, fetcher)
  
  // useEffect(() => {
  //   if (comments) {
  //     commChunks = _.chunk(comments.comm, 10)
  //     setCommList(commChunks[counter])
  //   }
  // },[comments])

  const fetchMoreData = () => {
    const newCounter = counter + 1;

    // loaded all, return
    if (commChunks[newCounter] === undefined || commChunks[newCounter] == null) {
        return;
    }

    const newCommList = [
        ...commList,
        ...commChunks[newCounter]
    ]
    setCommList(newCommList)
    setCounter(newCounter)
  }

  return (
    <div>
      <Accordion>
        <Row>
          <Col xs={12}>
            <Accordion.Toggle as={Button} onClick={() => {showComments()}} variant="link" eventKey="0"><div><span>Comments</span></div></Accordion.Toggle>
          </Col>
        </Row>
        <Accordion.Collapse eventKey="0">
          <div id="commentsBox" style={{maxHeight: '300px', overflowY: 'auto'}}>
            <Col xs={12}>
              {commList &&
                <InfiniteScroll
                    dataLength={commList.length}
                    next={fetchMoreData}
                    hasMore={true}
                    scrollableTarget="commentsBox"
                >
                  <Comments data={commList} />
                </InfiniteScroll>
              }
            </Col>
          </div>
        </Accordion.Collapse>
      </Accordion>
    </div>
  );
}

EDIT: as suggested below I reactivated useEffect, but it still needs two clicks of the Accordion

const showComments = () => {
    setShow(true)
    if (comments) {
      setCommChunks(_.chunk(comments.comm, 10))
      setCommList(commChunks[counter])
    }
  }

  const { data: comments } = useSWR(show ? `${siteurl}/api/c/${video.id}` : null, fetcher)

  useEffect(() => {
    if (comments) {
      setCommChunks(_.chunk(comments.comm, 10))
      setCommList(commChunks[counter])
    }
  },[comments])
m4tt
  • 825
  • 1
  • 11
  • 28

2 Answers2

2

The issue is in your useEffect, calling setCommList(commChunks[counter]) right after modifying commChunks state won't have the updated value. Setting state in React is an asynchronous operation (see React setState not updating immediately).

You should save the comments in a block-scoped variable and use that to update both states consecutively.

useEffect(() => {
    if (comments) {
        const commentsChunks = _.chunk(comments.comm, 10)
        setCommChunks(commentsChunks)
        setCommList(commentsChunks[counter])
    }
}, [comments])
juliomalves
  • 42,130
  • 20
  • 150
  • 146
0

You commented the useEffect that handles the comments :

  // useEffect(() => {
  //   if (comments) {
  //     commChunks = _.chunk(comments.comm, 10)
  //     setCommList(commChunks[counter])
  //   }
  // },[comments])

What happens :

  1. You click the Accordion, showComments is called
  2. show is set to true, but because comments is undefined, commList and commChunks are not set
  3. the component re-renders, now useSWR can use the url to fetch data
  4. the component re-renders when the fetching si done, now comments contains the data
  5. You click the Accordion the second time, showComments is called
  6. show is set to true, this time commList and commChunks are set
  7. the component re-renders with InfiniteScroll and Comments
Mohamed Ramrami
  • 12,026
  • 4
  • 33
  • 49
  • I commented it because was a test, it didn't work with it either, I also tried to get rid of setShow and use the function showComments as a trigger directly in useSWR as their docs example does, but it's the same, what you suggest? – m4tt Feb 03 '21 at 11:27
  • I tried to reactivate useEffect as you see it (but with setCommChunks as the function does), and using the showComments() directly getting rid of "show" state, it works in the sense that it shows comments on first click, the problem is it calls useSWR on page load, not on Accordion click – m4tt Feb 03 '21 at 11:58
  • Leave the "show" state like it was and `useSWR(show ? ` – Mohamed Ramrami Feb 03 '21 at 12:27
  • Can you provide the example in https://codesandbox.io/ ? – Mohamed Ramrami Feb 03 '21 at 12:29
  • updated, using show and useEffect as suggested, still needs two clicks – m4tt Feb 03 '21 at 13:36