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
正在尝试更新它自己的状态,但这不会起作用,原因有二:
- 首先来自同一文档:
We mentioned before that components defined as classes have some additional features. Local state is exactly that: a feature available only to classes.
- 即使您将组件转换为 class 组件,状态也应该在构造函数中或在 class 字段中定义。但这无论如何对你没有帮助,因为虽然你将能够更新状态,但它将是
Hit
的状态。
所以,如果我是你,你会选择以下解决方案之一(视情况而定)。
在 Admin 的渲染方法中内联 Content
、Hits
和 Hit
或将它们提取为 renderContent
和 renderHits
Admin 的 class 字段方法,这样你就可以在这个方法方法中更新状态。
可能是您应该考虑更多的一个:将 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>
)
}
如果它打开了模式,你可以尝试报告吗?
我有一个有很多项目集合的应用程序,在管理区域,管理员用户可以删除或修改项目(就像传统的电子商务网站后台)。在逻辑上,为了修改一个项目,用户点击 "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
正在尝试更新它自己的状态,但这不会起作用,原因有二:
- 首先来自同一文档:
We mentioned before that components defined as classes have some additional features. Local state is exactly that: a feature available only to classes.
- 即使您将组件转换为 class 组件,状态也应该在构造函数中或在 class 字段中定义。但这无论如何对你没有帮助,因为虽然你将能够更新状态,但它将是
Hit
的状态。
所以,如果我是你,你会选择以下解决方案之一(视情况而定)。
在 Admin 的渲染方法中内联
Content
、Hits
和Hit
或将它们提取为renderContent
和renderHits
Admin 的 class 字段方法,这样你就可以在这个方法方法中更新状态。可能是您应该考虑更多的一个:将
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>
)
}
如果它打开了模式,你可以尝试报告吗?