Search code examples
reactjstypescriptreact-functional-component

Why is encumbranceByType object possible undefined?


I have a react component that takes in data of type EncumbranceData[] and restructures that data before mapping through it. This restructuring involves creating an empty object of type EncumbrancesByType and creating keys and values for it based on encumbranceData

However, I keep getting an "Object possible undefined" error where it says encumbrancesByType[e.type].push(e.suite). I don't understand how this could be undefined, as none of the fields on these types are optional. This component will always receive data of type EncumbranceData that will always have a type, id, and suite. EncumbranceData cannot be undefined. The object starts as an empty one, but each iteration through the forEach method should initialize each key with a truthy empty array.

How can the object possible be undefined, then, at the push(e.suite) statement?

I can get it to work by just using a bunch of optional operators (encumbrancesByType?.push()), but I feel like that defeats the point of type safety.

type EncumbranceData = {
  id: string;
  suite: string;
  type: string;
};

interface EncumbrancesByType {
  [key: string]: string[];
}

type EncumbranceAlertByTypeProps = {
  id: string;
  suites: string[];
  type: string;
};

const EncumbranceAlertByType: React.FC<EncumbranceAlertByTypeProps> = ({ id, suites, type }) => {
  const renderSuites = suites.map((s) => <span>{s}</span>);
  return (
    <div>
      <div>{type}</div>
      {renderSuites}
    </div>
  );
};

type ConflictingEncumbrancesAlertProps = {
  encumbranceData: EncumbranceData[];
  isOpen: boolean;
  onContinue(): void;
  onCancel(): void;
  suiteIdsToCheck: string[];
  encumbranceTypesToConsider?: EncumbranceType[];
};

const ConflictingEncumbrancesAlert: React.FC<ConflictingEncumbrancesAlertProps> = ({
  encumbranceData,
  isOpen,
  onContinue,
  onCancel,
  suiteIdsToCheck,
  encumbranceTypesToConsider,
}) => {

  const encumbrancesByType: EncumbrancesByType = {}
  encumbranceData.forEach((e) => {
    if (!encumbrancesByType[e.type]) encumbrancesByType[e.type] = [e.suite]
    else encumbrancesByType[e.type].push(e.suite)
  })

  const encumbrancesContent = Object.keys(encumbrancesByType).map((type) => (
    <EncumbranceAlertByType suites={encumbrancesByType[type]} type={type} />
  ));

  return <div>{encumbrancesContent}</div>;
};

export default ConflictingEncumbrancesAlert;


Solution

  • You likely have the noUncheckedIndexedAccess rule enable in your tsconfig. When you have this rule the compiler will always complain on unchecked index access.

    Also, TS won't narrow down (remove the undefined) on index access. In order to have the compiler do that, you'll have to use an intermidiate variable.

    encumbranceData.forEach((e) => {
      const foo = encumbrancesByType[e.type]; // intermediate variable
      if (foo) {
        foo.push(e.suite); // ok 
      }
    });
    

    Playgroud