next.js 使用Array.map时出现多个获取请求(& filter)

gzszwxb4  于 2023-11-18  发布在  其他
关注(0)|答案(2)|浏览(135)

我正在使用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>
    );
}

8ulbf1ek

8ulbf1ek1#

根据问题:

问题是,每当有人开始在搜索栏中键入时,组件似乎会不必要地重新加载应用程序,导致多次读取。

原因:

aplicacao-grid.tsx中有一个 *API调用 ,并且在用户交互{ filtro, buscar }时,它也有更改的 props
此 * 状态更改 * 导致aplicacao-grid.tsx重新渲染,从而进行API调用。

解决方案:

您应该从服务器端呈现页面,将API数据传递到客户端组件以处理搜索操作。

以下是我编写的示例代码: 您可以在需要时进行必要的更改。
文件夹结构:

projectName
├── src
│   └── app
│       ├── comp
│       │   └── User.js
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.js
│       ├── page.js
│       └── user
│           └── page.js
└── tailwind.config.js

字符串

User.js组件loc\src\app\comp\User.js

'use client'
import React, { useState } from 'react'

const User = ({ Data }) => {

    // console.log("Data.users", Data.users);
    // VISBILE IN BROWSER

    const [UserData, SetUserData] = useState(Data.users)

    const [SearchTerm, SetSearchTerm] = useState("")

    return (
        <div>
            <h1>Client Component Gets data from Server as props.</h1>

            <input type="text" onChange={(e) => { SetSearchTerm(e.target.value) }} />
            {
                SearchTerm ?
                    // IF SearchTerm IS EMPTY THEN SHOW ALL DATA, ELSE SHOW SEARCHED 

                    // IM MAPPING USER'S FIRSTNAME, 
                    // HENCE IM SEARCHING USER BY FIRSTNAME, 

                    UserData.filter(s => s.firstName.toLowerCase().includes(SearchTerm.toLowerCase())).map((u, i) => (
                        <p key={i}>{u.firstName}</p>
                    ))

                    :

                    UserData.map((u, i) => (
                        <p key={i}>{u.firstName}</p>
                    ))
            }
        </div>
    )
}

export default React.memo(User);

page.jsloc\src\app\user\page.js

import User from "../comp/User";

async function GetUserData() {
    let data = await fetch('https://dummyjson.com/users')
    let UserData = await data.json()
    return UserData
}

// ABOVE API SIMULATES SERVER-SIDE DATA FETCHING
// YOU CAN MAKE NECESSARY CHANGES

export default async function Page() {

    let UserData = await GetUserData();
    // STORING DATA FROM API 

    // console.log("Data KEYS from API on Serverside : ", Object.keys(UserData));
    // THIS LOG WILL BE IN TERMINAL AS THIS PAGE IS SERVER-SIDE RENDERED
    // SHOWS USERS

    return (
        <div>
            <h1>Users Page</h1>

            <User Data={UserData} />
            {/* USER COMPONENT IS CIENT SIDE HENCE, SERVER DOESN'T RENDER IT, CIENT BROWSER RENDERS IT*/}

            <h3>Rest of the page is  SERVER-SIDE RENDERED </h3>

        </div>
    )
}

说明:

如果您还有任何疑问,或者错过了什么,请留言,我会更新答案。

dzhpxtsq

dzhpxtsq2#

每当search的状态发生变化时,React都会重新渲染它所在的组件。这里的问题是,它位于根组件中,并不是所有内容都需要重新渲染,只是DOM的特定部分。将搜索逻辑和内容提取到自己的组件(如<Content/>)中会更有意义,因此每当需要更改显示的内容时,它不会重新呈现整个页面,只会重新呈现需要重新呈现的内容。
TLDR;当setState被调用时,拥有状态的组件重新呈现。我们真正想要的是将搜索状态与身份验证/用户状态分离。

相关问题