Search code examples
javascriptcssarraysreactjsstyled-components

How can i populate 3 columns with a single array of strings by the order of column


I have an array of strings like this:

const checkboxItems = [
    'a', 'b', 'c',
    'd', 'e', 'f',
    'g', 'h', 'i',
]

and I want to populate three columns by the column order, so the output will be:

a d g
b e h
c f i

I have a wrapper component with css like this:

const CheckboxWrapper = styled('div')`
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
`;

and I need to render checkbox components in three columns with labels from checkboxItems. My component looks like this:

<CheckboxWrapper>
        {
            checkboxItems.map(item => {
                return <Checkbox
                    key={'checkBox'}
                    checked={false}
                    onClick={() => { }}
                >
                    <Label>{item}</Label>
                </Checkbox>
            })
        }
    </CheckboxWrapper>

Note that my current code renders items on this way:

a b c
d e f
g h i

What am I doing wrong? An example will be appreciated!


Solution

  • As you .map() over checkboxItems they're getting rendered in the order of appearance, whereas you need to render them transposed.

    To get desired result, general approach would be to break your source array into chunks of length 3 (the number of columns), transposed according to desired order:

    itemsToRender = Array(Math.ceil(checkboxItems.length/3))
              .fill()
              .map((_, i, s) => 
                Array(3)
                  .fill()
                  .map((_,j) => 
                    checkboxItems[i+j*s.length]))
    

    Following is a live demo of that approach:

    const { render } = ReactDOM,
          rootNode = document.getElementById('root')
          
    const App = () => {
      const checkboxItems = ['First name','Last name','Email','Phone','Address','Gender','City','Postcode','Country/State','Country','Lifetime value','Tickets in period','Revenue in period','Permission to contact','Customer ID','Account ID','AS Customer tags','Product/Event','Price Category','Section','Row','Seat'],
            
            itemsToRender = Array(Math.ceil(checkboxItems.length/3))
              .fill()
              .map((_, i, s) => 
                Array(3)
                  .fill()
                  .map((_,j) => 
                    checkboxItems[i+j*s.length]))
              
      return (
        <div className="wrapper">
          {
            itemsToRender.map((row, rowIdx) => (
              <div key={rowIdx} className="row">
                {
                  row.map((item, itemPos) => item && (
                    <label key={itemPos} className="cell">
                      <input type="checkbox" value={item} />
                      {item}
                    </label>
                  ))
                }
              </div>
            ))
          }
        </div>
      )          
    }
    
    render (
      <App />,
      rootNode
    )
    .wrapper {
      display: flex;
      flex-direction: column;
    }
    
    .row {
      display: flex;
      flex-direction: row;
    }
    
    .cell {
      width: 150px;
      height: 50px;
      margin: 5px;
      background-color: cadetblue;
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: flex-start;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

    Another approach (to proceed with grid layout and rearrange items, using CSS and repeat() function in particular) is to make use of Material-UI styling (which is the case here) and set grid-template-rows dynamically, based on the number of rows it will take to wrap your list:

    const  { render } = ReactDOM,
           { FormControlLabel, Checkbox } = MaterialUI,
           { makeStyles } = MaterialUI
    
    const rootNode = document.getElementById('root')
    
    const useStyles = rowNum => makeStyles({
      wrapper: {
        display: 'grid',
        gridTemplateRows: `repeat(${rowNum}, 1fr)`,
        gridAutoFlow: 'column'
      }
    })
    
    const App = () => {
      const checkboxItems = ['First name','Last name','Email','Phone','Address','Gender','City','Postcode','Country/State','Country','Lifetime value','Tickets in period','Revenue in period','Permission to contact','Customer ID','Account ID','AS Customer tags','Product/Event','Price Category','Section','Row','Seat'],
            classes = useStyles(Math.ceil(checkboxItems.length/3))()
            
      return(
        <div className={classes.wrapper}>
          {
            checkboxItems.map((item, itemIdx) => (
              <FormControlLabel
                key={itemIdx}
                control={
                  <Checkbox
                    name={item}
                  />
                }
                label={item}
              />
            ))
          }
        </div>
      )
    }
    
    render (
      <App />,
      rootNode
    )
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script><div id="root"></div>