Search code examples
reactjsaxiosreact-routerreact-bootstrap

When I use outlet in accordion, axios request in useEffect keeps executing


Because my outlet is placed in the accordion body of react-bootstrap, So when I enter the sub-route, the useEffect content of the sub-route is always executed. until the body of the accordion is fully opened,Wondering if there is a way to fix it.

The problem is that the outlet can't seem to be placed in accordion.body, I hope there is a solution, thank you! ! !

This is the parent route:

import React from 'react'
import { useParams, Link, Outlet } from 'react-router-dom'

import request from '../common/utils'

import { Spinner, Accordion } from 'react-bootstrap'

export default function ReposList() {
  let user = useParams()
  let [userData, setUserData] = React.useState(null)
  let [isLoading, setLoading] = React.useState(true)
  let [isPage, setPage] = React.useState(2)

  function getAPI(per_page, page) {
    return request({
      url: `users/${user.username}/repos`,
      params: {
        per_page,
        page
      }
    })
  }
//下拉加載
  function handleScroll(e) {
    if (e.target.clientHeight + parseInt(e.target.scrollTop)
      === e.target.scrollHeight) {
      getAPI(10, isPage).then((res) => {
        setUserData([...userData, ...res.data])
      })
      setPage(isPage + 1)
    }
  }

  React.useEffect(() => {
    const fetchData = async () => {
      const result = await getAPI(10, 1)
      setUserData(result.data)
      //console.log(res.data)
      setLoading(false)
      console.log(1)
    }
    fetchData()
  }, [])

  if (isLoading) {
    return (
      <Spinner animation="border" role="status">
        <span className="visually-hidden">Loading...</span>
      </Spinner>
    )
  }
//手風琴要包著map生成的item,不然item之間沒有對摺效果
  return (
    <div className='repos' onScroll={handleScroll} >
      <Accordion>
      {userData.map((data) => (
        // key要放在元素的最上方,不然會報錯。
          <Accordion.Item eventKey={data.id} key={data.id}>
            <Accordion.Header as={Link} to={data.name}>
              {data.name}{ '  '}{data.language == null? '': `/ ${data.language}`}
            </Accordion.Header>
            <Accordion.Body>
              <Outlet/>
            </Accordion.Body>
          </Accordion.Item>
      ))}
      </Accordion>
    </div>
  )
}

This is the sub route:

import React from 'react'
import { useParams } from 'react-router-dom'

import { Spinner , Table } from 'react-bootstrap'

import request from '../common/utils'

export default function Repo() {
  let user = useParams()
  let [userData, setUserData] = React.useState(null)
  let [isLoading , setLoading] = React.useState(true)
  //console.log(user)
 
  function getAPI(per_page,page) {
    return request({
      url: `repos/${user.username}/${user.repo}`,
      params:{
        per_page,
        page
      }
    })
  }

  React.useEffect(() => {
    const fetchData = async () => {
      const result = await getAPI()
      setUserData(result.data)
      //console.log(res.data)
      setLoading(false)
      console.log(1)
    }
    fetchData()
  }, [user])

   if (isLoading) {
    return (
      <Spinner animation="border" role="status">
        <span className="visually-hidden">Loading...</span>
      </Spinner>
    )
  }

  return(
      <Table striped bordered hover size="sm" 
      className='repoTable'>
      <thead>
      <tr>
            <th>full_name</th>
            <th>description</th>
            <th>stargazers_count</th>
            <th>url</th>
          </tr>
      </thead>
      <tbody>
          <tr>
            <td>{userData.full_name}</td>
            <td>{userData.description == null? 'null' : userData.description}</td>
            <td>{userData.stargazers_count}</td>
              <td>
                <a href={userData.svn_url }target="_blank">
                  Go!
                </a>
              </td>
          </tr>
        </tbody>
      </Table>
  )
}

Complete code and demo https://github.com/superrjohn/GithubRestApi

Hope someone can help, thanks!


Solution

  • Issue

    Trying to render multiple Outlet components in the mapped accordion bodies is a problem. ReposList as the parent route's component should only render a single Outlet for nested children routes. There's only the single "/user/:username/repos/:repo" route.

    Solution

    I suggest rendering the Repo component directly in the accordion body, passing the username and repo values as props.

    ReposList

    <Accordion>
      {userData.map((data) => (
        <Accordion.Item eventKey={data.id} key={data.id}>
          <Accordion.Header as={Link} to={data.name}>
            {data.name}
            {"  "}
            {data.language == null ? "" : `/ ${data.language}`}
          </Accordion.Header>
          <Accordion.Body>
            <Repo {...{ username, repo: data.name }} /> // Render Repo and pass props
          </Accordion.Body>
        </Accordion.Item>
      ))}
    </Accordion>
    

    Repos

    export default function Repo({ username, repo }) { // access props
      const [userData, setUserData] = React.useState(null);
      const [isLoading, setLoading] = React.useState(true);
    
      React.useEffect(() => {
        function getAPI(per_page, page) {
          return request({
            url: `repos/${username}/${repo}`,
            params: {
              per_page,
              page
            }
          });
        }
    
        const fetchData = async () => {
          const result = await getAPI();
          setUserData(result.data);
          setLoading(false);
        };
        fetchData();
      }, [repo, username]);
    
      if (isLoading) {
        return (
          <Spinner animation="border" role="status">
            <span className="visually-hidden">Loading...</span>
          </Spinner>
        );
      }
    
      return (
        <Table striped bordered hover size="sm" className="repoTable">
          <thead>
            <tr>
              <th>full_name</th>
              <th>description</th>
              <th>stargazers_count</th>
              <th>url</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>{userData.full_name}</td>
              <td>
                {userData.description == null ? "null" : userData.description}
              </td>
              <td>{userData.stargazers_count}</td>
              <td>
                <a href={userData.svn_url} target="_blank">
                  Go!
                </a>
              </td>
            </tr>
          </tbody>
        </Table>
      );
    }
    

    src/router/index.js

    Remove the nested route rendering the Repo component and add a trailing wildcard "*" matcher to the parent route.

    const routes = [
      {
        path: "/",
        element: <NavBarExample />,
        children: [
          {
            index: true,
            element: <Home />
          },
          {
            path: "user",
            element: <Home />,
            children: [
              {
                path: ":username",
                element: <UserName />,
                children: [
                  {
                    path: "repos/*",
                    element: <ReposList />,
                  }
                ]
              }
            ]
          },
          {
            path: "About",
            element: <About />
          },
          {
            path: "Contact",
            element: <Contact />
          }
        ]
      },
      {
        path: "*",
        element: <NoMatch />
      }
    ];
    

    Edit when-i-use-outlet-in-accordion-axios-request-in-useeffect-keeps-executing