Link 的 props 发生变化时如何更新状态?

How to update state when props from Link change?

我正在像这样从 component1 中的智能合约获取数据(这是一个带有链接的菜单:

const [contract, setContract] = useState()

  const [projects, setProjects] = useState([])
  const getData = async () => {
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    await provider.send('eth_requestAccounts', [])
    const campaignContract = new ethers.Contract(
      process.env.REACT_APP_CAMPAIGNS_CONTRACT_ADDRESS,
      CampaignsAbi,
      provider,
    )
    
    setContract(campaignContract)
    const numberOfProjects = await contract.numberOfCampaigns()

    const campaigns = []
    for (let i = 1; i <= numberOfProjects; i++) {
      campaigns.push(await contract.campaigns(i))
    }
 
    setProjects([...campaigns])
  }

  useEffect(() => {
    getData()
  }, [contract])

  return (
    <div className="App-top-bar">
      <div className="App-top-bar-menu">
      
        <Link className="link" to="/projects">
          <MenuButton title={'Donate'} />
        </Link>
        <Link
          className="link"
          to={{
            pathname: '/projects/my-projects',
          }}
          state={projects}>
        
          <MenuButton title={'My Projects'} />
        </Link>
  
      </div>
    </div>
  )

路径“/projects/my-projects”指向 'MyProjects.js'。

//index.js
ReactDOM.render(
  <React.StrictMode>
    <MoralisProvider serverUrl={serverURL} appId={applicationID}>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />}>
            <Route path="/projects" element={<Projects />}>
              <Route path="/projects/my-projects" element={<MyProjects />} />
         </Route>
          </Route>
          <Route
            path="*"
            element={
              <div>
                404 :(
              </div>
            }
          />
        </Routes>
      </BrowserRouter>
    </MoralisProvider>
  </React.StrictMode>,
  document.getElementById('root'),
)

这是它的样子:

const MyProjects = () => {
  return (
    <div>
      <ProjectList />
    </div>
  )
}
const ProjectList = () => {
  const location = useLocation()
  const { state: projects } = useLocation()

  return (
    <div className="donate-body">
      {projects && projects.length !== 0 ? (
        projects.map((campaign) => <Project projectDetails={campaign} />)
      ) : (
        <h1>No Projects</h1>
      )}
    </div>
  )
}

我希望我的 'ProjectList' 在合同中的数据更改和 'projects' 更改时更新。有什么帮助吗?感觉我犯了一个 React 错误。

您可以使用 useEffect 挂钩将状态更新加入队列,以“同步”传递的路由状态与本地组件状态。

示例:

const ProjectList = () => {
  const location = useLocation();

  const [projects, setProjects] = useState(location.state);

  useEffect(() => {
    setProjects(location.state);
  }, [location.state]);

  return (
    <div className="donate-body">
      {projects.map((campaign) => (
        <Project projectDetails={campaign} />
      ))}
    </div>
  );
};

请记住,在 React 中通常认为 anti-pattern 将传递的 state/props/etc 存储到本地组件状态。直接消费值是很常见的。

示例:

const ProjectList = () => {
  const { state: projects } = useLocation();

  return (
    <div className="donate-body">
      {projects?.map((campaign) => (
        <Project projectDetails={campaign} />
      ))}
    </div>
  );
};

更新

如果你想要一个单一的事实来源,那么我建议使用 React 上下文来存储 projects 状态。

示例:

import { createContext, useContext, useState } from 'react';

export const ProjectsContext = createContext({
  projects: [],
  setProjects: () => {},
});

export const useProjects = () => useContext(ProjectsContext);

const ProjectsProvider = ({ children }) => {
  const [projects, setProjects] = useState(location.state);

  return (
    <ProjectsContext.Provider value={{ projects, setProjects }}>
      {children}
    </ProjectsContext>
  );
};

export default ProjectsProvider;

提供上下文。

import ProjectsProvider from '../path/to/ProjectsProvider';

...

ReactDOM.render(
  <React.StrictMode>
    <ProjectsProvider>
      <MoralisProvider serverUrl={serverURL} appId={applicationID}>
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<App />}>
              <Route path="/projects" element={<Projects />} />
              <Route path="/projects/my-projects" element={<MyProjects />} />
            </Route>
            <Route path="*" element={<div>404 :(</div>} />
          </Routes>
        </BrowserRouter>
      </MoralisProvider>
    </ProjectsProvider>
  </React.StrictMode>,
  document.getElementById('root'),
);

项目

import { useProjects } from '../path/to/ProjectsProvider';

...


const { setProjects } = useProjects();

useEffect(() => {
  const getData = async () => {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    await provider.send('eth_requestAccounts', [])
    const campaignContract = new ethers.Contract(
      process.env.REACT_APP_CAMPAIGNS_CONTRACT_ADDRESS,
      CampaignsAbi,
      provider,
    );

    setContract(campaignContract);
    const numberOfProjects = await contract.numberOfCampaigns();

    const campaignsPromises = [];
    for (let i = 1; i <= numberOfProjects; i++) {
      campaigns.push(contract.campaigns(i));
    }

    const campaigns = await Promise.all(campaignsPromises);

    setProjects(campaigns);
  };

  getData();
}, [contract]);

return (
  <div className="App-top-bar">
    <div className="App-top-bar-menu">
      <Link className="link" to="/projects">
        <MenuButton title='Donate' />
      </Link>
      <Link className="link" to='/projects/my-projects'>
        <MenuButton title='My Projects' />
      </Link>
    </div>
  </div>
);

我的项目

import { useProjects } from '../path/to/ProjectsProvider';

...

const MyProjects = () => {
  return (
    <div>
      <ProjectList />
    </div>
  );
};

const ProjectList = () => {
  const { projects } = useProjects();
  return (
    <div className="donate-body">
      {projects.length ? (
        projects.map((campaign) => <Project projectDetails={campaign} />)
      ) : (
        <h1>No Projects</h1>
      )}
    </div>
  );
};