1

I need to pre-process data that comes from API. The raw data comes in the following format:

enter image description here

I want to dynamically build a Table, in which the columns should be created using fields task_name and saved_answers. Please notice that saved_answers may contain different sub-fields depending on the task_name. In other words, saved_answers does not always contain value21, value22, value23 and value24.

Below I show an example of a table.

============================================================================================================
| user_id  |  task11-value21  |   task11-value22  |   task11-value23  |  task11-value24  | task13-valueMu...
============================================================================================================
| 111      |  1               |   1               |   1               |  1               | 5
... 

So far I only can retrieve the data from API and show specific columns in the table. I would highly appreciate any help. Thanks.

import React, { useEffect } from 'react'
import { makeStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import axios from 'axios'
import config from '../../config/config.json';

const useStyles = makeStyles((theme) => ({
    root: {
        width: '100%',
    },
    heading: {
        fontSize: theme.typography.pxToRem(18),
        fontWeight: theme.typography.fontWeightBold,
    },
    content: {
        fontSize: theme.typography.pxToRem(14),
        fontWeight: theme.typography.fontWeightRegular,
        textAlign: "left",
        marginTop: theme.spacing.unit*3,
        marginLeft: theme.spacing.unit*3,
        marginRight: theme.spacing.unit*3
    },
    table: {
        minWidth: 650,
    },
    tableheader: {
        fontWeight: theme.typography.fontWeightBold,
        color: "#ffffff",
        background: "#3f51b5"
    }
}));


export function Main() {

    const [groupKey,setGroupKey] = React.useState([]);

    const classes = useStyles();

    const options = {
        'headers': {
            'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
        }
    }

    useEffect(() => {
        axios.get(config.api.url + '/api/test', options)
            .then( (groups) => {
                setGroupKey(groups);
                console.log(groups);
            })
            .catch( (error) => {
                console.log(error);
            })
    }, []);

    return (
        <div className={classes.root}>

            <Grid container spacing={3}>
                <Grid item xs={12} className={classes.content}>
                    <TableContainer component={Paper}>
                        <Table className={classes.table}>
                            <TableHead>
                                <TableRow>
                                    <TableCell className={classes.tableheader}>Group</TableCell>
                                    <TableCell className={classes.tableheader} colSpan={3}>Task name</TableCell>
                                </TableRow>
                            </TableHead>
                            <TableBody>
                                {groupKey.map( (row, index) => (
                                    <TableRow key={index} selected="false">
                                        <TableCell>{row.user_id}</TableCell>
                                        <TableCell>{row.task_name}</TableCell>
                                    </TableRow>))}
                            </TableBody>
                        </Table>
                    </TableContainer>

                </Grid>    
            </Grid>
        </div>
    )
}

UPDATE:

import '../../App.css';
import React, { useEffect } from 'react'
import PropTypes from 'prop-types';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import TablePagination from '@material-ui/core/TablePagination';
import TableFooter from '@material-ui/core/TableFooter';
import IconButton from '@material-ui/core/IconButton';
import FirstPageIcon from '@material-ui/icons/FirstPage';
import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
import LastPageIcon from '@material-ui/icons/LastPage';
import Paper from '@material-ui/core/Paper';
import axios from 'axios'
import config from '../../config/config.json';

const useStyles = makeStyles((theme) => ({
    root: {
        width: '100%',
    },
    heading: {
        fontSize: theme.typography.pxToRem(18),
        fontWeight: theme.typography.fontWeightBold,
    },
    content: {
        fontSize: theme.typography.pxToRem(14),
        fontWeight: theme.typography.fontWeightRegular,
        textAlign: "left",
        marginTop: theme.spacing.unit*3,
        marginLeft: theme.spacing.unit*3,
        marginRight: theme.spacing.unit*3
    },
    table: {
        minWidth: 650,
    },
    tableheader: {
        fontWeight: theme.typography.fontWeightBold,
        color: "#ffffff",
        background: "#3f51b5"
    },
    tableCell: {
        color: "#000000",
        background: "#ffffff"
    },
    button: {
        fontSize: "12px",
        minWidth: 100
    },
}));


function TablePaginationActions(props) {
    const classes = useStyles();
    const theme = useTheme();
    const { count, page, rowsPerPage, onChangePage } = props;
  
    const handleFirstPageButtonClick = (event) => {
      onChangePage(event, 0);
    };
  
    const handleBackButtonClick = (event) => {
      onChangePage(event, page - 1);
    };
  
    const handleNextButtonClick = (event) => {
      onChangePage(event, page + 1);
    };
  
    const handleLastPageButtonClick = (event) => {
      onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
    };
  
    return (
      <div className={classes.root}>
        <IconButton
          onClick={handleFirstPageButtonClick}
          disabled={page === 0}
          aria-label="first page"
        >
          {theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
        </IconButton>
        <IconButton onClick={handleBackButtonClick} disabled={page === 0} aria-label="previous page">
          {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
        </IconButton>
        <IconButton
          onClick={handleNextButtonClick}
          disabled={page >= Math.ceil(count / rowsPerPage) - 1}
          aria-label="next page"
        >
          {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
        </IconButton>
        <IconButton
          onClick={handleLastPageButtonClick}
          disabled={page >= Math.ceil(count / rowsPerPage) - 1}
          aria-label="last page"
        >
          {theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
        </IconButton>
      </div>
    );
  }


  function getGridHeader(config) {
    const gridHeader = ["user_id"];
    console.log(config);
    const savedAnswers = Object.keys(JSON.parse(config.saved_answers));
    savedAnswers.map(savedAnswer => {
      gridHeader.push(`${config.task_name}-${savedAnswer}`)
    });
    return gridHeader;
  }

  
  function getGridData(config) {
    const gridData = [config.user_id];
    const savedAnswers = JSON.parse(config.saved_answers);
    Object.keys(savedAnswers).map( savedAnswer => {
      gridData.push(savedAnswers[savedAnswer]);
    });
    return gridData;
  }

  
  TablePaginationActions.propTypes = {
    count: PropTypes.number.isRequired,
    onChangePage: PropTypes.func.isRequired,
    page: PropTypes.number.isRequired,
    rowsPerPage: PropTypes.number.isRequired,
  };


export function Main() {

    const [groupKey,setGroupKey] = React.useState([]);
    const [page, setPage] = React.useState(0);
    const [rowsPerPage, setRowsPerPage] = React.useState(5);
    const emptyRows = rowsPerPage - Math.min(rowsPerPage, groupKey.length - page * rowsPerPage);

    const gridHeader = getGridHeader(groupKey);
    const gridData = getGridData(groupKey);

    const handleChangePage = (event, newPage) => {
        setPage(newPage);
    };

    const handleChangeRowsPerPage = (event) => {
        setRowsPerPage(parseInt(event.target.value, 10));
        setPage(0);
    };

    const classes = useStyles();

    const options = {
        'headers': {
            'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
        }
    }

    useEffect(() => {
        axios.get(config.api.url + '/api/test', options)
            .then( (groups) => {
                setGroupKey(groups.data.subtask);
            })
            .catch( (error) => {
                console.log(error);
            })
    }, []);

    return (
        <div className={classes.root}>

            <Grid container spacing={3}>
                <Grid item xs={12} className={classes.content}>

                <TableContainer component={Paper}>
                    <Table className={classes.table}>
                        <TableHead>
                            <TableRow className={classes.tableheader}>
                                {gridHeader.map( (headerTitle, index) => (
                                <TableCell key={index}>{headerTitle}</TableCell>
                                ))}
                            </TableRow>
                        </TableHead>
                        <TableBody>
                        {(rowsPerPage > 0
                            ? gridData.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
                            : gridData
                        ).map((cellValue, index) => (
                            <TableRow key={index} selected="false">
                                <TableCell className={classes.tableCell}component="th" scope="row">{cellValue}</TableCell>
                            </TableRow>
                        ))}

                        {emptyRows > 0 && (
                            <TableRow style={{ height: 53 * emptyRows }}>
                            <TableCell colSpan={6} />
                            </TableRow>
                        )}
                        </TableBody>
                        <TableFooter>
                            <TableRow>
                                <TablePagination
                                    rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]}
                                    colSpan={3}
                                    count={groupKey.length}
                                    rowsPerPage={rowsPerPage}
                                    page={page}
                                    SelectProps={{
                                        inputProps: { 'aria-label': 'groups per page' },
                                        native: true,
                                    }}
                                    onChangePage={handleChangePage}
                                    onChangeRowsPerPage={handleChangeRowsPerPage}
                                    ActionsComponent={TablePaginationActions}
                                />
                            </TableRow>
                        </TableFooter>
                    </Table>
                    </TableContainer>

                </Grid>    
            </Grid>
        </div>
    )
}
4
  • Your grid header contains only task_name prefix of the first object: task11_value21, task11_value22, task11_value33.. What about task12 from the second object and other tasks? Commented Aug 6, 2020 at 11:20
  • @ArthurRubens: I need all object to appear in the table. Just it would be long to put all headers in the example. Therefore I wrote ... (which means etc). So, you are right: task12 and other tasks should appear in the header. Commented Aug 6, 2020 at 11:45
  • But in this case you will have very wide grid with one row, do I understand correctly? Commented Aug 6, 2020 at 11:48
  • @ArthurRubens: Yes, the number of columns is approximately 20-30. The number of rows will depend on user_id. Each user_id is represented by a single row with many columns. This is the idea. Commented Aug 6, 2020 at 11:59

1 Answer 1

1

So, the algorithm is the following:

  1. generate header array from the raw data
  2. generate array of arrays for table body data based on user_id

I hope you can do it.

Using this two arrays you can iterate over and create your data table, something like:

enter image description here

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

const useStyles = makeStyles({
  table: {
    minWidth: 650,
  },
});

function generateRandomSavedAnswer(rowIndex, maxNumberOfSavedAnswers) {
  //const numberOfSavedAnswers = Math.floor(Math.random()*maxNumberOfSavedAnswers);
  const savedAnswers = {};
  for(let i=0; i<maxNumberOfSavedAnswers; i++) {
    savedAnswers[`value${rowIndex}${i}`] = rowIndex+i;
  }
  return savedAnswers;
}
function generateGridRawData() {
  let gridData = [];
  for(let i=0; i<10; i++) {
    gridData.push({
      saved: true,
      saved_answers: JSON.stringify(generateRandomSavedAnswer(10, 5)),
      submited: true,
      task_name: 'task12',
      user_id: Math.floor(Math.random()*3),
      __v: 0,
      _id: Math.round(Math.sin(i*0.01)*100)
    });
  }
  return gridData;
}

function getGridHeader(config) {
  const gridHeader = ["user_id"];
  const savedAnswers = Object.keys(JSON.parse(config.saved_answers));
  savedAnswers.map(savedAnswer => {
    gridHeader.push(`${config.task_name}-${savedAnswer}`)
  });
  return gridHeader;
}

function getGridData(config) {
  const gridData = [config.user_id];
  const savedAnswers = JSON.parse(config.saved_answers);
  Object.keys(savedAnswers).map( savedAnswer => {
    gridData.push(savedAnswers[savedAnswer]);
  });
  return gridData;
}

export default function SimpleTable() {
  const classes = useStyles();
  const gridRawData = generateGridRawData();
  const gridHeader = getGridHeader(gridRawData[0]);
  const gridData = getGridData(gridRawData[0]);

  return (
    <TableContainer component={Paper}>
      <Table className={classes.table} aria-label="simple table">
        <TableHead>
          <TableRow>
            {gridHeader.map( (headerTitle, index) => (
              <TableCell key={index}>{headerTitle}</TableCell>
            ))}
          </TableRow>
        </TableHead>
        <TableBody>
          <TableRow>
            {gridData.map( (cellValue, index) => (
              <TableCell key={index}>{cellValue}</TableCell>
            ))}
          </TableRow>
        </TableBody>
      </Table>
    </TableContainer>
  );
}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks. I have a couple of questions regarding your answer. 1) Why do you use [0] in gridRawData[0]?; 2) I get the error TypeError: Cannot read property 'saved_answers' of undefined when I do const gridHeader = getGridHeader(groupKey[0]);.
May it happen that axios function is executed afterwards? I can paste my new code if needed.
Please check my UPDATE. I posted my current code based on your suggestions. I get the error: Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development.
You just need to re-write the getGridHeader and getGridData functions for your needs, it is not ready solution. I just tried to show how to create dynamic grid based on some json data..
The thing is that getGridData and getGridHeader are executed multiple times. When they are executed first time, the result is an empty array. Only when they are executed third time, then they return the data, but the table is not updated. So, I assume that this is the issue of calling functions in a proper sequence.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.