javascriptreactjsif-statementresourcedictionarycognitive-complexity

Javascript & React - How to avoid the "if hell" without using dictionary/objects?


I know this question has been asked before, but the usual answers bring another (potential) problem and doubts to my mind.

The context

I've got a function that returns what component has to be rendered depending on two parameters, paramA and paramB. The code, right now, looks something like this:

  if (paramA === paramATypes.PRE) {
    if (paramB === paramBTypes.REQUEST) {
      detailedView = (
        <ComponentA
          requestDetails={requestDetails as RequestDetailsDto<ComponentADto>}
        />
      );
    } else if (paramB === paramBTypes.MODIFICATION) {
      detailedView = (
        <ComponentB
          requestDetails={
            requestDetails as RequestDetailsDto<ComponentBDto>
          }
        />
      );
    }
  } else if (paramA === paramATypes.PRI) {
    if (paramB === paramBTypes.REQUEST) {
      detailedView = (
        <ComponentC
          requestDetails={requestDetails as RequestDetailsDto<ComponentCDto>}
        />
      );
    } else if (paramB === paramBTypes.MODIFICATION) {
      detailedView = (
        <ComponentD
          requestDetails={
            requestDetails as RequestDetailsDto<ComponentDDto>
          }
        />
      );
    } 
  } else if...

This goes on and on, as we have some different types of each, and each of them has a specific component that renders its properties in a certain way. Of course, it has been a bit oversimplified, just to be able to describe the situation.

The thing is, I could try to do something like this, as I usually do with simpler values:

const dict = {
  [paramATypes.PRE]: {
    [paramBTypes.REQUEST]: <ComponentA
      requestDetails={requestDetails as RequestDetailsDto<ComponentADto>}
    />,
    [paramBTypes.MODIFICATION]: <ComponentB
      requestDetails={
        requestDetails as RequestDetailsDto<ComponentBDto>
      }
    />,
  }, 
  ...
}

And then just call it like this:

const view = dict[paramA][paramB];

The problem

The problem I see with this is that with the "if-hell", the values of the components are only processed when the if conditions are met. So, in this case, it will only calculate/process one component per call.

However, if I use the object/dictionary paradigm, it will process all of the values because it needs it to create the actual object to be accessed later on, so each call to this method will calculate all of the possibilities, to just return one of them.

If the values were just declarations or simpler values, I wouldn't have any problem, but being React components I am not so sure.

The question

Am I right with the way it would be processed in both paradigms? What can I do to have a cleaner set of conditions?

Maybe wrap the values definition in a method, so it's only processed when I execute the result of the method, as in const view = dict[paramA][paramB]();?

I'm just wondering which would be the best way to put this so it's not only easier to read, but it also has a good performance (and good cognitive complexity in code analyzers).

Thank you!


Solution

  • As I see it, there are 2 possible solutions:

    const dict = {
      [paramATypes.PRE]: {
        [paramBTypes.REQUEST]: ComponentA,
        [paramBTypes.MODIFICATION]: ComponentB,
      },
    } as const satisfies Record<
      paramATypes,
      Record<paramBTypes, React.FC<{ requestDetails: RequestDetailsDto }>>
    >
    

    And then in the component do something like this:

    const Component = () => {
      const View = dict[paramA][paramB]
    
      return (
        <div>
          <View requestDetails={requestDetails} />
        </div>
      )
    }
    
    }
    
    const Component = () => {
      const dict = {
        [paramATypes.PRE]: {
          [paramBTypes.REQUEST]: () => (
            <ComponentA
              requestDetails={requestDetails as RequestDetailsDto<ComponentADto>}
            />
          ),
          [paramBTypes.MODIFICATION]: () => (
            <ComponentB
              requestDetails={requestDetails as RequestDetailsDto<ComponentBDto>}
            />
          ),
        },
      } as const satisfies Record<paramATypes, Record<paramBTypes, React.FC>>
    
      const View = dict[paramA][paramB]
    
      return (
        <div>
          <View />
        </div>
      )
    }
    
    
    

    in both cases only the correct component is rendered and not all possible components, so performance-wise you are good

    You should try the first option because it makes the props uniform and makes everything more maintainable,