React - Json Schema Form dropdowns won't load initally unless I use SetTimeout function

1k views Asked by At

This has been driving me and my team crazy. Here is the relevant code.

In the component's CDM we have:

    componentDidMount() {
    this.getContextID();
    this.getConsumerID();
    this.getEnvType();

    //setTimeout(() => this.setState({ populatedMultiSchema: this.multiSchema }), 200);
    //setTimeout(() => this.setState({ populatedMultiUISchema: this.multiUISchema }), 200);

     this.setState({ populatedMultiSchema: this.multiSchema });
     this.setState({ populatedMultiUISchema: this.multiUISchema });

}

so any one of the 3 methods listed will fetch the data for the dropdown. Here is an example of one (they are all basically the same).

getContextID() {
    contextIDOptions = [];
    console.log("CONVERT_TARGET::", this.props.fetchTarget)
    return (
        fetch(this.props.fetchTarget + "Configuration/ContextIDs", {
            method: 'GET',
            //mode: 'cors',
            credentials: 'include',
        }).then(response => {
            if (response.status >= 400) {
                this.setState({
                    value: 'no response - status > 400'
                });
                throw new Error('no response - throw');
            }
            return response.json()
        }).then(function (json) {
            for (var contextID = 0; contextID < json.List.length; contextID++) {
                contextIDOptions.push(json.List[contextID]);
            }
            this.setState({ contextIDArray: contextIDOptions });

            console.log("got contextIDs");
        }.bind(this)).catch(() => {
            this.setState({
                value: 'no response - cb catch'
            })
        })
    )
}

So we set the state there to 'contextIDArray'.

Then the JSON Schema form through it's multiUISchema Object has references to these widgets that help set the values for the form.

ContextIDWidget = (props) => {
    return (
        <div>
            <input type="text" placeholder="Select one..." className="form-control" list="contextIDSelect" onChange={(event) => props.onChange(event.target.value)} />
            <datalist id="contextIDSelect">
                {this.state.contextIDArray.map((value, index) => { return <option key={index} value={value}>{value}</option> })}
            </datalist>
        </div>
    )
}

This is the multiUISchema object (the part that matters for this discussion)

multiUISchema = {
    file: {
        'ui:widget': this.MultiFileWidget,
        classNames: "uiSchema"
    },

    contextID: {
        'ui:widget': this.ContextIDWidget,
        classNames: "uiSchema"
    },

}

And finally here it is in the return in the component.

        return (
        <div className="container" >
            <Form
                schema={this.state.populatedMultiSchema}
                uiSchema={this.state.populatedMultiUISchema}
                formData={this.state.formData}
                onChange={({ formData }) => { this.setState({ formData }); this.setState({ totalFileSize: this.getMultiFileSize() }); this.checkConversionSupport() }}
                onSubmit={this.handleSubmit}
            >

So long story short, if Im using state object in the form, and Im doing setState on the objects Im using. Why do I always get a blank dropdown when I first load the component. Shouldn't the DOM (the dropdown in this case) get repainted with the updated data from the fetches when the state object is changed? I have console logs that show the fetched data in my inspection window so I know the data has been fetched. This is tab component. If I leave the tab or navigate to another page in my SPA and then go back to this page, then the dropdowns are all fully loaded. But I can never get it just load initially unless I set these timeouts in CDM instead of just setting state.

   setTimeout(() => this.setState({ populatedMultiSchema: this.multiSchema }), 200);
      setTimeout(() => this.setState({ populatedMultiUISchema: this.multiUISchema }), 200);

I know the post is long but felt I needed to include all the parts to get help with this. I can assure you we have been trying to resolve the issue for over a week. We welcome any comments. Thanks!

1

There are 1 answers

6
alioguzhan On BEST ANSWER

I am not fully familiar to your codebase. But it looks like something related about asynchronous requests. Here:

this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });

These two lines will be executed BEFORE these ones:

this.getContextID();
this.getConsumerID();
this.getEnvType();

But you expect them to get executed in reverse order. No. Your getContextID method making a request to a server. Javascript is asynchronous. But, by using await expression in an asynchronous function, you can pause the execution and wait for the Promise.

So, just update your componentDidMount method as below:

async componentDidMount() {
    await this.getContextID();
    await this.getConsumerID();
    await this.getEnvType();

    this.setState({ populatedMultiSchema: this.multiSchema });
    this.setState({ populatedMultiUISchema: this.multiUISchema });

}

Here i created a Codepen on usage of async/await. There are some details in comments. You can play with it as you want.

Even if your problem is not caused mainly by this, this approach is better. You should either use async/await or Promise to work with network requests.