对于我一直在工作的仪表板组件,最近遇到了一些问题。我正在使用 react-router 来处理客户端的 url 路由,最近我开始为每个路由异步加载组件。
每当我从路由组件发出 http 请求时,我都会在控制台中收到一条警告消息。警告说 setState 不能是未安装组件的状态。到目前为止,我已经能够推断出安装循环可能导致 setState 无法成功设置组件的状态。除此之外,我不确定是什么导致此问题出现。
以前有没有其他人有这个问题?对此问题的任何建议表示赞赏。
asyncLoader.js:
import React from 'react';
export default (getComponent, extraProps=null) => {
return class asyncComponent extends React.Component {
static Component = null;
constructor(props){
super(props);
this.state = {Component: asyncComponent.Component};
}
componentWillMount(){
const {Component} = this.state;
if (Component === null) getComponent().then((component) => {
asyncComponent.Component = component;
if (this.mounted) this.setState({Component: component});
});
}
componentDidMount(){
this.mounted = true;
}
componentWillUnmount(){
this.mounted = false;
}
render(){
const {Component} = this.state;
return Component !== null ? <Component {...this.props} {...extraProps} /> : null;
}
};
};
路由容器.js:
import React from 'react';
import {Switch, Route, Redirect} from 'react-router-dom';
import asyncComponent from './asyncLoader';
export default class Container extends React.Component{
constructor(props){
super(props);
this.state = {loadingScreen: true};
this.handleStateChange = this.handleStateChange.bind(this);
}
handleStateChange(){
this.setState((prevState) => ({loadingScreen: !prevState.loadingScreen}));
}
render(){
const {user, getUserCallback} = this.props,
{loadingScreen} = this.state,
UserSettings = asyncComponent(() => import('./user').then((module) => module.UserSettings), {user: {...user}, getUserCallback: getUserCallback, loadingScreenCallback: this.handleStateChange}),
TransactionHistory = asyncComponent(() => import('./transactions').then((module) => module.TransactionHistory), {user: {...user}, loadingScreenCallback: this.handleStateChange, axiosInstance: this.props.axiosInstance}),
PaymentMethods = asyncComponent(() => import('./billing').then((module) => module.PaymentMethods), {loadingScreenCallback: this.handleStateChange, axiosInstance: this.props.axiosInstance}),
UserBillCreator = asyncComponent(() => import('./billing').then((module) => module.UserBillCreator), {loadingScreenCallback: this.handleStateChange, axiosInstance: this.props.axiosInstance}),
UserBillPaymentSender = asyncComponent(() => import('./billing').then((module) => module.UserBillPaymentSender), {loadingScreenCallback: this.handleStateChange, axiosInstance: this.props.axiosInstance, getUserCallback: getUserCallback, accountBalance: !user.admin ? user.account_balance : 0});
return(
<div className="d-flex flex-column col dashboard-app-container">
<div ref="loadingScreen" className={loadingScreen ? "row justify-content-center react-loading-screen" : "row justify-content-center react-loading-screen hide"}>
<div className="d-flex flex-column justify-content-center react-loading-container">
<i className="fa fa-spin fa-circle-o-notch" />
</div>
</div>
<Switch>
<Route path="/settings" component={UserSettings} />
<Route path="/transactions/history" component={TransactionHistory} />
<Route path="/transactions/paymentMethods" component={PaymentMethods} />
<Route path="/transactions/billing/create" component={UserBillCreator} />
<Route path="/transactions/billing/pay" component={UserBillPaymentSender} />
<Redirect from="/" to="/transactions/history" />
</Switch>
</div>
);
}
}
路由“/transactions/history”的组件示例
交易.js:
import React from 'react';
import _ from 'lodash';
import moment from 'moment';
import {Link} from 'react-router-dom';
import {AgGridReact} from 'ag-grid-react';
import DayPickerInput from 'react-day-picker/DayPickerInput';
import 'react-day-picker/lib/style.css';
export class TransactionHistory extends React.Component{
constructor(props){
super(props);
this.state = {
activity_day_period: '30 days',
date_filter: {
start: '',
end: ''
},
dateAccordianOpen: false,
data_grid: {
columns: [
{headerName: 'Bill ID', field: 'id'},
{headerName: 'Created On', field: 'created_on'},
{headerName: 'Paid on', field: 'transaction_created_on'},
{headerName: 'Amount', field: 'billed_amount'},
{headerName: 'Item', field: 'item_name'},
{headerName: 'Description', field: 'description'}
],
rows: [],
filtered_rows: [],
raw_data: []
}
};
this.handleDateChangeStart = this.handleDateChangeStart.bind(this);
this.handleDateChangeEnd = this.handleDateChangeEnd.bind(this);
this.onGridReady = this.onGridReady.bind(this);
this.handGridResize = this.handleGridResize.bind(this);
this.getTransactionData = this.getTransactionData.bind(this);
}
componentDidMount(){
this.getTransactionData();
setTimeout(this.props.loadingScreenCallback, 600);
}
componentWillUnmount(){
this.props.loadingScreenCallback();
}
handleDateChangeStart(date){
this.setState((prevState) => ({date_filter: {...prevState.date_filter, start: moment.utc(date).format('YYYY-MM-DD')}}));
}
handleDateChangeEnd(date){
this.setState((prevState) => ({date_filter: {...prevState.date_filter, end: moment.utc(date).format('YYYY-MM-DD')}}));
}
onGridReady(params){
this.api = params.api;
this.columnApi = params.columnApi;
this.api.sizeColumnsToFit();
}
handleGridResize(){
this.api.sizeColumnsToFit();
}
getTransactionData(){
const {activity_day_period} = this.state;
let api_url = window.location.origin;
switch (activity_day_period) {
case '30 days':
api_url = `${api_url}/api/user/billing?days=30`;
break;
case '3 months':
api_url = `${api_url}/api/user/billing?months=3`;
break;
case '6 months':
api_url = `${api_url}/api/user/billing?months=6`;
break;
case '1 year':
api_url = `${api_url}/api/user/billing?years=1`;
break;
case 'All':
api_url = `${api_url}/api/user/billing`;
break;
}
this.props.axiosInstance.get(api_url).then((response) => {
if (response.data.code == 200) {
// The warning seems to pop up when setState in this method is called...
this.setState((prevState) => ({
data_grid: {
...prevState.data_grid,
rows: response.data.bills.map((data) => {
if (data.bill_paid) {
return {
id: data.id,
created_on: moment.utc(data.created_on).format('YYYY-MM-DD'),
transaction_created_on: moment.utc(data.transaction.date).format('YYYY-MM-DD'),
billed_amount: `$${data.billed_amount.toFixed(2)}`,
item_name: data.item_name,
description: data.description
};
}
}),
raw_data: response.data.bills
}
}));
}
}).catch((error) => {
if (error.response) {
if (error.response.data.code == 401) {
//Here another http request is made to my api in order to get new auth tokens before retrying the original request.
}
});
}
render(){
const {user} = this.props,
{activity_day_period, date_filter, dateAccordianOpen, data_grid} = this.state,
start_date_obj = new Date(),
end_date_obj = new Date();
start_date_obj.setDate(start_date_obj.getDate() - 1);
return(
<div className="container pl-0 pr-0">
<div className="row">
<div className="col-md-6">
<h2>Transaction History</h2>
<p>You can check past and pending tranactions here.</p>
</div>
</div>
<div className="row">
<div className="col-md-7 ml-5 mt-3">
<h4>Account Balance</h4>
<h1 className="display-3 text-muted text-center">${user.account_balance > 0 ? user.account_balance.toFixed(2) : '0.00'}</h1>
{user.account_balance > 0 ? (<div className="row"><Link className="ml-auto" to="/transactions/billing/pay">Pay Now <i className="fa fa-chevron-right" /></Link></div>) : (<p className="text-right text-muted"><em>No balances to pay right now. HOORAY!</em></p>)}
</div>
</div>
<div className="row mt-4">
<div className="col-md-3 form-group">
<label htmlFor="activity">Activity</label>
<select id="activity" name="activity_day_period" className="form-control" value={activity_day_period} onChange={(e) => this.setState({activity_day_period: e.target.value}, this.getTransactionData)}>
<option key="1">30 days</option>
<option key="2">3 months</option>
<option key="3">6 months</option>
<option key="4">1 year</option>
<option key="5">All</option>
</select>
</div>
</div>
<div className="row pb-3">
<div className="ag-blue dashboard-transaction-grid">
<AgGridReact columnDefs={data_grid.columns}
rowData={data_grid.rows}
groupHeaders="true"
onGridReady={this.onGridReady}
onGridSizeChanged={this.handleGridResize}/>
</div>
</div>
</div>
);
}
}