尝试在功能组件之间传递状态但出现“未定义”错误

Posted

技术标签:

【中文标题】尝试在功能组件之间传递状态但出现“未定义”错误【英文标题】:Trying to pass state between functional components but getting 'undefined' error 【发布时间】:2021-04-30 08:30:57 【问题描述】:

我正在尝试将 2 组状态从一个组件传递到另一个组件,这样我就可以在 React 的 Header 中列出用户的公司和名称。我知道我得到了正确的信息,因为我在将它们设置为我的状态之前控制台记录了这两个变量但是,当我尝试在导航栏中渲染它们时,我得到了未定义。

这是我的 Header 组件:

import React,  useState  from 'react';
import  Route  from 'react-router-dom';
import  LinkContainer  from 'react-router-bootstrap';
import  Navbar, Nav, Container, NavDropdown  from 'react-bootstrap';
import SearchBox from './SearchBox';


const Header = ( companyName, userName ) => 
  console.log(userName);
  return (
    <header className='admin-bar'>
      <Navbar bg='white' variant='secondary' expand='lg' collapseOnSelect>
        <Container>
          <LinkContainer to='/'>
            <Navbar.Brand>companyName</Navbar.Brand>
          </LinkContainer>
       <NavDropdown title=userName id='basic-nav-dropdown'>
                <NavDropdown.Item href='#action/3.1'>Profile</NavDropdown.Item>
                <NavDropdown.Item href='#action/3.2'>Insights</NavDropdown.Item>
                <NavDropdown.Item href='#action/3.3'>Settings</NavDropdown.Item>
                <NavDropdown.Divider />
                <NavDropdown.Item href='#action/3.4'>Sign Out</NavDropdown.Item>
              </NavDropdown>
            </Nav>
          </Navbar.Collapse>
        </Container>
      </Navbar>
    </header>
  );
;

export default Header;

还有我初始化状态的组件:

import Amplify,  Auth  from 'aws-amplify';
import awsconfig from '../aws-exports';
import React,  useState, useEffect  from 'react';
import Upload from '../components/Upload';
import BucketList from '../components/BucketList';

Amplify.configure(awsconfig);

const FileUploadScreen = () => 
  const [companyName, setCompanyName] = useState('');
  const [userSession, setUserSession] = useState('');
  const [userName, setUserName] = useState('');

  const onPageRendered = async () => 
    let user = await Auth.currentAuthenticatedUser();
    let userToken = user.getSignInUserSession().getIdToken().getJwtToken();
    setUserSession(user.getSignInUserSession());
    let base64Url = userToken.split('.')[1];
    let var1 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    let var2 = decodeURIComponent(
      atob(var1)
        .split('')
        .map(function (c) 
          return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        )
        .join('')
    );
    let company = var2.substring(
      var2.indexOf('company') + 10,
      var2.indexOf('aud') - 3
    );
    console.log(company);
    setCompanyName(company);

    let userAccountName = var2.substring(
      var2.indexOf('"name":') + 8,
      var2.indexOf('exp') - 3
    );
    setUserName(userAccountName);
    console.log(userAccountName);
  ;

  useEffect(() => 
    onPageRendered();
  , []);

  return (
    <>
        <div className='container main p-5'>
          <h3 className='text-center pb-1 font-weight-normal bg-white'>
            Upload Files
          </h3>
          <Upload companyName=companyName />
          <BucketList companyName=companyName />
        </div>
    </>
  );
;

export default FileUploadScreen;

还有我的 App.js 文件:

import  useState, useEffect  from 'react';
import  BrowserRouter as Router, Route  from 'react-router-dom';
import  Container  from 'react-bootstrap';
import DashboardScreen from './screens/DashboardScreen';
import FileUploadScreen from './screens/FileUploadScreen';
import Sidebar from './components/Sidebar/Sidebar';
import CompanyProfileScreen from './screens/CompanyProfileScreen';
import InsightsScreen from './screens/InsightsScreen';
import WelcomeScreen from './screens/WelcomeScreen';
import Header from './components/Header';

const App = ( companyName, userName ) => 
  const [isLoading, setIsLoading] = useState(true);

  return (
    <Router>
      <Header companyName=companyName userName=userName />
      <Sidebar />
      <main className=''>
        <Container>
          <Route path='/company' component=CompanyProfileScreen />
          <Route path='/insights' component=InsightsScreen />
          <Route path='/fileupload' component=FileUploadScreen />
          <Route path='/landscape' component=DashboardScreen />
          <Route path='/' component=WelcomeScreen exact />
        </Container>
      </main>
    </Router>
  );
