how to push an object to a state array based on user input

Viewed 624

I have a table with textbox on each row and want to identify each of them

I need to change the "task" and "setTask" from simple string to an object array that can take user inputs for each row and add it as object {id, value} (to use later)

Here is a working example: https://codesandbox.io/s/dawn-wildflower-cfvol?file=/src/App.js

  1. Click on "Start Scan" button (it will load the grid)
  2. Click on any one of the "Write a task name" (it will open up for all records)

What I need is: open up for a particular record only, for which handleChange needs to push an object into the array instead of string

Code:

import { useState } from "react";
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 { makeStyles, withStyles } from "@material-ui/core/styles";
import { Typography } from "@material-ui/core";
import Button from "@material-ui/core/Button";

const useStyles = makeStyles((theme) => ({
container: {
  maxHeight: 440,
  width: "100%"
},
}));

const StyledTableCell = withStyles((theme) => ({
head: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white
},
body: {
fontSize: 14
}
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
root: {
"&:nth-of-type(odd)": {
  backgroundColor: theme.palette.action.hover
}
}
}))(TableRow);

 export default function CommentTest(
 )  {
const classes = useStyles();
const [bulkScanList, setBulkScanList] = useState([]);
const [name, setname] = useState([]);  
const [openId, setOpenId] = useState();
const [editMode, setEditMode] = useState(false);
const [task, setTask] = useState("");
const data = {
samples: [
  {
    sampleContainers: [
      {
        id: 4447,
        sampleId: 2656,
        code: "00JC",
        color: "Green",
        name: "250g Soil Jar",
        description: "250g Soil Jar",
        containerId: 1,
        comments: "",
        barcode: "00JC001180",
        testLists: null,
        isStandardContainer: true,
        preservationType: "none",
        scanStatus: 1,
        srComments: null
      }
    ],
    id: 2656,
    batchId: 1499,
    sampleName: "COC_SCA",
    dateSampled: "2021-12-21T10:46:00",
    matrixId: 2
  },
  {
    sampleContainers: [
      {
        id: 4448,
        sampleId: 2657,
        code: "00JC",
        color: "Green",
        name: "250g Soil Jar",
        description: "250g Soil Jar",
        containerId: 1,
        comments: "",
        barcode: "00JC001182",
        testLists: null,
        isStandardContainer: true,
        preservationType: "none",
        scanStatus: 1,
        srComments: null
      }
    ],
    id: 2657,
    batchId: 1499,
    sampleName: "COC_SCA",
    dateSampled: "2021-12-21T10:46:00",
    matrixId: 2
  },
  {
    sampleContainers: [
      {
        id: 4449,
        sampleId: 2658,
        code: "00JC",
        color: "Green",
        name: "250g Soil Jar",
        description: "250g Soil Jar",
        containerId: 1,
        comments: "",
        barcode: "00JC001179",
        testLists: null,
        isStandardContainer: true,
        preservationType: "none",
        scanStatus: null,
        srComments: "check"
      }
    ],
    id: 2658,
    batchId: 1499,
    sampleName: "COC_SCA",
    dateSampled: "2021-12-21T10:46:00",
    matrixId: 2
  },
  {
    sampleContainers: [
      {
        id: 4450,
        sampleId: 2659,
        code: "00JC",
        color: "Green",
        name: "250g Soil Jar",
        description: "250g Soil Jar",
        containerId: 1,
        comments: "",
        barcode: "00JC001181",
        testLists: null,
        isStandardContainer: true,
        preservationType: "none",
        scanStatus: null,
        srComments: "comment"
      }
    ],
    id: 2659,
    batchId: 1499,
    sampleName: "COC_SCA",
    dateSampled: "2021-12-21T10:46:00",
    matrixId: 2
  },
  {
    sampleContainers: [
      {
        id: 4451,
        sampleId: 2660,
        code: "00JC",
        color: "Green",
        name: "250g Soil Jar",
        description: "250g Soil Jar",
        containerId: 1,
        comments: "",
        barcode: "00JC001183",
        testLists: null,
        isStandardContainer: true,
        preservationType: "none",
        scanStatus: null,
        srComments: null
      }
    ],
    id: 2660,
    batchId: 1499,
    sampleName: "COC_SCA",
    dateSampled: "2021-12-21T10:46:00",
    matrixId: 2
  }
]
};

const handleKeyDown = (event, type) => {
// Handle when key is pressed
};

const retrieveBulkScanList = (e) => {

const sampleNames = data?.samples.map((x) => ({
  name: x.sampleName,
  id: x.id
}));
const containers = data?.samples
  .map((x) => x.sampleContainers)
  .flat()
  .map((elm) => elm);
console.log('retrieve bulk scan');
setname([...sampleNames]);
setBulkScanList([...containers]);

};

const handleChange = (e, container) => {
  const { value, id } = e.currentTarget;
  setTask(value);
  console.log("comments added: " + value + " id: " + id);
}

return (
<div className="App">
  <h1>Hello CodeSandbox</h1>
  <TableContainer className={classes.container}>
    <Table className={classes.table} stickyHeader aria-label="sticky table">
      <TableHead>
        <TableRow>
          <TableCell>
            <Typography variant="h6">Sample Name</Typography>
          </TableCell>
          <TableCell align="right">
            <Typography variant="h6">Container Name</Typography>
          </TableCell>
          <TableCell align="right">
            <Typography variant="h6">SR Comments</Typography>
          </TableCell>
        </TableRow>
      </TableHead>
      <TableBody>
        {bulkScanList.length === 0 ? (
          <div>Scan to fetch data...</div>
        ) : (
          bulkScanList
            .slice(0)
            .reverse()
            .map((x) => (
              <StyledTableRow key={x.id.toString()}>
                <StyledTableCell component="th" scope="row">
                  {name
                    .filter((n) => n.id === x.sampleId)
                    .map((e) => e.name)}
                  _{x.sampleId}
                </StyledTableCell>
                <StyledTableCell align="right">{x.name}</StyledTableCell>
                <StyledTableCell>
                {editMode ? (
    <div
      onBlur={() => setEditMode(false)}
      onKeyDown={e => handleKeyDown(e, "input")}
    >
     <input
    type="text"
    name="task"
    placeholder="Write a task name"
    value={task}
    onChange={e => handleChange(e, x)}
  />
    </div>
  ) : (
    <div
      onClick={() => setEditMode(true)}
    >
      <span>
        {task || "Write a task name" || "Editable content"}
      </span>
    </div>
  )}
                </StyledTableCell>
              </StyledTableRow>
            ))
        )}
      </TableBody>
    </Table>
  </TableContainer>
  <Button
    color="primary"
    style={{ height: "50%", borderRadius: "30px" }}
    onClick={retrieveBulkScanList}
  >
    Start Scan
  </Button>
</div>
);
}
2 Answers

