0

Say I have a functional React presentation component, like so:

const Functional = (props) => {
  // do some stuff

  return (
    <div>
      // more HTML based on the props
    </div>
  );
}

Functional.propTypes = {
  prop1: React.PropTypes.string.isRequired,
  prop2: React.PropTypes.string.isRequired,
  // ...
};

If I'm using Redux and following the container component pattern, what would be the best way to render a dynamic number of these <Functional/> components inside a wrapper component, based on elements inside a array (which is inside my Redux state)?

E.g. My Redux state might look like this:

{
  functionalItems: [
    {
      prop1: 'x1',
      prop2: 'y1',
      // ...
    },
    {
      prop1: 'x2',
      prop2: 'y2'
    },
    // ... more items
  ],
  // ...
}

So each item in the functionalItems array should correspond to a <Functional/> component, which all get rendered adjacent to each other.

This is the second time I have come across this problem, so I'm hoping that it's common enough that there is good solution out there.

I'll post the solutions I can come up with (but which have undesirable traits), as answers to this question.

pleasedesktop
  • 1,395
  • 3
  • 14
  • 25

2 Answers2

1

I'd like to suggest that you pass the entire array to the wrapper component like this:

const mapStateToProps = (state) => ({
    items: getFunctionalItems(state),
    // ...
});

and then in your Wrapper.jsx, do it like this:

const Wrapper = (props) => {

  const elements = props.items.map((item, index) => {
    <Functional prop1={ item.prop1 } prop2={ item.prop2 } ...
      key={ ... /* you can use index here */ }/>
  });

  return (
    <div>
      { elements }
    </div>
  );

};

...where getFunctionalItems() is an accessor function that is the canonical means of accessing the functional items from the state.

This way, you can handle changes in state structure, or a different rendering layout. (ergo more robust (I think)). And it looks more like following the Single Responsibility Principle.

JD Hernandez
  • 1,785
  • 1
  • 20
  • 29
  • What would be the benefit of passing in the array directly if you don't even use the items directly, i.e. you are still using the `getPropValueX()` functions? – pleasedesktop Jan 09 '17 at 03:56
  • No problem, I thought that might be the case. This is approach is cleaner, but it's actually more brittle in my opinion. You state that it would be robust to changes in state structure, but I would argue the opposite. You are relying in the state structure, as opposed to my solution, which does not. – pleasedesktop Jan 09 '17 at 04:01
  • Oh right, I'm mistaken by thinking that you can easily change the code once the state structure changes (that is what i meant by "you" in "you can handle changes in state struct"). So I assume that you plan to "hot-swap" container components with different sorts of state? – JD Hernandez Jan 09 '17 at 04:05
  • Not planning on hot-swapping as you hinted at, I just thought it was better practice to add a layer of indirection when accessing data from the state/store, so that if you change structure, you just change the accessor once, as opposed to changing all the direct accesses into the state/store. I.e. The analogy being that in OO you are encouraged to use accessor/mutator methods or properties, instead of directly working with class variables (outside of the class). This comes at a cost of some boilerplate of course. – pleasedesktop Jan 09 '17 at 04:12
  • Now I understand. Then your [answer](http://stackoverflow.com/a/41540621/2511541) must be the closest one since you're after the mutator/accessor design. Anyway, you can use the map function to make the code concise. – JD Hernandez Jan 09 '17 at 04:17
  • I have edited your answer slightly to get the best of both worlds. Are you able to see it and hence peer review it? – pleasedesktop Jan 09 '17 at 04:21
  • yeah. I think that's better. approved it – JD Hernandez Jan 09 '17 at 04:23
  • Thanks. So far I think this is best solution. I'll leave it open for a while, to see if a better one comes along. If not, then I'll mark yours as the accepted answer. – pleasedesktop Jan 09 '17 at 04:32
  • @pleasedesktop good day, are there any updates on this? I just want to know if a better solution has been created as I am currently dealing with this problem in my project. – JD Hernandez Jan 20 '17 at 03:43
0

Solution Description

  • Create a wrapper functional presentation component that takes in a quantity and functions to fetch the <Functional/> prop values.
  • Create a container component that connects to this new wrapper component and passes in the quantity (based on the Redux state) and accessor functions to fetch the <Functional/> prop values.

Example Code

Wrapper.jsx:

const Wrapper = (props) => {

  const elements = [];

  for (let i = 0; i < props.quantity; i++) {
    elements.push(
      <Functional prop1={ getPropValue1(i) } prop2={ getPropValue2(i) } ...
                  key={ ... }/>
    );
  }

  return (
    <div>
      { elements }
    </div>
  );
};

Wrapper.propTypes = {
  quantity: React.PropTypes.number.isRequired,
  getPropValue1: React.PropTypes.func.isRequired,
  getPropValue2: React.PropTypes.func.isRequired,
  // ...
};

ContainerComponent.js:

const mapStateToProps = (state) => ({
  quantity: state.functionalItems.length,
  getPropValue1: (index) => state.functionalItems[index].prop1,
  getPropValue2: (index) => state.functionalItems[index].prop2,
  // ...
});

const ContainerComponent = connect(mapStateToProps)(Wrapper);
pleasedesktop
  • 1,395
  • 3
  • 14
  • 25
  • This is the "best" solution I can come up with, but it still seems kind of brittle/clunky to me. Is there a more elegant approach? – pleasedesktop Jan 09 '17 at 03:33