28

I'm wondering what is the best approach to implement layouting in React app.

Basics

Let's say we want to have 4 components laid out in simple grid. The most basic way would be something like this.

<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
  <A color="red" x={0} y={0} width={width/2} height={height/2} />
  <B color="blue" x={width/2} y={0} width={width/2} height={height/2} />
  <B color="green" x={0} y={height/2} width={width/2} height={height/2} />
  <A color="yellow" x={width/2} y={height/2} width={width/2} height={height/2} />
</svg>

http://codepen.io/anon/pen/OWOXvV?editors=0010

It will work fine, but typing explicit size values is error-prone and not dev-friendly. What if we could use percentage values (0 - 1) instead?

Simple container

const Container = ({x, y, width, height, children}) => {
  return (
    <g transform={`translate(${x}, ${y})`}>
      {React.Children.map(children, (child) => React.cloneElement(child, { // this creates a copy
        x: child.props.x * width,
        y: child.props.y * height,
        width: child.props.width * width,
        height: child.props.height * height
      }))}
    </g>
  );
};

 <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
  <Container width={width} height={height}>{/* one root container is given real pixel size */}
    <Container width={1/2}>{/* it's children recursively use 0-1 coordinates */}
      <A color="red" height={1/2} />
      <B color="green" y={1/2} height={1/2} />
    </Container>
    <Container x={1/2} width={1/2}>
      <B color="blue" height={1/2} />
      <A color="yellow" y={1/2} height={1/2} />
    </Container>
  </Container>
</svg>

http://codepen.io/anon/pen/PWEmVd?editors=0010

In this case we'll allow Container component to map it's children relative values to real pixel values. It's much easier to use.

Layout container

Another step would be to create layout container, f.e. HContainer that simply lays its children horizontally.

const HContainer = ({ x, y, width, height, children }) => {
  const c = React.Children.toArray(children);
  const ratio = width / c.reduce((sum, child) => (sum + child.props.width), 0);
  return (
    <g transform={`translate(${x}, ${y})`}>
      {c.reduce((result, child) => {
        const width = child.props.width * ratio;
        result.children.push(React.cloneElement(child, { // this creates a copy
          x: result.x,
          y: 0,
          width,
          height
        }));
        result.x += width;
        return result;
      }, { x: 0, children: [] }).children}
    </g>
  );
};

<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
  <HContainer width={width} height={height}>{/* one root container is given real pixel size */}
    <Container width={1/4}>{/* it's children recursively use 0-1 coordinates */}
      <A color="red" height={1/2} />
      <B color="green" y={1/2} height={1/2} />
    </Container>
    <VContainer width={3/4}>
      <B color="blue" />
      <A color="yellow" />
      <HContainer height={1/2}>
        <B color="pink" />
        <A color="violet" width={3} />
        <B color="#333" />
      </HContainer>
    </VContainer>
  </HContainer>
</svg>

http://codepen.io/anon/pen/pRpwBe?editors=0010

Responsive components

Let's say we'd like some components be removed when width or height is below some value. You'd probably use conditional rendering like this.

const MinWidth = ({ children, width, minWidth, ... others }) => {
  return minWidth > width ? null : <Container width={width} {... others }>{ children }</Container>;
};

<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
  <HContainer width={width} height={height}>{/* one root container is given real pixel size */}
    <Container width={1/4}>{/* it's children recursively use 0-1 coordinates */}
      <A color="red" height={1/2} />
      <B color="green" y={1/2} height={1/2} />
    </Container>
    <VContainer width={3/4}>
      <B color="blue" />
      <MinHeight height={1} minHeight={80}>
        <A color="yellow" />
      </MinHeight>
      <HContainer height={1/2}>
        <B color="pink" />
        <A color="violet" width={3} />
        <MinWidth width={1} minWidth={60}>
          <B color="#333" />
        </MinWidth>
      </HContainer>
    </VContainer>
  </HContainer>
</svg>

http://codepen.io/anon/pen/dNJZGd?editors=0010

But this leaves empty spaces where skipped components used to be. Layout containers should be able to expand rendered components to fill available space.

Responsive layout

And here's the tricky part. I can see no other way to see if component will render, but to instantiate and render it (and it's children). Then, if I lay 3 child components within avaialable space and discover that 4th should not be rendered I'll have to re-render previous 3. It feels like breaking React flow.

Does anyone have any ideas?

m1gu3l
  • 763
  • 1
  • 6
  • 19
  • Because I'm trying to understand how React component should properly manage its children. Also there are render targets that don't support CSS. – m1gu3l Jan 31 '17 at 17:12
  • 1
    You are trying to implement 2008 year of web development. We named it "table layout".)))) – JerryCauser Feb 17 '17 at 10:21
  • 3
    @m1gu3l Writing your css inline with components will help you help manage your css along react's parent-child relationships. A child's
    shouldn't affect parent's
    too much if you're keeping to best practices. Now, if you have a specific need for css-less react for some reason, maybe due to some technological demand, then we can certainly work with you on this. Otherwise, "react without css" is just an unnecessary anti-pattern.
    – Denny Feb 23 '17 at 19:52
  • @DenTemple , it's more trying to wrap my head around how such thing should be done within React itself. I know how to do it with CSS, it's rather trivial with flexbox, but then - flexbox doesn't work on svg, and f.e. canvas doesn't support css at all. – m1gu3l Feb 24 '17 at 08:38
  • finally, a js developer implementing layouts in its imperative language, oh wait, you used svg instead doing the maths... :P Thanks! – cancerbero May 17 '19 at 13:15

2 Answers2

5

Use flexbox in inline styles. You are already using inline style from the look of your code. Here is some help

J. Mark Stevens
  • 4,911
  • 2
  • 13
  • 18
  • 1
    Flexbox is CSS module and it won't work on React components - only on their HTML DOM representation. That's why I used SVG in examples, SVG doesn't support Flexbox. – m1gu3l Jan 31 '17 at 18:14
0

You can use react-flexbox-grid to layout components easily without touching any CSS.

const {Grid, Row, Col} = require('react-flexbox-grid');

const App = React.createClass({
  render() {
    return (
      <Grid>
        <Row>
          <Col xs={6} md={3}>Hello, world!</Col>
        </Row>
      </Grid>
    );
  }
});
Fan Jin
  • 2,412
  • 17
  • 25