React 状态之外的连接函数

Connect function outside the state in React

我有一个有很多项目集合的应用程序,在管理区域,管理员用户可以删除或修改项目(就像传统的电子商务网站后台)。在逻辑上,为了修改一个项目,用户点击 "modify item" 按钮,然后打开一个模式。

我的问题是我无法在单击按钮时打开模式,因为当我单击 "modify item" 按钮时我的浏览器向我显示一条错误消息:"TypeError: Cannot read property 'setState' of undefined"

我使用 react-modal-responsive 来完成这个任务。

卡了好几天了,如果你能帮帮我,我会非常高兴。预先感谢。

这是我的代码:

import React, { Component } from 'react';
import { database } from '../firebase/firebase';
import * as firebase from 'firebase';
import withAuthorization from './withAuthorization';
import _ from 'lodash';
import AuthUserContext from './AuthUserContext';
import FileUploader from "react-firebase-file-uploader";
import Image from 'react-image-resizer';
import{InstantSearch, SearchBox, Hits, Highlight, RefinementList} from "react-instantsearch/dom";
import Modal from 'react-responsive-modal';



function removeToCatalogue(hit) { 
  const item = hit.objectID;
    firebase.database().ref(`catalogue/${item}`).remove();
    console.log("Capsule correctement supprimée du catalogue")
}


const Hit = ({hit}) =>
    <div className="item">
       <img src={hit.avatarURL} width={150} height={150}></img>
        <h1 className="marque">{hit.marque}</h1>
        <h3 className="numero">{hit.numero}</h3>
        <h4 className="reference">{hit.reference}</h4>
        <h4 className="marquesuite">{hit.marquesuite}</h4>
        <p className="cote">{hit.cote}</p>

        <button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button> 
        <button onClick={() => this.setState({open: true})}>Modify Item</button>
    </div>



const Content = () =>
  <div className="text-center">

    <Hits hitComponent={Hit}/>

  </div>


class Admin extends React.Component {
  constructor (props) {
    super (props);

    this.state = {
      marque: '',
      marquesuite: '',
      numero: '',
      reference: '',
      cote: '',
      avatar: "",
      isUploading: false,
      progress: 0,
      avatarURL: "",
      catalogue: {},
      open: false,

    };
    this.onInputChange = this.onInputChange.bind(this);
    this.onHandleSubmit = this.onHandleSubmit.bind(this);

  }


  onOpenModal = () => {
    this.setState({ open: true });
  };

  onCloseModal = () => {
    this.setState({ open: false });
  };


  onInputChange(e) {
    this.setState({
      [e.target.name]: e.target.value
    });
  }

  onHandleSubmit(e){
    e.preventDefault();
    const catalogue = {
      marque: this.state.marque,
      marquesuite: this.state.marquesuite,
      numero: this.state.numero,
      reference: this.state.reference,
      cote: this.state.cote,
      avatar: this.state.avatar,
      avatarURL: this.state.avatarURL,

    };
    database.push(catalogue);
    this.setState({
      marque: '',
      marquesuite: '',
      numero: '',
      reference: '',
      cote: '',
      avatar: "",
      isUploading: false,
      progress: 0,
      avatarURL: "",
    });
  }

  handleUploadStart = () => this.setState({ isUploading: true, progress: 0 });
  handleProgress = progress => this.setState({ progress });
  handleUploadError = error => {
    this.setState({ isUploading: false });
    console.error(error);
  };

  handleUploadSuccess = filename => {
    this.setState({ avatar: filename, progress: 100, isUploading: false });
    firebase
      .storage()
      .ref("images")
      .child(filename)
      .getDownloadURL()
      .then(url => this.setState({ avatarURL: url }));
  };


