skip to Main Content

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!

2

Answers


  1. As I see it, there are 2 possible solutions:

    • the best, not always doable
      standardize all the props of the possible components to be rendered and create such a dictionary
    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>
      )
    }
    
    }
    
    • the most flexible, but less optimal
    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,

    Login or Signup to reply.
  2. Just food for thought, because it is alot less commonly used but truly avoids if-else as well as dictionaries (where other solutions still use a dictionary): You could encode the if-else into a data structure and make it almost invisible:

    /* This is the data structure that "stands in" for if-conditionals */
    function Conditional(component, predicate) {
      // A "conditional component" is an object with two fields:
      // - The component, tacked into a thunk
      // - A variadic predicate function
      const it = Object.create(Conditional.prototype);
      it.component = () => component;
      it.predicate = predicate;
      return it;
    }
    
    Conditional.getRenderer = (...conditionals) => (...params) => {
      // Takes N conditional components and returns a renderer function that awaits data
      return conditionals.reduce(
        (result, conditional) => result == null ? conditional.render(...params) : result,
        null
      );
    };
    
    Conditional.prototype.render = function (...params) {
      // Renders a conditional component if the associated predicate accepts the data
      return this.predicate(...params) ? this.component() : null;
    };
    
    
    /* These are "dummy" components */
    const ComponentA = (details) => `A(${details.msg})`;
    const ComponentB = (details) => `B(${details.msg})`;
    const ComponentC = (details) => `C(${details.msg})`;
    
    
    /* The "render" function selects the right component */
    const paramTypes = {
      A: 'a',
      B: 'b'
    };
    
    const render = Conditional.getRenderer(
      Conditional(ComponentA, (a, b) => a === paramTypes.A && b < 2),
      Conditional(ComponentB, (a) => a === paramTypes.B),
      Conditional(ComponentC, () => true)
    );
    
    
    
    /* Try it */
    console.log(render('a', 0)({ msg: 'details' })); // Should be 'A(details)'
    console.log(render('b')({ msg: 'details' }));    // Should be 'B(details)'
    console.log(render()({ msg: 'details' }));       // Should be 'C(details)'

    Note that there is a kind of "fallback" component involved (ComponentC)
    which has a predicate associated that always returns true. When calling getRenderer
    this component must be the last one entered!

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