You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React组件Props读取错误:无法读取null的'CustomerJoin'属性

React Props渲染报错:Cannot read properties of null (reading 'CustomerJoin') 解决

问题说明

报错信息:Cannot read properties of null (reading 'CustomerJoin')

场景:项目包含Home父组件及两个子组件Customers、CustomerProfilePage。用户在Customers组件的列表中选择客户后,选中项会被设置到Home组件的state中,再以Props形式传递给CustomerProfilePage,但该组件触发上述报错。

状态管理逻辑:Home组件通过传递函数统一维护state,再将state作为Props分发给子组件使用。


相关组件代码

Home组件代码

class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = { activeComponent: window.location.pathname.replace('/',''), activeCustomer: null, activeFlight: null, activeContact: null, activeEmail: null}
  }

  setActiveCustomer = (selectedCustomer) => {
    this.setState({activeCustomer: selectedCustomer})
    console.log('active customer - Home Component: ', selectedCustomer)
  }

  setComponent = (component) => {
    this.setState({activeComponent: component})
  }

  renderComponent() {
    const path = this.state.activeComponent;
    if(path === 'CreateContacts')
    {
      return <CreateCustomer activeCustomer={this.state.activeCustomer}/>
    } else if (path === 'Customers') {
      return <Customers setActiveCustomer={this.setActiveCustomer}/>
    } else if (path.substring(0, path.indexOf('/')) === 'Customer') {
      return <CustomerProfilePage activeCustomer={this.state.activeCustomer} setComponent={this.setComponent} setActiveCustomer={this.setActiveCustomer} setActiveContact={this.setActiveContact}/>
    } else if (path === 'CreateContact'){
      return <CreateContact activeCustomer={this.state.activeCustomer} setComponent={this.setComponent} activeContact={this.state.activeContact}/>
    } else if (path === 'Flights'){
      return <Flights setFlight={this.setFlight} setComponent={this.setComponent}/>
    } else if (path === 'Flights/FlightConfiguration') {
      return <FlightPage activeFlight={this.state.activeFlight} setComponent={this.setComponent} setActiveEmail={this.setActiveEmail}/>
    } else if (path === 'EmailConfiguration'){
      return <EmailConfiguration activeFlight={this.state.activeFlight} activeEmail={this.state.activeEmail}/>
    }
    return <HomeComponent/>
  }

  render() {
    return (
      <BrowserRouter>
      <div className='container'>
        <div className='row'>
          <div className='col'>
          <Sidebar/>
          </div>
        </div>
      </div>
      <div className='component-container grey rounded-corners padding'>
        <div className='row'>
          <div className='col'>
            {this.renderComponent()}
          </div>
        </div>
      </div>
      </BrowserRouter>
    )
  }
}

CustomerProfilePage组件代码(报错组件)

import React from 'react';
import HeaderComponent from '../HelperComponents/ComponentHeaders'
import { removeContact, getListOfContacts } from '../../Library/API_CALLS'

export default class CustomerProfilePage extends React.Component {
    constructor(props){
        super(props);
        this.state = { component: 'Customer Profile Page', activeCustomer: this.props.activeCustomer, activeContactList:[] }
    }

    componentDidMount(){
        this.getContactList()
    }

    getContactList = () => {
        getListOfContacts(this.props.activeCustomer.CustomerId)
            .then((this.getContactListSuccess))
            .catch((this.getContactListError))
    }

    getContactListSuccess = (response) => {
        this.setState({activeContactList: response})
    }

    getContactListError = (returnedError) => {
        console.log('Error Message: ', returnedError)
    }

    deleteContact = (deleteContact) => {
        removeContact(deleteContact.ContactId);
        this.setState(prevState => {
            const indexOfContact = prevState.activeContactList.findIndex(
                contact => contact.Email === deleteContact.Email);
                const newContactList = [...prevState.activeContactList];
                if(indexOfContact >= 0) newContactList.splite(indexOfContact, 1);
                return {activeContactList: newContactList}
        });
    }

    editContact = (contact) => {
        this.props.setActiveContact(contact)
        this.setComponent()
    }

    editContactButton = () => {
        this.props.setComponent('CreateContacts')
    }

    setComponent = () => {
        this.props.setComponent('CreateContacts')
        this.props.setActiveCustomer(this.props.activeCustomer)
    }

    convertDate = (entryDate) => {
        let returnDate = new Date(entryDate).getFullYear()
        return returnDate
    }

