9

Having a bit of a hard time finding anything about this specific pattern, and I'm not even exactly sure how to describe it in search terms so apologies if this is a duplicate question (although I don't think it is).

I want to output a React layout based on an order passed to the app that the user can set via a settings panel. The idea is that there are a few different containers to output on the page that I want the user to be able to re-arrange. It's important to note that this order is not changeable after the app renders. (I want the user to be able to say "Show me PanelA, PanelC and PanelB in that order")

Now, I've figured out how to accomplish this using the following pattern:

// user-ordered array is passed to the app:
    const settings = {
        layout: [
            "panela",
            "panelb",
            "panelc"
        ]
    }

    class MyComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                // some state
            }
        }
        renderComponentsInOrder() {
            return settings.layout.map(component => {
                switch(component) {
                    case "panela":
                        return 
                            <PanelA {.../* some state */} />
                    case "panelb":
                        return 
                            <PanelB {.../* some state */} />
                    case "panelc":
                        return 
                            <PanelC {.../* some state */} />
                    default: return null
                }
            })
        }

        render() {
            return this.renderComponentsInOrder()
        }
    }

but this strikes me as really inefficient. The order of the components shouldn't need to be re-calculated every time render runs because the order won't change while the app is running. I've tried doing things like memoizing the renderComponentsInOrder method to cache the calculated layout or renaming the values in the settings.layout array and calling them directly, but haven't been able to get anything to work because the components need to update based on state.

Any help or advice would be greatly appreciated. Thanks!

EDIT: Ideally I'm looking for a JS-based solution as compatibility is a bit of an issue and I don't want to rely solely on the browser's implementation of CSS.

Dan Haddigan
  • 103
  • 2
  • 7

2 Answers2

5

A slightly different take on the answer given by Brandon. You could have a function to generate the component based on the state/props:

const settings = {
    layout: [
        "panela",
        "panelb",
        "panelc"
    ]
};

const panels = settings.layout.map(c => {
  switch (c) {
    case "panela": return (props, state) => <PanelA key="a" foo={state.foo} />
    case "panelb": return (props, state) => <PanelB key="b" bar={state.bar} />
  }
});

// Now use panels array to render:

class MyComponent extends React.Component {
  render() {
    const props = this.props;
    const state = this.state;
    const ordered = panels.map(p => p(props, state));

    return <div>{ordered}</div>
  }
}
Evan Trimboli
  • 29,900
  • 6
  • 45
  • 66
2

Method 1:

Just transform the settings array into an array of Components once before you render:

const settings = {
        layout: [
            "panela",
            "panelb",
            "panelc"
        ]
    };

const panels = settings.layout.map(c => {
  switch (c) {
    case "panela": return { Component: PanelA, key: c };
    case "panelb": return { Component: PanelA, key: c };
    ...
  }
});

// Now use panels array to render:

class MyComponent extends React.Component {
  // ...
  renderComponentsInOrder() {
    return panels.map(({Component, key}) => (
      <Component key={key} {.../* some state*/} />
    ));
  }

  // ...
}

Method 2:

Just create a mapping table:

const settings = {
        layout: [
            "panela",
            "panelb",
            "panelc"
        ]
    };

const panelLookup = {
  "panela": PanelA,
  "panelb": PanelB,
   ...
};

// Now use lookup to render:
// Now use panels array to render:

class MyComponent extends React.Component {
  // ...
  renderComponentsInOrder() {
    return settings.layout.map(key => {
      const Component = panelLookup[key];
      return <Component key={key} {.../* some state*/} />;
    });
  }

  // ...
}
Brandon
  • 38,310
  • 8
  • 82
  • 87