  render (){
    const { open } = this.state;
    return (
      <div className="container-fluid">
        <div className="container">
        <h1 class="text-center">Espace d'Administration</h1>
        <a href="https://super-capsule.000webhostapp.com/signaler-modification" class="btn btn-primary btn-lg active" role="button" aria-disabled="true">Signaler une modification</a>
        <form onSubmit={this.onHandleSubmit}>
          <div className="form-group">
          <label>Marque de la capsule:</label>
          <input
            value={this.state.marque}
            type="text"
            name='marque'
            placeholder="Marque"
            onChange={this.onInputChange}
            ref="marque"
            className="form-control" />
          </div>
          <div>
          <label>Numéro de la capsule:</label>
          <input
            value={this.state.numero}
            type="text"
            name='numero'
            placeholder="Numéro de la capsule"
            onChange={this.onInputChange}
            ref="numero"
            className="form-control"/>
          </div>
          <div className="form-group">
          <label>Référence de la capsule:</label>
          <input
            value={this.state.marquesuite}
            type="text"
            name='marquesuite'
            placeholder="Référence de la capsule"
            onChange={this.onInputChange}
            ref="marquesuite"
            className="form-control"/>
          </div>
          <div className="form-group">
          <label>Référence de la capsule (suite):</label>
          <input
            value={this.state.reference}
            type="text"
            name='reference'
            placeholder="Référence de la capsule (suite)"
            onChange={this.onInputChange}
            ref="reference"
            className="form-control"/>
          </div>
          <div className="form-group">
          <label>Cote de la capsule:</label>
          <input
            value={this.state.cote}
            type="text"
            name='cote'
            placeholder="Cote de la capsule"
            onChange={this.onInputChange}
            ref="cote"
            className="form-control"/>
          </div>

          <label>Visuel de la capsule:</label>
          {this.state.isUploading && <p>Progress: {this.state.progress}</p>}
          {this.state.avatarURL && <img src={this.state.avatarURL} />}
          <FileUploader
            accept="image/*"
            name="avatar"
            randomizeFilename
            storageRef={firebase.storage().ref("images")}
            onUploadStart={this.handleUploadStart}
            onUploadError={this.handleUploadError}
            onUploadSuccess={this.handleUploadSuccess}
            onProgress={this.handleProgress}
          />
          <button className="btn btn-info">Ajouter une capsule</button>
        </form>
      </div>

        <h1 className="text-center">Catalogue de capsule</h1>



        <InstantSearch
            apiKey="xxx"
            appId="xxx"
            indexName="xxx">


            <SearchBox translations={{placeholder:'Rechercher une capsule'}} width="500 px"/>

            <div>

                <Modal open={this.state.showModal} open={open} onClose={this.onCloseModal} center>
                  <h2>Simple centered modal</h2>
                </Modal>
            </div>

            <Content />    


          </InstantSearch>



      </div>
    )
  }
}



const authCondition = (authUser) => !!authUser;

export default withAuthorization(authCondition)(Admin);

组件的状态包含在定义它的组件中。在您的情况下,您正在尝试通过 Hit 组件更新 Admin 的状态。来自 React.js State and Life Documentation

State is similar to props, but it is private and fully controlled by the component.

但是抛出这个错误的原因不是因为Hit不能更新Admin的状态,它甚至没有尝试去更新。当您单击 Modify Item 按钮时发生的事情是 Hit 正在尝试更新它自己的状态,但这不会起作用,原因有二:

  1. 首先来自同一文档:

We mentioned before that components defined as classes have some additional features. Local state is exactly that: a feature available only to classes.

  1. 即使您将组件转换为 class 组件,状态也应该在构造函数中或在 class 字段中定义。但这无论如何对你没有帮助,因为虽然你将能够更新状态,但它将是 Hit 的状态。

所以,如果我是你,你会选择以下解决方案之一(视情况而定)。

  1. 在 Admin 的渲染方法中内联 ContentHitsHit 或将它们提取为 renderContentrenderHits Admin 的 class 字段方法,这样你就可以在这个方法方法中更新状态。

  2. 可能是您应该考虑更多的一个:将 openModel class 字段函数作为 onModifyItem 向下传递给 Hits 组件,并使用它作为 Modify Item 按钮的 onClick 道具。

这是一个例子:

const Hit = ({onModifyItem, hit}) =>
    <div className="item">
       <img src={hit.avatarURL} width={150} height={150}></img>
        <h1 className="marque">{hit.marque}</h1>
        <h3 className="numero">{hit.numero}</h3>
        <h4 className="reference">{hit.reference}</h4>
        <h4 className="marquesuite">{hit.marquesuite}</h4>
        <p className="cote">{hit.cote}</p>

        <button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button> 
        <button onClick={onModifyItem}>Modify Item</button>
    </div>


const Content = ({ onModifyItem }) => {
  const HitComponent = props => <Hit onModifyItem={onModifyItem} {...props}/>
  return (<div className="text-center">
    <Hits hitComponent={HitComponent}/>
  </div>);
}

<Content onModifyItem={this.openDialog}/>

我不知道这个答案是否可以完全解决您的问题,但它至少可以让您打开模式。

首先,你需要在渲染 Content 组件时传递一个处理程序,比如:

<Content onEdit={ this.onOpenModal } />

然后,我们应该在 Hit 组件上有一个类似的处理程序,将其附加到按钮点击事件:

const Hit = ({ hit, onEdit }) =>
  <div className="item">
    <img src={hit.avatarURL} width={150} height={150}></img>
    <h1 className="marque">{hit.marque}</h1>
    <h3 className="numero">{hit.numero}</h3>
    <h4 className="reference">{hit.reference}</h4>
    <h4 className="marquesuite">{hit.marquesuite}</h4>
    <p className="cote">{hit.cote}</p>

    <button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button> 
    <button onClick={ onEdit }>Modify Item</button>
  </div>

现在我们必须将此处理程序从 Content 组件传递给命中组件。

我看到的问题是 Hits 组件采用 hitComponent 属性,它必须是要呈现的组件。这意味着,要传递 onEdit 处理程序,我们必须在向下传递之前使用 Javascript clojure 增强 Hit 组件:

const Content = ({ onEdit }) => {

  const EnhancedHit = props =>
     <Hit onEdit={ onEdit } { ...props } />

  return (
    <div className="text-center">  
      <Hits hitComponent={ EnhancedHit } />
    </div>
  )
}

如果它打开了模式,你可以尝试报告吗?