Baspa
Baspa

Reputation: 1168

Perform two functions simultaneously

I have a function that refreshes the data of my component when the function is called. At this moment it only works for one component at a time. But I want to refresh two components at once. This is my refresh function:

   fetchDataByName = name => {
        const { retrievedData } = this.state;
        const { fetcher } = this.props;
        const fetch = _.find(fetcher, { name });

        if (typeof fetch === "undefined") {
          throw new Error(`Fetch with ${name} cannot be found in fetcher`);
        }

        this.fetchData(fetch, (error, data) => {
          retrievedData[name] = data;
          this._isMounted && this.setState({ retrievedData });
        });
      };

My function is called like this:

refresh("meetingTypes");

As it it passed as props to my component:

return (
        <Component
          {...retrievedData}
          {...componentProps}
          refresh={this.fetchDataByName}
        />
      );

I tried passing multiple component names as an array like this:

const args = ['meetingTypes', 'exampleMeetingTypes'];
refresh(args);

And then check in my fetchDataByName function if name is an array and loop through the array to fetch the data. But then the function is still executed after each other instead of at the same time. So my question is:

What would be the best way to implement this that it seems like the function is executed at once instead of first refreshing meetingTypes and then exampleMeetingTypes?

Should I use async/await or are there better options?

The fetchData function:

fetchData = (fetch, callback) => {
    const { componentProps } = this.props;
    let { route, params = [] } = fetch;
    let fetchData = true;

    // if fetcher url contains params and the param can be found
    // in the component props, they should be replaced.
    _.each(params, param => {
      if (componentProps[param]) {
        route = route.replace(`:${param}`, componentProps[param]);
      } else {
        fetchData = false; // don't fetch data for this entry as the params are not given
      }
    });

    if (fetchData) {
      axios
        .get(route)
        .then(({ data }) => {
          if (this.isMounted) {
            callback(null, data);
          }
        })
        .catch(error => {
          if (error.response.status == 403) {
            this._isMounted && this.setState({ errorCode: 403 });
            setMessage({
              text: "Unauthorized",
              type: "error"
            });
          }

          if (error.response.status == 401) {
            this._isMounted && this.setState({ errorCode: 401 });
            window.location.href = "/login";
          }

          if (error.response.status != 403) {
            console.error("Your backend is failing.", error);
          }
          callback(error, null);
        });
    } else {
      callback(null, null);
    }
  };

Upvotes: 0

Views: 220

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074208

I assume fetchData works asynchronously (ajax or similar). To refresh two aspects of the data in parallel, simply make two calls instead of one:

refresh("meetingTypes");
refresh("exampleMeetingTypes");

The two ajax calls or whatever will run in parallel, each updating the component when it finishes. But: See the "Side Note" below, there's a problem with fetchDataByName.

If you want to avoid updating the component twice, you'll have to update fetchDataByName to either accept multiple names or to return a promise of the result (or similar) rather than updating the component directly, so the caller can do multiple calls and wait for both results before doing the update.


Side note: This aspect of fetchDataByName looks suspect:

fetchDataByName = name => {
  const { retrievedData } = this.state;  // <=============================
  const { fetcher } = this.props;
  const fetch = _.find(fetcher, { name });

  if (typeof fetch === "undefined") {
    throw new Error(`Fetch with ${name} cannot be found in fetcher`);
  }

  this.fetchData(fetch, (error, data) => {
    retrievedData[name] = data;          // <=============================
    this._isMounted && this.setState({ retrievedData });
  });
};

Two problems with that:

  1. It updates an object stored in your state directly, which is something you must never do with React.
  2. It replaces the entire retrievedData object with one that may well be stale.

Instead:

fetchDataByName = name => {
  // *** No `retrievedData` here
  const { fetcher } = this.props;
  const fetch = _.find(fetcher, { name });

  if (typeof fetch === "undefined") {
    throw new Error(`Fetch with ${name} cannot be found in fetcher`);
  }

  this.fetchData(fetch, (error, data) => {
    if (this._isMounted) {                                  // ***
      this.setState(({retrievedData}) => (                  // ***
        { retrievedData: {...retrievedData, [name]: data} } // ***
      );                                                    // ***
    }                                                       // ***
  });
};

That removes the in-place mutation of the object with spread, and uses an up-to-date version of retrievedData by using the callback version of setState.

Upvotes: 1

Related Questions