Home > Blockchain >  react typescript: use "Object.freeze" (enum) as type
react typescript: use "Object.freeze" (enum) as type

Time:01-19

I have this "enum":

export const AuthEnum = Object.freeze({
      AUTHENTICATED: 1,
    UNAUTHENTICATED: 2,
    PENDING: 3
})

This would work but is kind of misleading:

const [isAuthenticated, setIsAuthenticated] = useState<number>(AuthEnum.PENDING);

This is what I would like to do, but which doesn't work:

const [isAuthenticated, setIsAuthenticated] = useState<AuthEnum>(AuthEnum.PENDING);

This doesn't work either:

const [isAuthenticated, setIsAuthenticated] = useState<typeof AuthEnum>(AuthEnum.PENDING);

Is it possible to somehow indicate that the type of the state is AuthEnum?

CodePudding user response:

This useState<AuthEnum>(AuthEnum.PENDING) does not work because AuthEnum is a runtime value and it is used as a type. It might be allowed to use in this way only if AuthEnum would be an enum.

This const [isAuthenticated, setIsAuthenticated] = useState<typeof AuthEnum>(AuthEnum.PENDING); does not work because typeof AuthEnum is an object and you are passing a AuthEnum.PENDING which is a number.

In fact, you want to use only values of AuthEnum. In order to do that, first of all you should use as const assertion to narrow the type of object values.

import React, { useState } from 'react'

const FakeEnum = {
    AUTHENTICATED: 1,
    UNAUTHENTICATED: 2,
    PENDING: 3
} as const // immutability assertion

export const AuthEnum = Object.freeze(FakeEnum)

type Values<T> = T[keyof T]

const App = () => {
    const [isAuthenticated, setIsAuthenticated] = useState<Values<typeof AuthEnum>>(AuthEnum.PENDING);

    setIsAuthenticated(1) // ok
    setIsAuthenticated(2) // ok
    setIsAuthenticated(3) // ok
    setIsAuthenticated(4) // expected error

    return null
}

Playground

Values utility type return a union of all object values.


What's the reason for having "as const" ?

Without as const AuthEnum becomes just:

Readonly<{
    AUTHENTICATED: number;
    UNAUTHENTICATED: number;
    PENDING: number;
}>

Whereas with as const - all object values are strictly narrowed:

Readonly<{
    readonly AUTHENTICATED: 1;
    readonly UNAUTHENTICATED: 2;
    readonly PENDING: 3;
}>

as const makes your object not only immutable but also narrows all value types.

This is the reason why author of the question is used immutable object instead of enum. Since you have only 3 properties : 1, 2, 3 why would you allow to call setIsAuthenticated function with any other arguments? It is clear that calling setIsAuthenticated(1000) is invalid and should be highlighted as an error.

Please see this example with regular enum:

enum AuthEnum {
    AUTHENTICATED = 1,
    UNAUTHENTICATED = 2,
    PENDING = 3
}

const App = () => {
    const [isAuthenticated, setIsAuthenticated] = useState<AuthEnum>(AuthEnum.PENDING);
    setIsAuthenticated(100) // no error, but should be

    return null
}

There is no error, but we would expect. This is because values of enum are not infered.

You can check my article about using enums is a safer way.

  •  Tags:  
  • Related