0

I have a React app using Create React App which uses Typescript and I am trying to read in config values, such as API urls. I currently have a config.json which contains this data, here's a sample with fake data to show the gyst of it:

{
  "apiUrl": "https://localhost:3001/",
  "externalUrl": "https://localhost:3002/"
}

The desired outcome

I can build the web app once and deploy it anywhere, updating the config values at deploy time e.g. for dev apiUrl should be https://localhost:4001/, in prod apiUrl should be https://localhost:5001/.

The config should be available in all files and not limited to just inside components (so hooks like useContext won't work) and be Typescript compliant e.g. can access the config values via a typed interface/type.

What I've tried

There is quite a few similar posts to this, such as:

The general suggestion is to put a config.js in the public folder, reference it as a script in index.html and finally call it via the window e.g. window.Config.apiUrl.

This throws an error in Typescript as us adding in this config in the index.html via a script doesn't actually add the type and I can get around this by using // ts-nocheck, though this is not ideal as it disables all checks for the file. Additionally, it would be preferable for the config file to be a type so can confidently reference it.

Troy Poulter
  • 677
  • 1
  • 8
  • 29

2 Answers2

1

Since you are loading the value at runtime there's not really any way for Typescript to "know" about it in your code--and even if it did, the file is, of necessity, going to be a javascript file, not a typescript file (since its going to be read by your runtime, i.e., javascript) so it won't have typing info.

I think the best you can do is a user-defined type guard that performs an actual runtime check. We use that solution in our apps. Something like this (assuming you have already added the object via an import of a javascript file in public/index.html per the links in your question) in your React/Typescript code:

// the strongly type object you want to get out of your config file

type ConfigObject = {
  value1: string
  value2: number
}

// the cast of window is necessary as the default window type 
// won't know about the object you are adding

const runtimeConfigObject = (window as any).configObject

if(isConfigObject(runtimeConfigObject) {
  // use it, TS will now have the correct type
} else {
  // handle error
}

function isConfigObject(obj: unknown): obj is ConfigObject {
  return typeof obj.value1 === "string" && typeof obj.value2 === "number"
}
sam256
  • 1,291
  • 5
  • 29
0

You can create a file window.d.ts under src/typings with the following:

declare interface Window {
  configObject: {
    value1: string;
    value2: number;
  }
}

and reference it from your tsconfig.json under:

"typeRoots": ["./src/typings", "./node_modules/@types"]

It's still your responsability to ensure that object has the expected structure and type, but at least you can access it avoiding any.

ejose19
  • 539
  • 3
  • 6