0

I have the following component. I did debugging. The function inside useEffect never get called. The code reaches to useEffect but then does not enter inside, and therefore does not fetch records from the database. Any ideas why this is happening?

import * as React from 'react';
import { useEffect } from 'react';
import { connect } from 'react-redux';
import { FetchAssignmentData } from './AssignmentDataOperations'

const AssignmentComprehensive = (props) => {

    useEffect(() => {
        if (props.loading != true)
            props.fetchAssignment(props.match.params.id);
    }, []);

    if (props.loading) {
        return <div>Loading...</div>;
    }

    if (props.error) {
        return (<div>{props.error}...</div>)
    }

    //these are always null
    const assignmentId = props.assignmentIds[0];
    const assignment = props.assignments[assignmentId];

    return (
        //this throws error since the values are never fetched from db
        <div>{props.assignments[props.assignmentIds[0]].title}</div>
    );
}

const mapStateToProps = state => ({
    assignmentIds: state.assignmentReducer.assignmentIds,
    assignments: state.assignmentReducer.assignments,
    submissions: state.assignmentReducer.submissions,
    rubric: state.assignmentReducer.rubric,
    loading: state.assignmentReducer.loading,
    error: state.assignmentReducer.error
})

const mapDispatchToProps = dispatch => {
    return { fetchAssignment: (id) => dispatch(FetchAssignmentData(id)) };
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(AssignmentComprehensive);
renakre
  • 8,001
  • 5
  • 46
  • 99

1 Answers1

1

Because of useEffect second argument:

https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

So it runs only once (when the props.loading istrue) and never again.

You seem to have 3 dependencies:

useEffect(() => {
  ...
}, [props.loading, props.fetchAssignment, props.match.params.id])

See also: react-hooks/exhaustive-deps eslint rule.

Aprillion
  • 21,510
  • 5
  • 55
  • 89
  • thanks a lot for the answer! I see the problem for my case. The `return` statement has variables that needs to be set by `useEffect`. but `return` is rendered before `useEffect`, causing the problem. I do not know how to resolve it. Any ideas my friend? – renakre May 05 '19 at 18:10
  • 2
    @renakre Yes that's always the case. You should always be ready to render, You can't stop it. Therefore you should always return some JSX. You can start off with a loading state and then when the fetch is done you render the results, which I believe is what you're doing here and it's totally fine. – Amiratak88 May 05 '19 at 18:17
  • @Amiratak88 I am a bit confused. If I want to return something based on the database values fetched inside `useEffect`, then I will always get error since what I want to return contains undefined variables, which is the problem in my case. Do you understand what I mean? I think I am missing something here. The error I am receiving now is `TypeError: Cannot read property 'title' of undefined` – renakre May 05 '19 at 18:20
  • 2
    @renakre Right therefore you should render something else. (a loading state while the data is being fetched) Obviously something that's not dependent on the data. Something like `
    Loading...
    ` and once you have the data `
    {props.data}
    `
    – Amiratak88 May 05 '19 at 18:23
  • 1
    the `
    Loading...
    ` is already in the OP, I believe editing the second argument of useEffect from `[]` to my suggestion should work... maybe the 3rd one needs to be more defensive: `[props.loading, props.fetchAssignment, props.match && props.match.params && props.match.params.id]`
    – Aprillion May 05 '19 at 18:27
  • 1
    @Aprillion That's a good quick fix. But I think he's confused because he wants to do something else before returning JSX which is because he needs to understand the fundamentals of SPA's and async rendering. – Amiratak88 May 05 '19 at 18:32
  • ah, now I understand the confusion - what is happening with `useEffect` is similar to execution order of `const fn = () => console.log('second'); console.log('first'); fn();`... way more details in https://stackoverflow.com/a/748235/1176601 – Aprillion May 05 '19 at 18:37
  • 2
    @renakre This is a very good [blog](https://overreacted.io/writing-resilient-components) by Dan Abramov I think every React developer should read. If you don't have time read this [part](https://overreacted.io/writing-resilient-components/#principle-1-dont-stop-the-data-flow) which addresses this very issue. – Amiratak88 May 05 '19 at 18:39
  • @Aprillion when I change the `return` statement to this: `props.assignmentIds.map(assignmentId => { return
    {props.assignments[assignmentId].title}
    })` it worked. Do you know why?
    – renakre May 05 '19 at 19:05
  • 1
    while not having a crystal ball, `props.assignments` and `props.assignmentIds` are 2 different props... so the first one could have been `undefined` while loading and the 2nd one haven't? – Aprillion May 05 '19 at 19:10