    buildContactTable(customerContactList, editContact){
        const contactRow = customerContactList.map(contact => {
            return <tr><td><span>{contact.FirstName + ' ' + contact.LastName}</span></td><td>{contact.Email}</td><td>{contact.PhoneNumber}</td><td><button className='rounded-corners contact-button' onClick={function(){editContact(contact)}}>Edit</button><button className='red rounded-corners contact-button' onClick={() => this.deleteContact(contact)}> Delete</button></td></tr>
        })

        return(
            <div className='row'>
                <div className='col'>
                    <table>
                        <thead>
                            <tr className='green'>
                                <th>Name</th>
                                <th>Email</th>
                                <th>Phone</th>
                            </tr>
                        </thead>
                        <tbody>
                            {contactRow}
                        </tbody>
                    </table>
                </div>
            </div>
        )
    }

    render(){
        const renderedDate = this.convertDate(this.state.activeCustomer.CustomerJoin)
        const contactList = this.state.activeContactList;
        return(
            <div className={this.state.component}>
                <HeaderComponent component={this.state.component} activeCustomer={this.props.activeCustomer.CustomerName}/>
                <div className='Component-Body'>
                    <div className='Component-Element-Container'>
                        <div className='row'>
                            <div className='col'>
                                <div className='center Component-Header-Text green'>
                                    <h3>{this.props.activeCustomer.CustomerName}</h3>
                                </div>
                            </div>
                        </div>
                        <div className='row'>
                            <div className='col'>
                                This Organization's profile is below
                            </div>
                        </div>
                        <div className='row'>
                            <div className='col'>
                                <b>Organization Detail:</b>
                                <div className='center'>
                                    <button className='rounded-corners customer-profile-button' onClick={this.setComponent}>Edit Customer</button> <button onClick={this.setComponent} className='rounded-corners customer-profile-button'>Add Contact</button> <button className='red rounded-corners customer-profile-button'> Delete Customer</button>
                                </div>
                            </div>
                        </div>
                        <div className='row'>
                            <div className='col'>
                                <table>
                                    <thead>
                                        <tr>
                                            <th>Hello</th>
                                            <th>Contact</th>
                                            <th>Country</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <tr>
                                            <td><b>Organization Name:</b> {this.props.activeCustomer.CustomerName}</td>
                                            <td><b>Phone: </b></td>
                                            <td><b>Fax: </b></td>
                                        </tr>
                                        <tr>
                                            <td><b>Primary Contact: </b></td>
                                            <td><b></b></td>
                                            <td><b></b></td>
                                        </tr>
                                        <tr>
                                            <td><b>Default Language: </b></td>
                                            <td><b>Fiscal Year Starts: </b></td>
                                            <td><b>Newsletter?: </b></td>
                                        </tr>
                                        <tr>
                                            <td><b>Customer Street:</b>{this.props.activeCustomer.CustomerStreet1}</td>
                                            <td><b>Customer Street 2: </b></td>
                                            <td><b>City: </b>{this.props.activeCustomer.CustomerCity}</td>
                                        </tr>
                                        <tr>
                                            <td><b>Customer State: </b>{this.props.activeCustomer.CustomerCity}</td>
                                            <td><b>Customer Acquisition Date: </b>{renderedDate}</td>
                                            <td></td>
                                        </tr>
                                        <tr>
                                            <td><b>Created By: </b></td>
                                            <td><b>Modified By: </b></td>
                                            <td></td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                    <div className='Component-Element-Container'>
                        {contactList.length > 0 ? this.buildContactTable(contactList, this.editContact) : null}
                    </div>
                </div>
            </div>
        )
    }
}

Customers组件代码

import React from 'react';
import HeaderComponent from '../HelperComponents/ComponentHeaders'
import {getCustomers} from '../../Library/API_CALLS'

export default class Customers extends React.Component {
    constructor(props){
        super(props);
        this.state = { component: 'Customers', customerList: [], firstPage: 0, lastPage: 5}
        this.handleClick = this.handleClick.bind(this)
        //There is presently no way to set the state for active customer in this component.
        //This is because the entire customer set of information is not required on this page.
    }

    componentDidMount(){
        this.getListOfCustomers();
    }

    getListOfCustomers = () => {
        getCustomers()
            .then((this.getCustomersListSuccess))
            .catch((this.getContactListError))
    }

    getCustomersListSuccess = (returnedCustomerList) => {
        this.setState({customerList: returnedCustomerList})
    }

    getContactListError = (error) => {
        console.log(error)
    }

    setCustomer = (customer) => {
        console.log('Customer List Component: ', customer)
        this.props.setActiveCustomer(customer)
    }

    renderCustomerRow = (customer) => {
        let customerCard = customer
        return(
            <div className='Component-Element-Container' onClick={() => this.setCustomer(customerCard)} key={customer.CustomerId}>
                <a href={`/Customer/${customer.CustomerName.replace(/\s/g, '')}`}><h3>{customer.CustomerName}</h3></a>
                <p>{customer.CustomerStreet1}</p>
            </div>
        )
    }

    handleClick() {
        this.manageList()
    }

