我正在使用Next.js 13和App Routes开发一个应用程序。我遇到了一点障碍,需要一些帮助。
在我的一个页面上,我设置了一个网格来显示信息,顶部有一个搜索栏,允许用户搜索这些数据。问题是,每当有人开始在搜索栏中输入时,组件似乎会不必要地重新加载应用程序,导致多次读取。
gaia\app\(pages)\(secured)\vets\vacinas\page.tsx
'use client'
--imports
export default function Page() {
const [search, setSearch] = useState('');
const { authenticatedUser, authFlow } = useAuthenticatedUser();
const [filtroEscolhido, setFiltroEscolhido] = useState('2');
const router = useRouter();
const filtros = [
{ nome: "Todos", codigo: '1' },
{ nome: "Agendados", codigo: '2' },
{ nome: "Esse mês", codigo: '3' },
{ nome: "Mais antigos", codigo: '4' }
];
if (!authenticatedUser && authFlow.code !== 3) {
router.push('/');
} else {
return (
<Container>
<Stack gap={5}>
<Row className="text-center">
<Stack direction="horizontal" gap={5} className="justify-content-end">
<Col xs={5}>
<FloatingLabel controlId="inputBuscar" label="Buscar" >
<Form.Control type="text" placeholder="Buscar..." onChange={(e) => setSearch(e.target.value.toLowerCase())}></Form.Control>
</FloatingLabel>
</Col>
<AplicacaoModal />
</Stack>
</Row>
<Row>
<p className="display-6">Resultados da pesquisa</p>
<ButtonGroup>
{filtros.map((filtro, cod) => (
<ToggleButton
key={cod}
id={`filtro-${cod}`}
type="radio"
variant="info"
name="filtro"
value={filtro.codigo}
checked={filtro.codigo === filtroEscolhido}
onChange={(e) => setFiltroEscolhido(e.currentTarget.value)}
>
{filtro.nome}
</ToggleButton>
))}
</ButtonGroup>
</Row>
<Row>
<Suspense fallback={<Loading />}>
<Aplicacoes filtro={filtroEscolhido} buscar={search}></Aplicacoes>
</Suspense>
</Row>
</Stack>
</Container>
);
}
}
字符串
为了给予更多的上下文,我创建了“Aplicacoes”组件,它从我的Node后端API获取数据,并使用Array.map和.filter来应用过滤器并列出信息。
gaia\app\(pages)\(secured)\vets\(components)\aplicacao-grid.tsx
import { get_aplicacoes } from "@/app/_api/(secured)/aplicacoes-api";
import Table from "react-bootstrap/Table";
async function Aplicacoes({ filtro, buscar }: { filtro: string, buscar: string }) {
try {
console.log('acionando get_aplicacoes()');
const aplicacoes = await get_aplicacoes();
return (
<Table striped bordered hover responsive >
<thead>
<tr>
<th>Nome PET</th>
<th>Vacina aplicada</th>
<th>Dose</th>
<th>Data da aplicação</th>
<th>Valor cobrado</th>
</tr>
</thead>
<tbody>
{aplicacoes
.filter((vacina) => {
if (buscar === '') {
return vacina;
}
let filtrd_pet = vacina.nomePet.toLowerCase().includes(buscar);
return filtrd_pet || vacina.nomeVacina.toLowerCase().includes(buscar);
})
.filter((vacina) => {
if (filtro === '1') {
return vacina;
}
let hoje = new Date();
hoje.setHours(0, 0, 0, 0);
let data_partes = vacina.dataAplicacao.toString().split("/");
let data_vacina = new Date(+data_partes[2], +data_partes[1] - 1, +data_partes[0]);
if (filtro === '2' && data_vacina > hoje) {
return vacina;
}
if (filtro === '3' && (+data_partes[1] - 1 === hoje.getMonth() && +data_partes[2] === hoje.getFullYear())) {
return vacina;
}
if (filtro === '4' && (data_vacina < hoje)) {
return vacina;
}
})
.map((aplicacao) => (
<tr key={aplicacao._id}>
<td>{aplicacao.nomePet}</td>
<td>{aplicacao.nomeVacina}</td>
<td>{aplicacao.dose}</td>
<td>{aplicacao.dataAplicacao.toString()}</td>
<td>{aplicacao.valorCobrado}</td>
</tr>
))}
</tbody>
</Table>
);
} catch (error) {
console.log(`Erro no componente Aplicacoes ${error}`);
return null;
}
}
export {Aplicacoes};
型
如您所见,这是对我的Node后端API的多个请求。
x1c 0d1x的数据
随着时间的推移,idk如果这是相关的或不,但这里是我的Axios组件获取的数据。
gaia\app_API\(secured)\aplicacoes-API.tsx
'use client'
import instance_authenticated from "./axios-instance";
import { Aplicacao } from "@/app/_types/vets/IAplicacoes";
async function get_aplicacoes(): Promise<Aplicacao[]> {
const axiosResponse = await instance_authenticated.get('/diarioDeVacinas/get');
return axiosResponse.data;
}
export {get_aplicacoes};
型
提前感谢您的帮助!
我对Next.js还是个新手,所以我不太确定我在这里错过了什么。我已经通读了好几遍Next.js 13页的生命周期文档,但似乎无法弄清楚。
EDIT 1我正在使用App Router,这是我的文件夹结构。
的
编辑2
我使用next-auth来启用Google和Facebook等社交登录,但我有自己的身份验证提供程序和MongoDB数据库,由我的Node.js后端API管理。
gaia\app\layout.tsx
'use client'
---another imports
import type { Metadata } from 'next'
import { MenuAccess, MenuNavigation } from './_components/nav';
import { UserProvider } from './_components/auth';
import { SessionProvider } from "next-auth/react";
export default function Layout(props: { children: React.ReactNode}) {
return (
<html>
<head>
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<SessionProvider>
<UserProvider>
<Stack gap={3}>
<Navbar expand="lg" className="bg-body-tertiary">
<Container>
<Navbar.Brand href="/">GAIA</Navbar.Brand>
<MenuNavigation />
<MenuAccess />
</Container>
</Navbar>
<Container>
<Stack gap={3}>
<Row>
{props.children}
</Row>
<Row>
<footer>
<p>© Gaia 2023</p>
</footer>
</Row>
</Stack>
</Container>
</Stack>
</UserProvider>
</SessionProvider>
</body>
</html>
)
}
型
gaia\app_components\auth\user-components.tsx
import { login, oauth_use } from "@/app/_api/(public)/auth-api";
import { redirect, useRouter } from "next/navigation";
import { useContext, createContext, useState, useEffect } from "react";
import { useSession, signOut, signIn } from "next-auth/react";
import { jwtDecode } from "jwt-decode";
//Exported components
export { useAuthenticatedUser, UserProvider }
interface AuthedUser {
id: string;
email: string;
profile: string;
accessToken: string;
expiresIn: number;
displayName: string;
pictureUrl: string;
}
interface AuthFlow {
code: number,
status: string,
message: string
}
let useFakeLoggin = false;
let fakeAuthedUser: AuthedUser = {
id: "63c3811392a585127099d34a",
email: "[email protected]",
profile: "admin",
accessToken: "xpto",
expiresIn: 86400,
displayName: "MASTER ADMIN",
pictureUrl: "https://xpto.png"
}
let loggedOffAuthFlow = { code: 1, status: 'LOGGED_OFF', message: '' };
let authenticatingAuthFlow = { code: 2, status: 'AUTHENTICATING', message: '' };
let authenticatedAuthFlow = { code: 3, status: 'AUTHENTICATED', message: '' };
let authErrorAuthFlow = { code: 4, status: 'AUTH_ERROR', message: 'Erro na autenticação. Verifique os dados e tente novamente.' };
let socialAuthErrorAuthFlow = { code: 5, status: 'SOCIAL_AUTH_ERROR', message: 'Erro na autenticação. Verifique o meio utilizado e tente novamente.' };
const useAuthenticatedUser = () => useContext(AuthenticatedUserContext);
const AuthenticatedUserContext = createContext({
authenticatedUser: useFakeLoggin ? fakeAuthedUser : undefined,
doLogout: () => { },
doLogin: (email: string, password: string) => { },
authFlow: loggedOffAuthFlow
});
function UserProvider(props: { children: React.ReactNode }) {
const [authenticatedUser, setAuthenticatedUser] = useState<AuthedUser | undefined>();
const [authFlow, setAuthFlow] = useState<AuthFlow>(loggedOffAuthFlow);
const router = useRouter();
const { data: token_data, status } = useSession();
useEffect(() => {
console.log('tokenData: ', token_data?.user);
console.log('status: ', status);
let usuarioLogado = localStorage.getItem('@Gaia:user');
let usuarioAccessToken = localStorage.getItem('@Gaia:userAccessToken');
console.log('usuarioLogado: ', usuarioLogado);
console.log('usuarioAccessToken: ', usuarioAccessToken);
if (!usuarioLogado && token_data && status === 'authenticated') {
// console.log('calling oAuthLogin');
oauth_login(token_data.user?.email, token_data.user?.name, token_data.user?.last_name, token_data.user?.picture, token_data.user?.provider_name, token_data.user?.id_token);
}
if (usuarioAccessToken) {
let currentDate = new Date();
let decodedAccessToken = jwtDecode(usuarioAccessToken);
if (decodedAccessToken.exp && (decodedAccessToken.exp * 1000) < currentDate.getTime()) {
setAuthFlow(loggedOffAuthFlow);
setAuthenticatedUser(undefined);
localStorage.removeItem('@Gaia:user');
localStorage.removeItem('@Gaia:userAccessToken');
usuarioLogado = null;
usuarioAccessToken = null;
}
}
if (usuarioLogado && usuarioAccessToken) {
setAuthFlow(authenticatedAuthFlow);
setAuthenticatedUser(JSON.parse(usuarioLogado));
}
}, [token_data]);
async function getAuthedUser() {
return authenticatedUser ?? undefined;
}
function doLogout() {
setAuthFlow(authenticatingAuthFlow);
localStorage.removeItem('@Gaia:user');
localStorage.removeItem('@Gaia:userAccessToken');
setAuthenticatedUser(undefined);
signOut(); //next-auth signOut
setAuthFlow(loggedOffAuthFlow);
router.push('/account/login');
//Clear token data.
}
function handleCallbackLogin(cbStatus: number, data: any) {
// console.log('callback recebido');
if (cbStatus == 9999 || cbStatus == 404) {
setAuthFlow(authErrorAuthFlow);
};
if (cbStatus == 404) {
authErrorAuthFlow.message.concat(data);
setAuthFlow(authErrorAuthFlow);
}
if (cbStatus != 200) {
//handleError
// console.log(`Callback login: Error Status ${cbStatus} | Message: ${data}`);
setAuthFlow(authErrorAuthFlow);
} else {
// console.log(`cbStatus != 200. data: ${JSON.stringify(data, null, 4)}`);
//Handle login flow.
if (data) {
let usuarioLogado: AuthedUser = {
id: data.id,
email: data.email,
profile: data.profile,
accessToken: data.accessToken,
expiresIn: data.expiresIn,
displayName: data.displayName,
pictureUrl: data.pictureUrl
};
setAuthenticatedUser(usuarioLogado);
localStorage.setItem('@Gaia:user', JSON.stringify(usuarioLogado));
localStorage.setItem('@Gaia:userAccessToken', usuarioLogado.accessToken);
setAuthFlow(authenticatedAuthFlow);
router.push('/vets/vacinas');
} else {
authErrorAuthFlow.message.concat(data);
setAuthFlow(authErrorAuthFlow);
}
//Set token data
}
}
function doLogin(email: string, password: string) {
//Fetch from apis
setAuthFlow(authenticatingAuthFlow);
login(email, password, handleCallbackLogin);
}
function oauth_login(email: string | null | undefined, nome: string | null | undefined, sobrenome: string | null | undefined, picture_url: string | null | undefined, handler: string, id: string) {
//Fetch from apis
setAuthFlow(authenticatingAuthFlow);
oauth_use(email, nome, sobrenome, picture_url, handler, id, handleCallbackLogin);
}
return (
<AuthenticatedUserContext.Provider value={{ authenticatedUser, doLogout, doLogin, authFlow }}>
{props.children}
</AuthenticatedUserContext.Provider>
);
}
型
2条答案
按热度按时间8ulbf1ek1#
根据问题:
问题是,每当有人开始在搜索栏中键入时,组件似乎会不必要地重新加载应用程序,导致多次读取。
原因:
在
aplicacao-grid.tsx
中有一个 *API调用 ,并且在用户交互{ filtro, buscar }
时,它也有更改的 props,此 * 状态更改 * 导致
aplicacao-grid.tsx
重新渲染,从而进行API调用。解决方案:
您应该从服务器端呈现页面,将API数据传递到客户端组件以处理搜索操作。
以下是我编写的示例代码: 您可以在需要时进行必要的更改。
文件夹结构:
字符串
User.js组件
loc\src\app\comp\User.js
:型
page.js
loc\src\app\user\page.js
:型
说明:
'use client'
,用户组件是客户端的。Network Tab
,在Name
下点击user
,在右边点击Preview
,你会看到预渲染的页面。user?_rsc=something
,这是React服务器组件负载(RSC负载由React在客户端使用来更新DOM)。阅读:
*渲染:https://nextjs.org/docs/app/building-your-application/rendering
*客户端组件:https://nextjs.org/docs/app/building-your-application/rendering/client-components
*服务器端和客户端组成模式:https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns
***什么是React Server Component Payload(RSC)?**https:nextjs.org/docs/app/building-your-application/rendering/server-components#how-are-server-components-rendered
如果您还有任何疑问,或者错过了什么,请留言,我会更新答案。
dzhpxtsq2#
每当
search
的状态发生变化时,React都会重新渲染它所在的组件。这里的问题是,它位于根组件中,并不是所有内容都需要重新渲染,只是DOM的特定部分。将搜索逻辑和内容提取到自己的组件(如<Content/>
)中会更有意义,因此每当需要更改显示的内容时,它不会重新呈现整个页面,只会重新呈现需要重新呈现的内容。TLDR;当
setState
被调用时,拥有状态的组件重新呈现。我们真正想要的是将搜索状态与身份验证/用户状态分离。