skip to Main Content

I have some data which looks like this –

    {
      System: 'VIT0056',
      Value: {
        Start: 3.3,
        End: 3.9
      },
      'Initial Range' : {
        'Start': '1/12/2022',
        'End': '31/12/2022',
      },
      Manager: 'Karl Woods',
      Location: 'Tokyo',
      Price: '$1.50',
    },

I am currently mapping over this object like so –


 {Object.entries(data).map(([key, val]) => {
              if (key === 'Value') {
                return (
                  <S.ValueContainer key={key}>
                    <Label>{key}:</Label>
                    <Text size="small">** DO SOMETHING HERE TO SHOW VALUES **</Text>
                  </S.ValueContainer>
                )
              }

              if (key === 'Initial Range') {
                return (
                  <S.ValueContainer key={key}>
                    <Label>{key}:</Label>
                    <Text size="small">** DO SOMETHING HERE TO SHOW VALUES ** </Text>
                  </S.ValueContainer>
                )
              }

              return (
                <S.ValueContainer key={key}>
                  <Label>{key}:</Label>
                  <Text size="small">{val}</Text>
                </S.ValueContainer>
              )
            }
         )
      }

For the Value and Initial Range keys I want to be able to show them like this –

Value : 3.3 – 3.9

Initial Range: From 01 Dec 2022 To 31 Dec 2022

However I am having trouble accessing the values due to the fact that they are nested objects.

When I put them inside the if statement and try to do val.Start it tells me that –

Property ‘Start’ does not exist on type ‘string | Range’.

But I know that the value I’ll be accessing in this block will only be of type Range. Is there a way to define this?

3

Answers


  1. You can use a type assertion to tell your compiler, that the value actually is a Range

    <Text size="small">{(val as Range).Start} - {(val as Range).End}</Text>
    

    or you can implement a ToString(value: string | Range) function

    function ToString(value: string | Range) {
      //if value is a string, just return the value
      if (typeof value === "string") return value;
    
      //here the compiler knows it must be a Range
      //because it's not a string ...
      return `${value.Start} - ${value.End}`;
    }
    

    and use this in your rendering

    <Text size="small">{ToString(val)}</Text>
    

    Furthermore, you seem to have different types of ranges also. So when using the ToString() approach, you might also need to do a typecheck on your start and end properties

    function ToString(value: string | Range) {
      if (typeof value === "string") return value;
    
      if (typeof value.Start === "number")
        return `${value.Start} - ${value.End}`;
    
     //here you know value is a range and
     //the properties of range are strings
     //(given both always have the same type
    
      //or apply appropriate formatting for your dates
      return `${value.Start} to ${value.End}`;
    
    }
    

    Of course you can also return some renderings instead of only strings. As in the following example

    function ToRender(value: string | Range) {
      //if value is a string, just return the value
      if (typeof value === "string") return (<div>{value}</div>);
    
      //here the compiler knows it must be a Range
      //because it's not a string ...
      return (<div><span>{value.Start}</span> to <span>{value.End}</span></div>)
    }
    
    Login or Signup to reply.
  2. When you pass your object into Object.entries, you are essentially decoupling your keys from the types of their associated values. It looks like the array you are iterating over has type [string, string | Range][], presumably with Range being some custom type defined elsewhere.

    If you want to access properties on your object in a way where the association between keys and value types is maintained, then you should access them through the object instead of using the val argument accessible within your map callback.

    So instead of val.Start, try data[key].Start (once you’ve narrowed the type of key of course).

    Login or Signup to reply.
  3. This is quite a common issue with union types, the reason the error exists is to stop you from trying to access Start when the you have a string for val.

    I would check the type first, like so:

          {Object.entries(data).map(([key, val]) => {
            if (typeof val !== "string") {
              if (key === "Value") {
                return (
                  <S.ValueContainer key={key}>
                    <Label>{key}:</Label>
                    <Text size="small">** DO SOMETHING HERE TO SHOW VALUES **</Text>
                  </S.ValueContainer>
                );
              }
    
              if (key === "Initial Range") {
                return (
                  <S.ValueContainer key={key}>
                    <Label>{key}:</Label>
                    <Text size="small">** DO SOMETHING HERE TO SHOW VALUES ** </Text>
                  </S.ValueContainer>
                );
              }
            }
    
            return (
              <S.ValueContainer key={key}>
                <Label>{key}:</Label>
                <Text size="small">{val}</Text>
              </S.ValueContainer>
            );
          })}
    

    This would then always fall back to just showing the value of val and then for the other parts you are free to access Start and End on the Range type.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search