私が取り組んできたダッシュボードコンポーネントについて、最近いくつかの問題が発生しています。クライアント側の URL ルートを処理するために react-router を使用していますが、最近、ルートごとに非同期でコンポーネントをロードし始めました。
ルート コンポーネントから 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;
}
};
};
route-container.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>
);
}
}