To push one object to an already existing state array you can use:

setState((oldArray) => [...oldArray, newObj]);

To map your full state array of objects you can use:

{state && state.map(({ id, value}) => (
    <div>{id}</div>
    <div>{value}</div>
))}

I resolved it finally..Here is a working code sample https://codesandbox.io/s/happy-framework-e27gl?file=/src/App.js

Basically I changed in 4 places

  1. Change <input type="text"....> to the following

     { 
            Object.values(task).filter((c) => c.id == x.id).length > 0 ? 
             Object.values(task).filter((c) => c.id == x.id).map(obj =>  
               <input
               id={x.id}
               type="text"
               name="task"
               placeholder="Write a task name"
               value={obj.value}
               onChange={e => handleChange(e, x)}
           />)
           :
      <input
               id={x.id}
               type="text"
               name="task"
               placeholder="I am duplicate"
               onChange={e => handleChange(e, x)}
           />
    
         }
    
  2. Change {task || "Write a task name"....} to the following

    <span>{
       Object.values(task).filter((c) => c.id == x.id).length > 0 ? 
             Object.values(task).filter((c) => c.id == x.id).map(obj =>
         obj.value) : "I am the span" }
       </span>
    

This one took me 1 whole day to sort as jsx was not pointing me to the right error and i kept on fixing other places..it was just giving me following error

Error: Objects are not valid as a React child (found: object with keys {id, value}).

P.S: Also I replaced {id, value} with obj in my code

  1. This one is straight forward

    const [task, setTask] = useState([]);
    
  2. Here's the magic! (cumbersome though)

    const handleChange = (e, container) => {
    const { value, id } = e.currentTarget;
    const oldArray = Object.values(task);
    const pos = oldArray.map(function(e) { return e.id; }).indexOf(container.id);
    console.log('position: ' + pos);
    
    const matched = oldArray.filter(c => c.id === container.id);
    
    if(matched.length > 0){
     oldArray[pos] =  {"id": container.id, "value":value};
     setTask(oldArray);
     console.log("hey")
    }
    else{
     var newStateArray = oldArray.slice();
     newStateArray.push({"id": container.id, "value":value});
     setTask(newStateArray);
     console.log("comments added: " + value + " id: " + container.id);
    
    }
    
    }
    
Related