;

export default App;

正如我所说,我在我的 console.log 中获得了正确的名称(公司和用户名),但是一旦我通过它们,什么都不会呈现。

我也在这里添加了 BucketList 组件和 Upload 组件的代码以供参考:

import  useState, useEffect  from 'react';
import AWS from 'aws-sdk';
import  ListGroup  from 'react-bootstrap';

AWS.config.update(
  accessKeyId: process.env.REACT_APP_ACCESS_ID,
  secretAccessKey: process.env.REACT_APP_ACCESS_KEY,
  region: process.env.REACT_APP_REGION,
);
const s3 = new AWS.S3();

const BucketList = ( companyName ) => 
  const [listFiles, setListFiles] = useState([]);
  const [isVisible, setIsVisible] = useState(false);

  const params = 
    Bucket: process.env.REACT_APP_BUCKET_NAME,
    Delimiter: '',
  ;

  useEffect(() => 
    s3.listObjectsV2(params, (err, data) => 
      if (err) 
        console.log(err, err.stack);
       else 
        setListFiles(data.Contents);
      
    );
  , []);

  // Filtering correct file names
  let files = [];
  for (let i = 0; i < listFiles.length; i++) 
    let str = listFiles[i].Key;
    if (
      str.includes('OUTPUT') &&
      str !== `$companyName/` &&
      str !== 'OUTPUT' &&
      str.startsWith(companyName)
    ) 
      files.push(str);
    

    for (let i = 0; i < files.length; i++) 
      if (
        files[i].endsWith(
          'vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        )
      ) 
        files[i] = files[i].replace(
          '.vnd.openxmlformats-officedocument.spreadsheetml.sheet',
          '.xlsx'
        );
       else if (files[i].endsWith('.vnd.ms-excel')) 
        files[i] = files[i].replace('.vnd.ms-excel', '.csv');
      
    
  

  // Adding dynamic class to card for scroll
  useEffect(() => 
    files.length > 3 && setIsVisible(true);
  , [files.length]);

  return (
    <div className=`card my-4 $isVisible ? 'test' : ''`>
      <div className='card-header'>companyName Current Files</div>
      <ListGroup className='list-group'>
        files &&
          files.map((name, index) => (
            <ListGroup.Item key=index>
              name.split('/').slice(2)
            </ListGroup.Item>
          ))
      </ListGroup>
    </div>
  );
;

export default BucketList;

import React,  useRef, useState, Fragment, useEffect  from 'react';
import  ListGroup  from 'react-bootstrap';
import Message from './Message';
import ProgressBar from './ProgressBar';
import AWS from 'aws-sdk';

const Upload = ( companyName ) => 
  const bucketName = process.env.REACT_APP_BUCKET_NAME;

  const fileInput = useRef();

  const [filename, setFilename] = useState('Choose File');
  const [message, setMessage] = useState('');
  const [fileInfos, setFileInfos] = useState([]);
  const [progress, setProgress] = useState(0);
  const [showProgressBar, setShowProgressBar] = useState(false);
  const [isScroll, setIsScroll] = useState(false);

  const selectFile = (e) => 
    setFilename(e.target.files[0].name);
  ;

  const handleClick = (event) => 
    event.preventDefault();
    let newArr = fileInput.current.files;
    for (let i = 0; i < newArr.length; i++) 
      handleUpload(newArr[i]);
    
  ;

  AWS.config.update(
    accessKeyId: process.env.REACT_APP_ACCESS_ID,
    secretAccessKey: process.env.REACT_APP_ACCESS_KEY,
  );

  const myBucket = new AWS.S3(
    params: 
      Bucket: `$bucketName/$companyName`,
    ,
    region: process.env.REACT_APP_REGION,
  );

  const handleUpload = (file) => 
    const params = 
      ACL: 'public-read',
      Key: file.name,
      ContentType: file.type,
      Body: file,
    ;

    let newFileName = file.name;

    myBucket
      .putObject(params)
      .on('httpUploadProgress', (evt) => 
        setProgress(Math.round((evt.loaded / evt.total) * 100));
        setFileInfos([newFileName, ...fileInfos]);
        setMessage('File uploaded');
      )
      .send((err) => 
        if (err) 
          console.log(err);
          setShowProgressBar(false);
          setMessage('Could not upload file');
        
      );
  ;

  useEffect(() => 
    fileInfos.length >= 2 && setIsScroll(true);
  , [fileInfos.length]);

  if (progress === 100) 
    setTimeout(() => 
      setProgress(0);
      setShowProgressBar(false);
      setMessage('');
    , 3000);
  

  return (
    <Fragment>
      <form className='bg-white my-4' onSubmit=handleClick>
        message ? <Message msg=message /> : null
        <div className='custom-file mb-2'>
          <input
            type='file'
            onChange=selectFile
            multiple
            ref=fileInput
            id='customFile'
            className='custom-file-input'
          />

          <label className='custom-file-label' htmlFor='customFile'>
            filename
          </label>
        </div>
        showProgressBar ? <ProgressBar percentage=progress /> : null

        <input
          type='submit'
          value='Upload'
          className='btn btn-primary btn-block mt-3'
          disabled=filename === 'Choose File'
          onClick=() => setShowProgressBar(true)
        />

        <br />

        <div className=`card $isScroll ? 'test' : ''`>
          <div className='card-header'>Recently Added</div>
          <ListGroup className='list-group'>
            fileInfos &&
              fileInfos.map((file, index) => (
                <ListGroup.Item key=index>file</ListGroup.Item>
              ))
          </ListGroup>
        </div>
      </form>
    </Fragment>
  );
;

export default Upload;

任何建议将不胜感激!

【问题讨论】:

我认为你必须在 useEffect 挂钩中定义你的异步函数onPageRendered 几个问题:(1)你能分享BucketListUpload组件的代码吗? (2) 您能否分享您在控制台中看到的任何错误? 我看不到您将值从 FileUploadScreen 传递到 Header 组件以显示您的数据 感谢您的回复,我已经添加了我的代码 BucketList 和 Upload @secan - 我在控制台中没有收到任何错误,但如果我在 Header 组件中使用 console.log(userName)我回来了“未定义”,但是 FileUploadScreen 组件中的 console.logging 用户名给了我正确的信息 @KeerthiVijay 感谢您的回复,我仍在学习很多关于 React 的知识,从外观上看,这是我出错的地方吗?我想当我在 Header 组件中解构它们时,它们现在被正确传递了? 【参考方案1】:

您必须将以下依赖项添加到您的 useEffect 挂钩中,以便在您获得公司名称和用户名时更新:

useEffect(() => 
    onPageRendered();
  , [companyName, userName]);

【讨论】:

非常感谢,但是我仍然遇到同样的问题,即两者都必须未定义,因为 UI 中没有呈现任何内容 可能是因为你没有把 FileUploadScreen 中的 companyName 和 userName 给 Header 组件 组件结构我不懂。 Header 由 App 渲染,但 App 由谁渲染? FileUploadScreen 在哪里渲染? 在 Header 中,我以为我像这样传递 companyName 和 userName:'const Header = ( companyName, userName ) => ' FileUploadScreen 在 App 中呈现,我相信我同时传递了两者像我在上面粘贴的那样将状态集作为道具放入 Header 组件中 您在 FileUploadScreen 中有变量 companyName 和 userName。但是你永远不会把它们交给 Header 组件。【参考方案2】:

为什么会这样

    useState 挂钩中的 Setter 方法是一个异步函数。 您在 useEffect Hook 中调用的函数也是一个异步函数 现在,由于您没有在 useEffect Hook 中设置任何依赖项,因此在初始化程序加载时,会在 wepApi(event loop) 中发送异步函数。 由于 sate 的初始值是一个空字符串,并且程序最初运行时不关心 Async 函数,因此您在 jsx 中留下了空值。

如何解决

    只需传递useEffect依赖数组中的依赖项

    useEffect(() => onPageRendered(); , [公司名, 用户名]);

    此外,您可以在 jsx 中添加条件渲染以检查该值是否存在。

【讨论】:

非常感谢,我刚才确实将两者都添加为依赖项,但是我仍然遇到同样的问题,即两者都必须未定义,因为 UI 中没有呈现任何内容

以上是关于尝试在功能组件之间传递状态但出现“未定义”错误的主要内容,如果未能解决你的问题,请参考以下文章

传递给子组件的道具未定义,但 console.log 显示道具的值

类组件中的 UseRef。画布未定义

在功能性反应组件中获取未定义的道具

错误:元素类型无效:在尝试更新状态时,应在反应中出现字符串

未处理的拒绝:操作未定义React js和Redux

React中传递道具和映射数据