如何使用 react、redux 和 graphql 解决“未定义的 proptype”错误

Posted

技术标签:

【中文标题】如何使用 react、redux 和 graphql 解决“未定义的 proptype”错误【英文标题】:How to resolve a 'undefined proptype' error with react, redux and graphql 【发布时间】:2020-11-26 07:36:35 【问题描述】:

我正在尝试学习如何使用 graphql 构建 React/Redux 应用程序,但在解决“未捕获的类型错误:无法读取未定义的属性“记录”错误时遇到问题。

当我尝试使用“match.params.recordId”从 url (.../record/:recordId) 中提取“记录”ID 并将其发送到操作/reducer 方法时,我遇到了这个错误.

我的代码包含 console.log 语句来尝试跟踪组件、操作和 reducer 之间的“记录”道具的值,但是从不调用日志。这让我相信组件从不使用“useEffect”挂钩来调用操作,但我不确定。

我的记录组件:

import React, useState, useEffect from 'react'
import  connect  from 'react-redux';
import PropTypes from 'prop-types';

import  getrecord  from '../../actions/records';
import  Container, Row, Col, Card, Button, Form, ListGroup, Jumbotron, Modal  from 'react-bootstrap';



const MyRecord = (getrecord, match, recordID = id: match.params.recordId, thisRecord:record, loading) => 
    useEffect(() =>   
        getrecord(recordID);
    , [getrecord, recordID]);

    const LogInfo = () =>
        console.log('record ID: ',match.params.id);
        console.log('record: ', record);
    
    
    LogInfo();
    const [show, setShow] = useState(false);
    const handleClose = () => setShow(false);
    const handleShow = () => setShow(true);

    return (
        <>
            record === null ? (
                <h2>Loading...</h2>
            ) : (
            <Container id='MyRecord'>
                <Row>
                <Jumbotron className='recordJumbotron'>
                    <h1>Record: record.recordname </h1>
                    <Row>
                        <Col>
                            <Button variant="primary" onClick=handleShow>
                                Add an Item To Your Record
                            </Button>
                            <Modal show=show onHide=handleClose>
                                <Modal.Header closeButton>
                                <Modal.Title>Modal heading</Modal.Title>
                                </Modal.Header>
                                <Modal.Body>
                                <Form>
                                    <Form.Group controlId="formBasicEmail">
                                        <Form.Label>Email address</Form.Label>
                                        <Form.Control type="email" placeholder="Enter email" />
                                        <Form.Text className="text-muted">
                                        We'll never share your email with anyone else.
                                        </Form.Text>
                                    </Form.Group>

                                    <Form.Group controlId="formBasicPassword">
                                        <Form.Label>Password</Form.Label>
                                        <Form.Control type="password" placeholder="Password" />
                                    </Form.Group>
                                    <Form.Group controlId="formBasicCheckbox">
                                        <Form.Check type="checkbox" label="Check me out" />
                                    </Form.Group>
                                    <Button variant="primary" type="submit">
                                        Submit
                                    </Button>
                                </Form>
                                </Modal.Body>
                                <Modal.Footer>
                                <Button variant="secondary" onClick=handleClose>
                                    Close
                                </Button>
                                <Button variant="primary" onClick=handleClose>
                                    Save Changes
                                </Button>
                                </Modal.Footer>
                            </Modal>
                        </Col>
                        <Col>
                            <Button>Upload a Document</Button>
                        </Col>
                    </Row>
                </Jumbotron>
                </Row>

                <Row className='recordItemList'>
                <Card className='recordItemList'>
                    <Card.Header>Record Items</Card.Header>
                    <Card.Body>
                        <Card.Title>Special title treatment</Card.Title>
                        <ListGroup variant="flush">
                            <ListGroup.Item>Cras justo odio</ListGroup.Item>
                            <ListGroup.Item>Dapibus ac facilisis in</ListGroup.Item>
                            <ListGroup.Item>Morbi leo risus</ListGroup.Item>
                            <ListGroup.Item>Porta ac consectetur ac</ListGroup.Item>
                        </ListGroup>
                    </Card.Body>
                    </Card>
                </Row>
            </Container>
            )
        </>
    )


MyRecord.propTypes = 
    getrecord: PropTypes.func.isRequired,
    thisRecord: PropTypes.object
;

const mapStateToProps = state => (
   thisRecord: state.record
);

export default connect(mapStateToProps, getrecord)(MyRecord)

Records Reducer 文件:

import 
    CREATE_RECORD_SUCCESS,
    CREATE_RECORD_FAIL,
    LIST_RECORDS_BY_OWNER,
    GET_RECORD_BY_OWNER,
    GET_RECORDS_SUBSCRIPTION
   from '../actions/types';

  const initialState = 
    record: null,
    records: [],
    loading: true,
    error: 
  ;

  export default function (state = initialState, action) 
      const type, payload = action;

      switch(type)
        
        case CREATE_RECORD_SUCCESS:
            return 
                ...state,
                ...payload,
                //record:[payload, ...state.records],
                loading: false
            ;
        case GET_RECORD_BY_OWNER:
            return 
                ...state,              
                record: payload,
                loading: false
            ;
        
        case LIST_RECORDS_BY_OWNER:
            return 
                ...state,              
                records:payload,
                loading: false
            ;
        case GET_RECORDS_SUBSCRIPTION:
            return 
                ...state,              
                records:[payload, ...state.records],
                loading: false
            ;
        case CREATE_RECORD_FAIL:
            return 
                ...state,
                error: payload,
                loading: false
            ;
        default:
            return state;
        

  

记录操作:

//GET A RECORD
//GET A RECORD
export const getrecord = (recordID) => async dispatch => 
    try 
        console.log(' id: ', recordID);
        const res = await API.graphql(graphqlOperation(getRecord, recordID)) 
        console.log('record by id: ', res.data.getRecord);
        dispatch(
            type: GET_RECORD_BY_OWNER,
            payload: res.data.getRecord
        )
        console.log('getrecord: ', res)
     catch (error) 
        console.error(error);
        dispatch(
            type: CREATE_RECORD_FAIL         
        );
    


【问题讨论】:

【参考方案1】:

您对 Proptype 的导入有误。这将是

import PropTypes from 'prop-types';

验证将是:

MyRecord.propTypes = 
    getrecord: PropTypes.func.isRequired,
    thisRecord: PropTypes.object
;

【讨论】:

感谢您发现错字,代码已更新。不幸的是,这对 undefined 属性没有帮助......【参考方案2】:

使用对象操作链时尝试验证

使用前检查未定义

match?.params?.recordId

这样可以防止出错。

【讨论】:

【参考方案3】:

答案:问题实际上出在我的组件中。我的 mapStateToProps 方法有错字“...state.record”,但由于操作集名称应该是“...state.records”。这就是为什么“记录”未定义并且操作/记录方法甚至从未被调用的原因。

【讨论】:

以上是关于如何使用 react、redux 和 graphql 解决“未定义的 proptype”错误的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 react、redux 和 graphql 解决“未定义的 proptype”错误

如何组织 Redux Action-Creators 以在 Redux-React 中使用

如何使用来自 react-redux-firebase 的 redux thunk getFireBase 和 getFirestore 将数据添加到 firebase doc

如何使用 thunk 和 useDispatch(react-redux 挂钩)从操作中返回承诺?

使用 React-Router 和 Redux 时单击链接时如何调度操作?

如何使用 React 和 Redux 从另一个组件访问 ref?