2

I'm building a React app with .NET Core and put some constant values in the appsettings.json file (nothing sensitive) and exposing these values as an API controller. My idea is to put the API call in a .js file and expose the values as a constant, so they will be available in all other .js files.

I have the following:

var y = "foo";

function successCallback(resp) {
    y = resp;
    console.log(y); //shows expected value
}

async function readConfig() {
    fetch('api/ClientConfiguration')
        .then(response => {
            if (response.ok) {
                return response.json();
            } else {
                throw new Error('error...')
            }
        })
        .then(responseJson => {
            successCallback(responseJson.apiUrl);
        });
}

readConfig();

console.log(y); //shows "foo"

export const clientConfig = {
    apiUrl: y
};  

I understand that the async nature of fetch makes the property in const clientConfig always have "foo" value. Is there a way to export the value I want (using this or another approach)? Thanks.

dtc
  • 155
  • 1
  • 7

2 Answers2

3

This is a special case of this problem.

It's possible to assign it later:

function successCallback(resp) {
    clientConfig.apiUrl = resp;
}
...
export const clientConfig = {
    apiUrl: null
};  

This shouldn't be done; the result likely won't exist at the moment when module import is used in other modules and there is no way to track when it appears.

A proper way is to export a promise:

export const clientConfig = fetch('api/ClientConfiguration')
.then(response => {
    if (response.ok) {
        return response.json();
    } else {
        throw new Error('error...')
    }
})
.then(responseJson => {
    return { apiUrl: responseJson.apiUrl };
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • You're welcome. Yes, it should originally be a promise. You can unwrap a promise in parent component to be passed as object prop to child components, see another answer for some inspiration. – Estus Flask Aug 23 '18 at 15:55
1

It doesn't look like there's an easy or elegant way of doing this, as is the async nature of things. The way I'd go about it is to make a component which performs the fetching, then have that component pass the value down to children once it's finished.

Here's a roughly-written example. You could make it more flexible using context, which would allow you to pass the value more seamlessly further down the tree, or render props which would allow you to replace the App here with any other component. Lots of ways to accomplish this either way

class ClientConfigProvider extends React.Component {
  state = {
    response: null,
    error: null,
  }

  componentDidMount() {
    fetch("api/ClientConfiguration")
      .then((response) => {
        if (response.ok) {
          return response.json()
        } else {
          this.setState({ error: "oops" })
        }
      })
      .then((response) => {
        this.setState({ response })
      })
  }

  render() {
    if (error) {
      return <div>Error while fetching config: {this.state.error}</div>
    }
    if (response) {
      return <App apiUrl={this.state.response.apiUrl} />
    }
    return <div>Loading...</div>
  }
}
kingdaro
  • 11,528
  • 3
  • 34
  • 38