Reputation: 3707
I am using redux with react-router. I am also making one API call to load the data before loading routes. Technically my data should be load first then my route should be loading. However I found that it first load the route then it load the data from API. Following is code details.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import {ReduxRouter} from "redux-react-router";
import {Route} from "react-router"
import createBrowserHistory from "history/lib/createBrowserHistory"
import configureStore from './store';
import App from "./containers/App";
import Home from "./components/Home";
import Countries from "./components/Countries";
import {fetchData} from './actions/actions';
const history = new createBrowserHistory();
const store = configureStore();
function loadData() {
store.dispatch(fetchData('https://restcountries.eu/rest/v1/all'))
}
ReactDOM.render(
<Provider store={store}>
<ReduxRouter>
<Route history={history}>
<Route component={App}>
<Route path='/' component={Home} />
<Route path='/countries' component={Countries} onEnter={loadData} />
</Route>
</Route>
</ReduxRouter>
</Provider>,
document.getElementById('root')
);
store.js
import { compose , createStore, applyMiddleware } from "redux";
import rootReducer from "./../reducers";
import thunkMiddleware from 'redux-thunk';
import { routerStateReducer, reduxReactRouter } from 'redux-react-router';
import createHistory from 'history/lib/createBrowserHistory';
//Create app store
const createAppStore = compose(
applyMiddleware(thunkMiddleware),
reduxReactRouter({createHistory})
)(createStore);
export default function configureStore( initialState) {
const store = createAppStore(rootReducer, initialState);
return store;
}
reducer.js
import * as types from "./../actions/actionTypes";
import {combineReducers} from "redux";
import {routerStateReducer} from "redux-react-router";
function exampleReducer (state = {isLoading : false, data : [], error:false}, action = null) {
switch (action.type ) {
case types.RECV_ERROR :
return Object.assign({}, {isLoading : false, data: action.data, error: true});
case types.REQ_DATA :
return Object.assign({}, {isLoading : false, error: false});
case types.RECV_DATA :
console.log("Data receive, return immutable store ")
return Object.assign({}, {isLoading : false, data: action.data, error: false});
default:
return state;
}
}
const rootReducer = combineReducers({
router: routerStateReducer,
example: exampleReducer
});
export default rootReducer;
action.js
import * as types from "./actionTypes";
import axios from 'axios';
function requestData() {
return {type: types.REQ_DATA}
};
function receiveData(json) {
return{
type: types.RECV_DATA,
data: json
}
};
export function fetchData(url) {
return function(dispatch) {
//dispatch request for request data to start the spinner
dispatch(requestData());
//Now make the real API call to get the data
return axios( {
url: url,
timeout: 20000,
method: 'get',
responseType: 'json'
}).then(function(response) {
console.log("Data is loaded");
//Data is receive, now call function to aware this
dispatch(receiveData(response.data));
}).catch(function(response){
console.error("Some error happened");
});
}
}
Countries.js
import React from "react";
import {connect} from 'react-redux';
import Country from "./Country";
@connect(state => ({data: state.example.data}))
export default class Countries extends React.Component {
constructor(props) {
super(props);
}
render() {
const {data} = this.props;
console.log("Countries component is loading... data "+ data);
console.log("Countries this.props "+ JSON.stringify(this.props));
return (<div className='container'>
<table className='table table-bordered table-striped'>
<thead>
<tr>
<th>Name</th>
<th>Capital</th>
<th>Population</th>
<th>Domain</th>
</tr>
</thead>
<tbody>
{ data ? data.map(country => { return (<Country key={country.name} country={country} />)}): null}
</tbody>
</table>
</div>
);
};
}
Please help.
Upvotes: 0
Views: 2598
Reputation: 1426
The complexity in your case comes from the fact that the data-loading is an asynchronous operation.
If you want the onEnter function to wait for the loaded data before triggering the route transition, you should use the "callback" argument (as stated in react-router's doc).
But to know when the data has been loaded, you should also listen to the changes of your redux state in your onEnter function. You could write something like this:
function loadData(nextState, replace, callback) {
let unsubscribe;
function onStateChanged() {
const state = store.getState();
if (state.example.data) {
unsubscribe();
callback(); // data is loaded: trigger the route transition
}
}
unsubscribe = store.subscribe(onStateChanged);
store.dispatch(fetchData('https://restcountries.eu/rest/v1/all'));
}
Rather complicated, isn't it?!
You should try to avoid this pattern, for several reasons:
That's why you'd better load your data in the componentDidMount method of a component, in a non-blocking way. Before loaded data is received, simply define an initial state (without data) for the component.
You'll find useful standard patterns in Redux's real-world example, which features react-router.
Upvotes: 3
Reputation: 2866
Use componentDidMount or componentWillMount in your <App/>
component.
And you can fetch data before routing.
Upvotes: 0