0

I’m using react’s componentDidMount(), which holds a lot of code and does two callbacks. Eventually, in the inner most callback (i.e., the second one), I execute this.setState({}).

Minimal code

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { data: {} };
  }

  requests() {
    fooCallback.request({ data: { query: userQuery } }).subscribe(({ jsonResp1 }) => {

      // 100 lines of data processing with jsonResp1


      // second Callback
      barCallback.request({ data: { string: userString } }).subscribe(({ jsonResp2 }) => {

        // 200 lines of data processing with jsonResp2
        this.setState({ data: processedData })

      });
    });


    componentDidMount() {
      this.requests();
    }
  }

  render() {
    // ...
    return (
          // ...
        );
  }

}

Since the request()method is very huge it bloats my main container component App. I’ve already tried to create an external js file (default export function ...) and imported that in my App.js. Yet, this was not possible probably due to the asynchron nature of the method.

Is it somehow possible to reduce my App.js?

Edit

What I tried in many variants is to create an external.js file:

external.js

export default async () => {
    return fooCallback.request({ data: { query: userQuery } }).subscribe(({ jsonResp1 }) => {

      // 100 lines of data processing with jsonResp1


      // second Callback
      barCallback.request({ data: { string: userString } }).subscribe(({ jsonResp2 }) => {

        // 200 lines of data processing with jsonResp2
        return processedData

      });

    return barCallback;

    });

... and then to import it

App.js

import getData from './js/getData.js'
// ...
async componentDidMount() {
  const response = await this.getData()
  this.setState({data: response})
}

Yet, no success. Is the only way to create a class component, maybe?

Kalaschnik
  • 769
  • 1
  • 7
  • 21
  • I dont really understand the problem, the app is hanging while loading that data? – Prince Hernandez Jan 10 '19 at 18:48
  • @PrinceHernandez I wan’t to reduce the amount of lines in my container component. In my render method everything is done by components loaded externally and doing their things externally. I want the same behavior for the request() method. – Kalaschnik Jan 10 '19 at 18:53
  • as i sort of mentioned in my answer, I think you might want to rethink how you're handling the callbacks and async sort of actions. Try wrapping the callbacks in promises so you can await the results of the callback by resolving the result within the callback – Julian Jan 10 '19 at 19:40

1 Answers1

1

If I understand your question correctly, you want to move the request function to a separate file, but within the function you use "this.setState" which is out of the components scoping.

You're right to move this logic out of the component. Callbacks are a little confusing to work with sometimes, especially when callbacks depend on other callbacks, so on and so forth.

Wouldn't it be nice if callbacks acted synchronous? You can simulate this by wrapping the callbacks in Promises and awaiting the results of the callbacks to resolve. This way, you're program will wait for the first callback to execute entirely and return data before executing the following callback (you were going to nest) that needed the output of the first.

Utility file will look something like:

function processRespOne(fooCallback, userQuery) {
  return new Promise((resolve, reject) => {
    fooCallback.request({ data: { query: userQuery } }).subscribe(({ jsonResp1 }) => {
      // ...process jsonResp1
      // by resolving this data, it acts like a standard function "return" if the invoking expression awaits it
      resolve(processedData);
    });
  });
}

function processRespTwo(respOneData, barCallback, userString) {
  return new Promise((resolve, reject) => {
    barCallback.request({ data: { string: userString } }).subscribe(({ jsonResp2 }) => {
      // process jsonResp2 with the output of respOneData
      resolve(processedData);
    });
  });
}


// here I define the function "async" so i can use the "await" syntax, so my asynchronous function acts synchronous 
// Async functions return promises. So caller's can await this function.
export async function requests(fooCallback, userQuery, barCallback, userString) {
  const respOneData = await processRespOne(fooCallback, userQuery);
  const results = await processRespTwo(respOneData, barCallback, userString)

  // anyone who is "await"ing this function will get back results
  return results;
}

In App.js

import { requests } from './js/getData.js';
//...
   async componentDidMount() {
     const results = await requests(fooCallback, userQuery, barCallback, userString);
     this.setState({ results });
   }

Here's a link to a really good answer discussing converting callbacks into promises for more synchronous like execution. Definitely take a look at the "nodebacks" example in the accepted answer: How do I convert an existing callback API to promises?

Julian
  • 550
  • 1
  • 4
  • 16
  • Thanks @Julian. I’ll try that tomorrow morning. However, the second callback’s input depends on some processed data from the first callback. So I cannot split them... – Kalaschnik Jan 10 '19 at 19:48
  • 1
    oh gotcha, i'll update my answer later as well. Moral of the story is to promisify your callbacks by wrapping them in a promise so your result can be awaited – Julian Jan 10 '19 at 20:00
  • Thank you for the help. I’m still struggling with async stuff... I’m curious to test you code... – Kalaschnik Jan 10 '19 at 20:08
  • 1
    it's very confusing at first but once you get it, node as a whole becomes much more understandable because the most unpredictable part of the language is how async code is handled. I didn't check the code so let me know if it doesn't work, I can make a code pen or something – Julian Jan 11 '19 at 19:21
  • Sorry, that it took so long. You’re approach is working perfect for my case! Thanks a lot! – Kalaschnik Jan 17 '19 at 12:22