![](/img/trans.png)
[英]How to update state of a component with value from reducer state in react-redux?
[英]React-Redux state in the component differs from the state in the store
我正在用React和Redux構建一個應用程序。
我有一個Account組件,該componentWillMount
從componentWillMount
方法中的服務器中獲取數據。 在提取數據時,該組件必須顯示“正在加載”文本,因此我已將“ isFetching”屬性添加到帳戶精簡器中。 從服務器獲取數據時,此屬性設置為true。
問題在於,在獲取數據時, render
方法中“ isFetching”屬性的值為false,而同時store.getState().account.isFetching
值為true(因為必須)。 這將導致異常,因為this.props.isFetching
為false,因此代碼試圖在仍從服務器加載data
同時顯示this.props.data.protectedString
(因此為null)。
我假設mapStateToProps綁定了一個錯誤的值(也許是初始狀態),但是我無法弄清楚為什么以及如何解決它。
這是我的AccountView代碼:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionCreators from '../../actions/account';
class AccountView extends React.Component {
componentWillMount() {
const token = this.props.token;
// fetching the data using credentials
this.props.actions.accountFetchData(token);
}
render() {
console.log("store state", window.store.getState().account); // isFetching == true
console.log("componentState", window.componentState); // isFetching == false
return (
<div>
{this.props.isFetching === true ? <h3>LOADING</h3> : <div>{this.props.data.protectedString}</div> }
</div>
);
}
}
const mapStateToProps = (state) => {
window.componentState = state.account;
return {
data: state.account.data,
isFetching: state.account.isFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actionCreators, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AccountView);
帳戶減少器:
const initialState = {
data: null,
isFetching: false
};
export default function(state = initialState, action) {
switch (action.type) {
case ACCOUNT_FETCH_DATA_REQUEST:
return Object.assign({}, state, {
isFetching: true
});
case ACCOUNT_RECEIVE_DATA:
return Object.assign({}, state, {
data: action.payload.data,
isFetching: false
});
default:
return state;
}
}
動作:
export function accountReceiveData(data) {
return {
type: ACCOUNT_RECEIVE_DATA,
payload: {
data
}
};
}
export function accountFetchDataRequest() {
return {
type: ACCOUNT_FETCH_DATA_REQUEST
};
}
export function accountFetchData(token) {
return (dispatch, state) => {
dispatch(accountFetchDataRequest());
axios({
// axios parameters to fetch data from the server
})
.then(checkHttpStatus)
.then((response) => {
dispatch(accountReceiveData(response.data));
})
.catch((error) => {
//error handling
});
};
}
這是我創建商店的方式:
import { applyMiddleware, compose, createStore } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import rootReducer from '../reducers';
export default function configureStore(initialState, history) {
// Add so dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware(history);
const middleware = applyMiddleware(thunk, reduxRouterMiddleware);
const createStoreWithMiddleware = compose(
middleware
);
return createStoreWithMiddleware(createStore)(rootReducer, initialState);
}
在index.js中:
import { createBrowserHistory } from 'history';
import { syncHistoryWithStore } from 'react-router-redux';
import configureStore from './store/configureStore';
const initialState = {};
const newBrowserHistory = createBrowserHistory();
const store = configureStore(initialState, newBrowserHistory);
const history = syncHistoryWithStore(newBrowserHistory, store);
// for test purposes
window.store = store;
我的代碼基於此示例-https://github.com/joshgeller/react-redux-jwt-auth-example
代碼看起來相同,但是由於某些模塊的新版本,我更改了一些位置。
使用react&redux獲取數據時,您應該始終問自己兩個問題:
您已經通過使用isFetching
回答了第二個問題,但是第一個問題仍然存在,這就是導致您出現問題的原因。 您應該做的是在化didInvalidate
中使用didInvalidate
( https://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md )
使用didInvalidate
您可以通過分派INVALIDATE_ACCOUNT
類的操作輕松檢查數據是否有效,並在需要時使它們無效。 由於您尚未提取數據,因此默認情況下您的數據無效。
(獎勵)何時可能使數據無效的一些示例:
渲染結果如下所示:
class AccountView extends React.Component {
componentDidMount() { // Better to call data from componentDidMount than componentWillMount: https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
const token = this.props.token;
// fetching the data using credentials
if (this.props.didInvalidate && !this.props.isFetching) {
this.props.actions.accountFetchData(token);
}
}
render() {
const {
isFetching,
didInvalidate,
data,
} = this.props;
if (isFetching || (didInvalidate && !isFetching)) {
return <Loading />; // You usually have your loading spinner or so in a component
}
return (
<div>
{data.protectedString}
</div>
);
}
}
這是您使用didInvalidate
的帳戶減少didInvalidate
:
const initialState = {
isFetching: false,
didInvalidate: true,
data: null,
};
export default function(state = initialState, action) {
switch (action.type) {
case INVALIDATE_ACCOUNT:
return { ...state, didInvalidate: true };
case ACCOUNT_FETCH_DATA_REQUEST:
return {
...state,
isFetching: true,
};
case ACCOUNT_RECEIVE_DATA:
return {
...state,
data: action.payload.data,
isFetching: false,
didInvalidate: false,
};
default:
return state;
}
}
在您的新生命周期以下:
1.首先渲染
{ isFetching: false, didInvalidate: true, data: null }
<Loading />
2. componentDidMount
3.函數調用:accountFetchData(1)
{ type: ACCOUNT_FETCH_DATA_REQUEST }
4.帳戶減少器
{ isFetching: true, didInvalidate: false, data: null }
: { isFetching: true, didInvalidate: false, data: null }
5.第二次渲染
{ isFetching: true, didInvalidate: false, data: null }
: { isFetching: true, didInvalidate: false, data: null }
<Loading />
6.調用的函數:accountFetchData(2)
7.帳戶減少器
{ isFetching: false, didInvalidate: false, data: { protectedString: '42: The answer to life' } }
8.第三渲染
{ isFetching: false, didInvalidate: false, data: { protectedString: '42: The answer to life' } }
<div>42: The answer to life</div>
希望能幫助到你。
編輯:讓我在另一個答案中的一個評論中回答您的問題
@Icepickle我不確定這是一種干凈的方法。 假設用戶將轉到/ account URL。 然后轉到其他URL。 然后回到/ account。 雖然將第二次從服務器加載數據,但是isFetching將為true,並且必須顯示“正在加載”文本,但是“ data”變量將不為null,因為它將包含來自先前請求的數據。 因此,將顯示舊數據,而不是“加載”。
使用didInvalidate
值,因為組件將知道您的數據是否有效,所以沒有無限重訪的風險。
在componentDidMount
,要重新獲取的條件將為false,因為其值為以下{ isFetching: false, didInvalidate: false }
。 然后不重新引用。
if (this.props.didInvalidate && !this.props.isFetching)
獎勵:但是,請注意didInvalidate
的數據緩存問題。
人們對此問題的討論不多,但是您將開始問這個問題:“我的數據無效時開始嗎?” (=我什么時候應該提取?)
減速器
如果可以的話,從長遠來看,讓我重構您的reducer代碼。
您的異徑管將更加模塊化,並且易於維護。
import { combineReducers } from 'redux';
export default combineReducers({
didInvalidate,
isFetching,
lastFetchDate,
data,
errors,
});
function lastFetchDate(state = true, action) {
switch (action.type) {
case 'ACCOUNT_RECEIVE_DATA':
return new Date();
default:
return state;
}
}
function didInvalidate(state = true, action) {
switch (action.type) {
case 'INVALIDATE_ACCOUNT':
return true;
case 'ACCOUNT_RECEIVE_DATA':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'ACCOUNT_FETCH_DATA_REQUEST':
return true;
case 'ACCOUNT_RECEIVE_DATA':
return false;
default:
return state;
}
}
function data(state = {}, action) {
switch (action.type) {
case 'ACCOUNT_RECEIVE_DATA':
return {
...state,
...action.payload.data,
};
default:
return state;
}
}
function errors(state = [], action) {
switch (action.type) {
case 'ACCOUNT_ERRORS':
return [
...state,
action.error,
];
case 'ACCOUNT_RECEIVE_DATA':
return state.length > 0 ? [] : state;
default:
return state;
}
}
動作
我將僅添加失效函數,以便更容易理解我在組件中調用的函數。 (注意:我沒有重命名您的函數,但您一定要注意命名)
export function invalidateAccount() {
return {
type: INVALIDATE_ACCOUNT
};
}
export function accountReceiveData(data) {
return {
type: ACCOUNT_RECEIVE_DATA,
payload: {
data
}
};
}
export function accountFetchDataRequest() {
return {
type: ACCOUNT_FETCH_DATA_REQUEST
};
}
export function accountFetchData(token) {
return (dispatch, state) => {
dispatch(accountFetchDataRequest());
axios({
// axios parameters to fetch data from the server
})
.then(checkHttpStatus)
.then((response) => {
dispatch(accountReceiveData(response.data));
})
.catch((error) => {
//error handling
});
};
}
零件
您將不得不在某些時候使數據無效。 我認為您的帳戶數據將在60分鍾后失效。
import isBefore from 'date-fns/is_before';
import addMinutes from 'date-fns/add_minutes'
const ACCOUNT_EXPIRATION_MINUTES = 60;
class AccountView extends React.Component {
componentDidMount() {
const token = this.props.token;
// fetching the data using credentials
if (this.props.didInvalidate && !this.props.isFetching) {
this.props.actions.accountFetchData(token);
}
// Below the check if your data is expired or not
if (
!this.props.didInvalidate && !this.props.isFetching &&
isBefore(
addMinutes(this.props.lastFetchDate, ACCOUNT_EXPIRATION_MINUTES), new Date()
)
) {
this.props.actions.invalidateAccount();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.didInvalidate && !nextProps.isFetching) {
nextProps.actions.accountFetchData(token);
}
}
render() {
const {
isFetching,
didInvalidate,
lastFetchDate,
data,
} = this.props;
/*
* Do not display the expired data, the componentDidMount will invalidate your data and refetch afterwars
*/
if (!didInvalidate && !isFetching &&
isBefore(addMinutes(lastFetchDate, ACCOUNT_EXPIRATION_MINUTES), new Date())
) {
return <Loading />;
}
if (isFetching || (didInvalidate && !isFetching)) {
return <Loading />; // You usually have your loading spinner or so in a component
}
return (
<div>
{data.protectedString}
</div>
);
}
}
這段代碼可以更簡潔,但更容易閱讀:)
您的三元陳述不轉換嗎? 您的渲染函數具有以下功能:
{this.props.isFetching === true ? <h3>LOADING</h3> : <div>{this.props.data.protectedString}</div> }
而您的化簡器中的initialState是這樣的:
const initialState = {
data: null,
isFetching: false
};
掛載時將默認為this.props.data.protectedString
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.