Alright, it took me awhile to even formulate the question for this. This is more related to a design strategy rather than anything.
I have a view that contains a table that displays a list of all users.Each row of the table has an icon on the left that will expand (or collapse) a component called UsersTrainingSummary that just brings a summary of some specific data. See image:
Here is the full code of this view component:
import React from 'react'; import { Card, CardContent, CardHeader, Chip, Divider, Grid, Typography } from '@material-ui/core'; import { gridSpacing } from '../../../store/constant'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableHead from '@material-ui/core/TableHead'; import TableRow from '@material-ui/core/TableRow'; import MuiTableCell from '@material-ui/core/TableCell'; import TableBody from '@material-ui/core/TableBody'; import { makeStyles, withStyles } from '@material-ui/core/styles'; import {Link} from "react-router-dom"; import IconButton from '@material-ui/core/IconButton'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; import moment from 'moment'; import UsersTrainingSummary from './UsersTrainningSummary'; import useLocalStorage from 'react-use-localstorage'; import { useQuery } from '@apollo/client'; import ALL_USERS from '../../../graphql/AllUsers'; import Loader from '../../../component/Loader/Loader'; const useStyles = makeStyles({ table: { minWidth: 350, }, }); const TableCell = withStyles({ root: { borderBottom: "none" } })(MuiTableCell); function createData(userId, username, firstName, lastName, dateJoined, lastLogin, email, isActive, trainings ) { return { userId, username, firstName, lastName, dateJoined, lastLogin, email,isActive, trainings }; } const UserDashboard = () => { const classes = useStyles(); const [storage, setStorage] = useLocalStorage('orgId'); const orgId = storage const { data, error , loading} = useQuery(ALL_USERS, { variables: { orgId: Number(orgId) }, }); function Row(props){ const { row } = props; const [open, setOpen] = React.useState(false); return ( <React.Fragment> <TableRow key={row.userId}> <TableCell> <IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}> {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} </IconButton> </TableCell> <Link to={{pathname: `/users/${row?.userId}`, state: { user: row}}} style={{ textDecoration: 'none' }}> <TableCell className={classes.root} hover={true} component="th" scope="row">{row?.username} </TableCell> </Link> <TableCell>{row.firstName}</TableCell> <TableCell>{row.lastName}</TableCell> <TableCell>{moment(row?.dateJoined).format('MM/DD/YYYY')}</TableCell> <TableCell>{moment(row?.lastLogin).format('MM/DD/YYYY')}</TableCell> <TableCell>{row?.email}</TableCell> <TableCell>{row?.isActive? <React.Fragment>Yes</React.Fragment> : <React.Fragment>No</React.Fragment>}</TableCell> </TableRow> <TableRow> <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> {open && <UsersTrainingSummary userId={row.userId} trainings={row.trainings}/>} </TableCell> </TableRow> </React.Fragment> ) } if(data) { let userList = data.organization?.user const rows = []; if (userList) { userList.map((user) => { rows.push(createData(user.id, user.username, user.firstName, user.lastName, user.dateJoined, user.lastLogin, user.email, user.isActive, user.trainings)) }) } return ( <Grid container spacing={gridSpacing}> <Grid item xs={12}> <Card> <CardHeader title={ <Typography component="div" className="card-header"> List of all trainees </Typography> } /> <Divider/> <CardContent className="p-0"> <TableContainer> <Table className={classes.table} aria-label="simple table"> <TableHead> <TableRow> <TableCell></TableCell> <TableCell>Username</TableCell> <TableCell>First Name</TableCell> <TableCell>Last Name</TableCell> <TableCell>Date Joined</TableCell> <TableCell>Last Login</TableCell> <TableCell>Email</TableCell> <TableCell>Is Active</TableCell> </TableRow> </TableHead> <TableBody> {rows?.length > 0 && rows.map((row) => ( <Row key={row?.userId} row={row}/> ))} </TableBody> </Table> </TableContainer> </CardContent> </Card> </Grid> </Grid> ); } else return ( <Loader />) } export default UserDashboard
Because the query that brings the summary data can be quite slow if you try to bring for too many users at once due to the involved tables in the DB having a huge amount of data, and because most of the time you will only need for one or a few users, I decided to use a lazy strategy: only when the user clicks on that arrow icon, the component UsersTrainingSummary is rendered and there I have the graphql query that brings the data from the backend and also do all the necessary logic to render that Collapse component.
What I am trying to achieve now is to only display that icon for users that actually have something to show e.g query does not bring empty results from BE, because as user it feels a little misleading to me to have that icon just so you can click and see a message “oh…nothing here”. However, since I need to execute the query first in order to know if there’s something to display, it doesn’t seem possible to me unless I perform that query first for all users, which is something I really don’t wanna do, as the weight on performance is much bigger than the weight on user friendly interface.
Or, is there a way I can achieve this?
Advertisement
Answer
As mentioned in the comment, an alternative to querying the DB in order to find all users who have additional information, could be managing the issue on back-end side, by adding a boolean field (e.g. “hasMoreInfo”) to the table that you use to populate the first view and use that value to decide whether to render or not the icon.