3

I'm currently fetching data in Component1, then dispatching an action to update the store with the response. The data can be seen in Component2 in this.props, but how can I render it when the response is returned? I need a way to reload the component when the data comes back.

Initially I had a series of functions run in componentDidMount but those are all executed before the data is returned to the Redux store from Component1. Is there some sort of async/await style between components?

class Component1 extends React.Component {
    componentDidMount() {
        this.retrieveData()
    }

    retrieveData = async () => {
        let res = await axios.get('url')
        updateParam(res.data) // Redux action creator
    }
}
class Component2 extends React.Component {
    componentDidMount() {
       this.sortData()
    }

    sortData = props => {
        const { param } = this.props
        let result = param.sort((a,b) => a - b)
    }
}

mapStateToProps = state => {
    return { param: state.param }
}

connect(mapStateToProps)(Component2)

In Component2, this.props is undefined initially because the data has not yet returned. By the time it is returned, the component will not rerender despite this.props being populated with data.

8
  • Render the component when the data is ready reactjs.org/docs/conditional-rendering.html Commented Jul 28, 2020 at 23:48
  • 2
    Components rerender when state or props update. If a new prop object is received by Component2 then it should rerender. If you do something slightly silly like store props into local component state then you'll need to utilize componentDidUpdate or an useEffect with appropriate dependency to update local state. Please update your question with a Minimal, Complete, and Reproducible code example for more help or better assistance. Commented Jul 28, 2020 at 23:56
  • I've updated the question with some code. Commented Jul 29, 2020 at 0:10
  • "...but the view is always one render behind." What are you doing with the result from the sortData function? Commented Jul 29, 2020 at 5:03
  • 1
    appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function. Commented Jul 29, 2020 at 5:18

1 Answer 1

1

Assuming updateParam action creator is correctly wrapped in call to dispatch in mapDispatchToProps in the connect HOC AND properly accessed from props in Component1, then I suggest checking/comparing props with previous props in componentDidUpdate and calling sortData if specifically the param prop value updated.

class Component2 extends React.Component {
    componentDidMount() {
       this.sortData()
    }

    componentDidUpdate(prevProps) {
      const { param } = this.props;
      if (prevProps.param !== param) { // <-- if param prop updated, sort
        this.sortData();
      }
    }

    sortData = () => {
        const { param } = this.props
        let result = param.sort((a, b) => a - b));
        // do something with result
    }
}

mapStateToProps = state => ({
  param: state.param,
});

connect(mapStateToProps)(Component2);

EDIT

Given component code from repository

let appointmentDates: object = {};
class Appointments extends React.Component<ApptProps> {
    componentDidUpdate(prevProps: any) {
        if (prevProps.apptList !== this.props.apptList) {
            appointmentDates = {};
            this.setAppointmentDates();
            this.sortAppointmentsByDate();
            this.forceUpdate();
        }
    }

    setAppointmentDates = () => {
        const { date } = this.props;
        for (let i = 0; i < 5; i++) {
            const d = new Date(
                new Date(date).setDate(new Date(date).getDate() + i)
            );
            let month = new Date(d).toLocaleString("default", {
                month: "long"
            });
            let dateOfMonth = new Date(d).getDate();
            let dayOfWeek = new Date(d).toLocaleString("default", {
                weekday: "short"
            });
            // @ts-ignore
            appointmentDates[dayOfWeek + ". " + month + " " + dateOfMonth] = [];
        }
    };

    sortAppointmentsByDate = () => {
        const { apptList } = this.props;
        let dates: string[] = [];
        dates = Object.keys(appointmentDates);
        apptList.map((appt: AppointmentQuery) => {
            return dates.map(date => {
                if (
                    new Date(appt.appointmentTime).getDate().toString() ===
                    // @ts-ignore
                    date.match(/\d+/)[0]
                ) {
                    // @ts-ignore
                    appointmentDates[date].push(appt);
                }
                return null;
            });
        });
    };

    render() {
        let list: any = appointmentDates;

        return (
            <section id="appointmentContainer">
                {Object.keys(appointmentDates).map(date => {
                    return (
                        <div className="appointmentDateColumn" key={date}>
                            <span className="appointmentDate">{date}</span>
                            {list[date].map(
                                (apptInfo: AppointmentQuery, i: number) => {
                                    return (
                                        <AppointmentCard
                                            key={i}
                                            apptInfo={apptInfo}
                                        />
                                    );
                                }
                            )}
                        </div>
                    );
                })}
            </section>
        );
    }
}

appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function.

Sign up to request clarification or add additional context in comments.

2 Comments

Similar to StackQ's answer, I have to add this.forceUpdate at the end of componentDidUpdate for this to work correctly. I'm comparing a large object in props. Even if I were to compare each property individually, some are initialized as [ ] and [ ] === [ ] is false. This would lead componentDidUpdate to execute unintentionally.
@SimonWong Yes, very similar. I didn't see theirs until after I answered and the page refreshed. Perhaps you can update your question to include complete component code and share what the prop shape is that you're comparing. The pattern of our answers is correct but it seems the issue is getting the conditional test correct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.