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!
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.
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 />
}
];