    manageList(){
        let lowIndex = Math.floor(Math.random() * this.state.customerList.length - 5);
        let highIndex = lowIndex + 5;
        if(lowIndex <= 0){
            lowIndex = 0
            highIndex = 5
        }
        this.setState({firstPage: lowIndex, lastPage: highIndex})

        //Since we're working on this just as a prototype, we have configured it in such a way that the pages will randomly flip and return
        //a positive value that is within the list of pages.

        //in a full blown application, this would be managed systematically.
    }
    render(){
        const CustomerList = this.state.customerList.slice(this.state.firstPage,this.state.lastPage)
        return(
            <div className='Customers'>
                <HeaderComponent component={this.state.component}/>
                <div className='Component-Body'>
                    {CustomerList.length > 0 ? CustomerList.map(this.renderCustomerRow) : <div>No number</div>}
                    <div className='component-element-container'>
                        <div className='center'>
                                <button className='green forward-button' id='forward-button'onClick={this.handleClick.bind(this)}>Click</button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }}

原因分析

  1. 初始空值未处理:Home组件初始state中activeCustomer为null,如果直接访问CustomerProfilePage(比如刷新页面、手动输入URL),此时activeCustomer还未被设置,组件渲染时直接访问this.state.activeCustomer.CustomerJoin会触发空值报错。
  2. Props到State的同步问题:CustomerProfilePage在constructor中把props.activeCustomer存到自身state,但Props更新时State不会自动同步,即使Home的activeCustomer后续被设置,组件内部State还是旧的null。
  3. 生命周期中的空值调用:componentDidMount中直接调用getListOfContacts(this.props.activeCustomer.CustomerId),如果此时activeCustomer为null,会触发同样的空值报错。

解决方案

1. 全局添加空值检查(核心修复)

(1)修改CustomerProfilePage的render方法,优先判断空值

render(){
    // 先判断activeCustomer是否存在,不存在则显示加载占位
    if (!this.props.activeCustomer) {
        return <div>加载中...</div>;
    }

    // 直接使用props中的activeCustomer,不再依赖组件内部state
    const renderedDate = this.convertDate(this.props.activeCustomer.CustomerJoin)
    const contactList = this.state.activeContactList;
    
    return(
        <div className={this.state.component}>
            <HeaderComponent component={this.state.component} activeCustomer={this.props.activeCustomer.CustomerName}/>
            <div className='Component-Body'>
                {/* 原渲染内容保持不变,所有访问activeCustomer的地方统一用props */}
            </div>
        </div>
    )
}

(2)移除组件内部对activeCustomer的State存储

constructor(props){
    super(props);
    // 去掉activeCustomer,直接使用props
    this.state = { component: 'Customer Profile Page', activeContactList:[] }
}

(3)给getContactList添加空值判断

getContactList = () => {
    if (!this.props.activeCustomer) return;
    getListOfContacts(this.props.activeCustomer.CustomerId)
        .then(this.getContactListSuccess)
        .catch(this.getContactListError)
}

2. 监听Props变化,同步更新数据

添加componentDidUpdate生命周期,当activeCustomer变化时重新获取联系人列表:

componentDidUpdate(prevProps) {
    if (this.props.activeCustomer !== prevProps.activeCustomer) {
        this.getContactList();
    }
}

3. 优化路由跳转逻辑(避免跳转后State未更新)

将Customers组件中的a标签跳转改为先设置State再跳转,确保activeCustomer在页面渲染前已更新:

// 先导入withRouter
import { withRouter } from 'react-router-dom';

// 修改renderCustomerRow方法
renderCustomerRow = (customer) => {
    const handleNavigate = () => {
        this.setCustomer(customer);
        // 使用history跳转代替a标签
        this.props.history.push(`/Customer/${customer.CustomerName.replace(/\s/g, '')}`);
    }

    return(
        <div className='Component-Element-Container' onClick={handleNavigate} key={customer.CustomerId}>
            <h3>{customer.CustomerName}</h3>
            <p>{customer.CustomerStreet1}</p>
        </div>
    )
}

// 导出时用withRouter包裹组件
export default withRouter(Customers);

4. 添加路由守卫(可选)

在Home组件的renderComponent方法中,判断如果访问CustomerProfilePage但activeCustomer为空,直接重定向到Customers页面:

// 先导入Redirect
import { Redirect } from 'react-router-dom';

// 修改对应分支逻辑
else if (path.substring(0, path.indexOf('/')) === 'Customer') {
    if (!this.state.activeCustomer) {
        return <Redirect to="/Customers" />;
    }
    return <CustomerProfilePage activeCustomer={this.state.activeCustomer} setComponent={this.setComponent} setActiveCustomer={this.setActiveCustomer} setActiveContact={this.setActiveContact}/>;
}

内容的提问来源于stack exchange,提问作者Stephen Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 17:51:59