Friday, February 17, 2017

React responsive layout without CSS

Leave a Comment

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?

2 Answers

Answers 1

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

Answers 2

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>     );   } }); 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment