Search code examples
javascriptreactjssubstrate

Is there a way to use await keyword inside render in React code


I am trying to use the Substrate UI to display the functionalities that are available in my run-time module. The code is written in React. I am trying to create a component that will help me track all the previous states of a structure.

The runtime.template.assets(i) call takes a hex value i as input and returns a structure kitty which has a key parent_hash(having a hex value). I want the while loop to execute till I have reached a special hex value which in this case is "0x11da6d1f761ddf9bdb4c9d6e5303ebd41f61858d0a5647a1a7bfe089bf921be9" which I have converted to String to achieve the termination condition.

Please ignore the <KittyShrieks> tag because it is something that I am using to display some values in the UI. Basically I am unable to do this inside the while loop

var kitty = await runtime.template.kitties(i);
.
.
i = kitty.parent_hash;

The required code

export class TrackCards extends ReactiveComponent {
    constructor(props) {
        super(['hash'])

    }


    unreadyRender() {
        return <span>Nothing to track</span>
    }
    async readyRender() {
      let kitties = [];
        var i = this.state.hash;
        var root_hash = "0x11da6d1f761ddf9bdb4c9d6e5303ebd41f61858d0a5647a1a7bfe089bf921be9";
        var root = "17,218,109,31,118,29,223,155,219,76,157,110,83,3,235,212,31,97,133,141,10,86,71,161,167,191,224,137,191,146,27,233";
        var iter = 0;
        var a;

        while ((root != i.toString())  {


          var kitty = await runtime.template.kitties(i);
          kitties.push(
              <div className="column" key={i}>

                  <KittyShrieks
                  owner_count = {runtime.template.kittyHistoryCount(i)}
                  hash_original = {this.state.hash}
                  />
              </div>
          );
          i = kitty.parent_hash;
        }
        return <div className="ui stackable six column grid">{kitties}</div>;
    }
}

I don't think using async render is a valid thing, but I am doing that because I seem to have no choice but to use the await keyword. So, the problem is that the UI doesnot load and I get the following error:

Uncaught Invariant Violation: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

The UI loads fine if I remove async keyword, var kitty = await runtime.template.kitties(i); statement and insert a break statement at the end of the loop. Therefore, disallowing me to iterate through the loop.

I am kind of new to React and even Javascript in general. Any kind of help would be appreciated.


Solution

  • A component always needs to be able to render something (or null). Here, effectively you are asking React to render a promise, hence your error.

    You should extract this logic into the componentDidMount() life cycle. Taking your code as an example, it could look something like this:

    export class TrackCards extends ReactiveComponent {
      constructor(props) {
          super(['hash'])
    
          this.state = {
            kitties: []
          }
      }
    
      async componentDidMount() {
        const kitties = [];
        var i = this.state.hash;
        var root_hash = "0x11da6d1f761ddf9bdb4c9d6e5303ebd41f61858d0a5647a1a7bfe089bf921be9";
        var root = "17,218,109,31,118,29,223,155,219,76,157,110,83,3,235,212,31,97,133,141,10,86,71,161,167,191,224,137,191,146,27,233";
        var iter = 0;
        var a;
    
        while (root != i.toString())  {
          var kitty = await runtime.template.kitties(i);
          kitties.push(
              <div className="column" key={i}>
                  <KittyShrieks
                  owner_count = {runtime.template.kittyHistoryCount(i)}
                  hash_original = {this.state.hash}
                  />
              </div>
          );
          i = kitty.parent_hash;
        }
        this.setState(
          kitties
        );
      }
    
      render () {
        const { kitties } = this.state;
        return (
          kitties.length 
            ? <div className="ui stackable six column grid">{kitties}</div>
            : <span>Nothing to track</span>
        )
      }
    }
    

    We are initially returning <span>Nothing to track</span>, while you are waiting for kitties to complete.