首页 > 解决方案 > React Component 不会在使用 useContext 的上下文更改时重新渲染

问题描述

我正在尝试在侧边栏组件中处理的上下文值更改( )上重新呈现表格组件currentRep,我正在使用useContext. When the selection is changed, the state at the App-level changes, which is passed through the context provider. 此上下文在表组件中使用并触发useEffect挂钩以获取数据。虽然触发了这个钩子,但表格不会重新渲染。我曾在某处读到该useContext钩子每次都会触发值更改的渲染,所以我对(缺乏)结果感到困惑。

我可以确认在选择更改useEffect时表中的钩子会触发并且我会收到 API 响应。我错过了什么导致重新渲染?我尝试将 设置setCurrentRep为 -level 的状态设置器App,但这并没有改变任何东西。

这是创建的上下文:

上下文.tsx

import React from 'react'
import { AccountRep } from './Data';

export interface InfoContextInterface {
    repData:AccountRep[];
    currentRep: string;
    setCurrentRep: (r:string) => void;
}

const InfoContext = React.createContext<InfoContextInterface>({
    repData: [],
    currentRep:"",
    setCurrentRep: (rep) => {}
});
export const InfoProvider = InfoContext.Provider

export default InfoContext

上下文提供者是 -level 中的父App级:

应用程序.tsx

import React, {useState, useRef, useEffect} from 'react'
import Sidebar from './Sidebar'
import {Switch,Route,useLocation} from 'react-router-dom';
import Home from './pages/Home'
import SchoolInfo from './pages/SchoolInfo'
import {InfoProvider} from './Context'
import {Location} from 'history'
import {AccountRep} from './Data'

export default function App() {
  const [data, setData] = useState<AccountRep[]>([]);
  const [currentRep,setCurrentRep] = useState("")
  const cancelRequest = useRef<boolean>(false);
  let location = useLocation()
  let locState = location.state as locationStateProps 
  let background = locState && locState.background

  useEffect(() => {
      const getReps = async () => {
          const req = await (await fetch(`http://localhost:8080/api/reps`)).json()
          if (cancelRequest.current) {return}
          setData(req)
      };
      void getReps()
      return () => {
          cancelRequest.current = true
      }
  }, []);

  return (
    <>
    <InfoProvider value={{repData:data,currentRep:currentRep,setCurrentRep}}>
        <Sidebar />
        <Switch location={background || location}>
          <Route exact path='/' children={<Home/>}/>
          <Route path='/school-info' exact children={<SchoolInfo />}/>    
        </Switch>
    </InfoProvider>
    </>
    );
};

带有选择的侧边栏,其中上下文更改:

侧边栏.tsx

import React, {useState, useCallback,useContext} from 'react'
import './Sidebar.css'
import InfoContext from './Context'
import {AccountRep} from './Data'


const RepViewDropdown = () => {
    const [selectState, setState] = useState("");
    const context = useContext(InfoContext)
    
    const handleChangeHandler = useCallback((event: React.FormEvent<HTMLSelectElement>): void => {
        setState(event.currentTarget.value)
        context.setCurrentRep(event.currentTarget.value)
    },[]);

    const renderOptions = (repData: AccountRep[]) => {
        return repData.map(obj => {
            return <option key={obj.id} value={obj.id}>{obj.first_name}</option>
        })
    }

    return (
        <fieldset>
            <legend>Account Rep</legend>
            <select value={selectState} id="select-rep" onChange={handleChangeHandler}>
                <option value="">All</option>
                {renderOptions(context.repData)}
            </select>
        </fieldset>
    )
}

const Sidebar = () => {
    return (
        <div className="sidebar">
                <RepViewDropdown />
        </div>
    )
}

export default Sidebar;

和有问题的表格组件:

表格.tsx

import { useEffect, useState, useRef, useContext } from 'react';
import { Link, useLocation } from 'react-router-dom';
import InfoContext,{ InfoContextInterface } from '../Context'
import { School } from '../Data'
import CreateSchoolModal from '../CreateSchoolModal';
import './SchoolInfo.css'

interface SchoolInfoTableRowProps {
    data: School;
}

const SchoolInfoTableRow = (props:SchoolInfoTableRowProps) => {
    const location = useLocation()
    return (
        <tr>
            <td><Link to={{
                    pathname:`/school/${props.data.id}`,
                    state: { background: location }
                }}>{props.data.name}</Link></td>
            <td><Link to={{
                    pathname:`/school/${props.data.id}`,
                    state: { background: location }
                }}>{props.data.code}</Link></td>
            <td><Link to={{
                    pathname:`/school/${props.data.id}`,
                    state: { background: location }
                }}>{props.data.address}</Link></td>
        </tr>
    )
}

const SchoolInfoTable = () => {
    const cancelRequest = useRef<boolean>(false);
    const [data, setData] = useState<School[]>([]);
    const context: InfoContextInterface = useContext(InfoContext)

    useEffect(() => {
        const fetchData = async () => {
            const res = await (await fetch(`http://localhost:8080/api/schools${context.currentRep !== "" ? `?account_rep_id=${context.currentRep}` : ""}`)).json()
            if (cancelRequest.current) { return }
            setData(res)
        };
        void fetchData();
        console.log(context.currentRep)
        return () => {
            cancelRequest.current = true
        }
    }, [context.currentRep]);

    return (
        <table>
        <thead>
        <tr>
            <th>Name</th>
            <th>Code</th>
            <th>Address</th>
        </tr>
        </thead>
        <tbody>
        {data.map((obj,i) => {
                return ( <SchoolInfoTableRow key={i} data={obj}/> )
            })}
        </tbody>
        </table>
    )
}

const SchoolInfo = () => {
    const [showModal, setModal] = useState(false)

    const openModal = () => {
        setModal(prev => !prev)
    }
    
    return (
        <div id='school-info'>
            <h1 className='page-header'>School Info</h1>
            <div className="table-buttonset">
                <div className="buttonset">
                    <button onClick={openModal}>Add School</button>
                    <button disabled>Test Button</button>
                    <button disabled>Test Button</button>
                </div>
                <SchoolInfoTable/>
                <CreateSchoolModal showModal={showModal} setModal={setModal}/>
            </div>
        </div>
    )
}

export default SchoolInfo;

标签: reactjsreact-hooks

解决方案


我已经找到了解决方案。因为无论如何我最初都在获取所有数据,所以我可以只根据上下文值渲染行,而不是每次都在useEffect挂钩中获取新数据。

const renderTable = (data:School[]) => {
        if (context.currentRep) {
            data = data.filter(obj => obj.account_rep_id == context.currentRep)
        }
        return data.map((obj,i) => {
            return ( <SchoolInfoTableRow key={i} data={obj}/> )
        })
    }

    return (
        <table>
        <thead>
        <tr>
            <th>Name</th>
            <th>Code</th>
            <th>Address</th>
        </tr>
        </thead>
        <tbody>
        {renderTable(data)}
        </tbody>
        </table>
    )

推荐阅读