Reputation: 185
I am trying to load data for a component using redux-saga and axios but I keep getting an infinite loop. These are the relevant components:
App.js
class App extends React.Component {
render() {
return (
<div className="app">
<header>
<div className="pure-u-1 pure-u-sm-1-3">
</div>
<nav className="pure-u-1 pure-u-sm-2-3">
<ul>
<li><Link to="/meal-plans">Meal Plans</Link></li>
</ul>
</nav>
</header>
<main>
<div className="view-area">
<Switch>
<Route exact path="/" component={() => <DashBoard {...this.props} />} />
<Route exact path="/meal-plans" component={() => <MealPlansContainer {...this.props} />} />
<Route path="/meal-plans/:id" component={props => <MealPlan {...this.props} {...props} />} />
</Switch>
</div>
</main>
</div>
)
}
}
function mapStateToProps(state) {
return {
app: state.app,
mealPlan: state.mealPlan,
mealPlans: state.mealPlans,
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
}
App = withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
export default App;
MealPlan.js
class MealPlan extends React.Component {
componentDidMount() {
let id = this.props.match.params.id
this.props.fetchMealPlan(id);
}
render() {
return (
<div className="meal-plan pure-g box">
<div className="pure-u-1">
<p>Future Meal Plan Page</p>
<LoadingSpinner show={true} />
</div>
</div>
);
}
}
I'm new to React and I don't have a good grasp on the component lifecycle, but I believe that componentDidMount() is being called each time the AJAX request completes and the 'FETCH_MEAL_PLAN_SUCCESSFUL' case runs in the reducer.
actionCreators.js
export function fetchMealPlan(id) {
return {
type: 'FETCH_MEAL_PLAN',
id
}
}
export function fetchMealPlanSuccessful(data) {
return {
type: 'FETCH_MEAL_PLAN_SUCCESSFUL',
data
}
}
export function fetchMealPlanFail(message) {
return {
type: 'FETCH_MEAL_PLAN_FAIL',
message
}
}
sagas.js
function* fetchMealPlan(action) {
try {
const mealPlan = yield call(api.get, '/meal_plans/' + action.id);
yield put(fetchMealPlanSuccessful(mealPlan.data));
} catch (e) {
yield put(fetchMealPlanFail(e.message));
}
}
function* watchFetchMealPlan() {
yield takeLatest('FETCH_MEAL_PLAN', fetchMealPlan);
}
mealPlan.js (reducer)
function mealPlan(state = {}, action) {
switch(action.type) {
case 'FETCH_MEAL_PLAN':
return state;
case 'FETCH_MEAL_PLAN_FAIL':
return state;
case 'FETCH_MEAL_PLAN_SUCCESSFUL':
state = Object.assign({}, action.data);
return state;
break;
default:
return state;
}
}
export default mealPlan;
If I don't stop the app, it'll keep making requests. In this test, it made 4,200+ requests: App in an infinite loop
I've spent hours researching how to best load data from my API for components after changing a route and so far I've been unsuccessful. Thank you in advance for your help.
Upvotes: 3
Views: 2810
Reputation: 884
Normally a component should run componentDidMount
once, unless it is deallocated and then mounted again. If its props or state change, then componentDidUpdate
will be fired.
So if you are experiencing multiple componentDidMount
, then it is likely that this component is nested as a child (I guess you did from your code snippet), and then when its parent's (App) state or props change, which will re-render its children (including MealPlan). Because you are connecting mealPlan
& mealPlan
from redux to App. Whenever you call the API, App's props will be updated, thus causing its children to be re-rendered, and causing MealPlan trigger componentDidMount
again and again.
I am not super experience with React. But I have not seen the way you set up routes. One way is you define a router in separate file, and use the router like this (this is not using the code-splitting feature of react-router):
const router = (
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/meal-plans" component={MealPlan} />
</Route>
</Router>
)
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}> // react-redux provider
{ router }
</Provider>,
document.querySelector('#main'));
If you wanna do code-splitting, since you are using redux-sagas, a good template is https://github.com/react-boilerplate/react-boilerplate. By looking at the ways how they do it, you could achieve what you want
Upvotes: 5