0

I have a table in Next.js that, despite inputting the text into a text field, isn't being redrawn. When redrawn, the table should return a single result, but it is currently only updating the top row to make the search result visible.

Here is my useEffect and where my state is declared:

const [filteredData, setFilteredData] = useState([]);
const [inputText, setInputText] = useState("");

//Get data from database
useEffect(() => {
  if (!checked) {
    setChecked(true);
    fetchData();
  }
  let data: any =
    inputText && inputText !== ""
      ? rows.filter(
          (item: any) =>
            item?.product_id?.toLowerCase().indexOf(inputText.toLowerCase()) !==
            -1
        )
      : rows;
  setFilteredData(data);
  console.log("results: ", data, inputText);
}, [checked, inputText]);

Here is my fetchData() function:

const fetchData: any = async () => {
  const response: any = await axios.get(`/api/layer_data`);
  setChecked(true);
  // setRows.checked(false);
  let dataRows: any[] = response.data;
  dataRows.map((dataRow: any) => (dataRow.isSelected = false));
  console.log("response: ", response.data);
  setRows(dataRows);
  setFilteredData(dataRows as any);
};

Here is where I draw the table:

<>
  {filteredData
    ?.sort(getComparator(order, orderBy))
    .map((row: any, index: any) => {
      const isItemSelected = isSelected(row.product_id);
      const labelId = `enhanced-table-checkbox-${index}`;

      return (
        <StyledTableRow
          hover
          onClick={(event) => handleClick(event, row.product_id)}
          role="checkbox"
          aria-checked={isItemSelected}
          tabIndex={-1}
          key={row.product_id}
          selected={isItemSelected}
        >
          <StyledTableCell padding="checkbox">
            <Checkbox
              color="primary"
              checked={row.isSelected}
              inputProps={{
                "aria-labelledby": labelId,
              }}
              onChange={handleCheckbox}
              value={index}
            />
          </StyledTableCell>
          <StyledTableCell align="right">
            <input
              type="number"
              min="0"
              required
              defaultValue="0"
              onChange={(e) => handleInput(e, index)}
            />
          </StyledTableCell>
          <StyledTableCell align="right">{row.sku_id}</StyledTableCell>

          <StyledTableCell
            component="th"
            id={labelId}
            scope="row"
            padding="none"
            align="right"
          >
            {row.product_id}
          </StyledTableCell>
          <StyledTableCell align="right">{row.in_stock}</StyledTableCell>
          <StyledTableCell align="right">{row.bin}</StyledTableCell>
          <StyledTableCell align="right">{row.units_per_layer}</StyledTableCell>
          <StyledTableCell align="right">{row.description}</StyledTableCell>
        </StyledTableRow>
      );
    })}
</>;

Any help is greatly appreciated. I appreciate any help you can provide.

niceman
  • 2,653
  • 29
  • 57
Blake Lucey
  • 349
  • 1
  • 7
  • 18

1 Answers1

1

The problem might be the async function fetchData is not being awaited, so the logic starting from let data: any = ... would probably acting strangely because some of them depend on the states updated in fetchData, but fetchData has not even started yet.

Also, since setState is not always synchronous, the states are not guaranteed to be the newest/latest after setState is called until the next re-render. So even if you await fetchData(), your code might still not run in the way you expect it to be.

Use useEffect one after another to make sure each part of the logic is executed at the right time can fix your problem, but that is going to make the component so hard to read and maintain, which is apparently not a good choice.

If batching is enabled & used in the version of React you're using, the following code would probably work if we want to keep most of the logic the same: (I'm mentioning batching here because you setChecked and setRows in fetchData, but only checked is in the dependency list of useEffect.)

const [rows, setRows] = useState([]); // <- I assume you have something like this in your component.
const [filteredData, setFilteredData] = useState([]);
const [inputText, setInputText] = useState("");

const fetchData = async (): any[] => {
  const response: any = await axios.get(`/api/layer_data`);
  setChecked(true);
  // setRows.checked(false);
  let dataRows: any[] = response.data;
  dataRows.map((dataRow: any) => (dataRow.isSelected = false));
  console.log("response: ", response.data);
  setRows(dataRows);
  setFilteredData(dataRows as any);
  // [ADDED]: We return dataRows in this function.
  return dataRows;
};

useEffect(() => {
  //Get data from database
  // [ADDED]: create an async function here because we have to use await in an effect.
  const computeFilteredData = async () => {
    let fetchedRows: any[]
    if (!checked) {
      setChecked(true);
      fetchedRows = await fetchData();
    } else {
      fetchedRows = rows;
    }
    let data: any =
      inputText && inputText !== ""
        ? fetchedRows.filter(
            (item: any) =>
      item?.product_id?.toLowerCase().indexOf(inputText.toLowerCase()) !== -1
          )
      : fetchedRows;
    setFilteredData(data);
    console.log("results: ", data, inputText);
  };
  computeFilteredData();
}, [checked, inputText]);

However, from the code you provided, filteredData looks more like a computed property based on rows and inputText. Therefore, it probably makes more sense to use useMemo in this case, so that we don't have to worry about manually updating filteredData every time rows or inputText changes; but I'm not sure if that fully suits your case:

import { useMemo } from "react";

const filteredData: any[] = useMemo(() => {
  if (inputText && inputText !== "") {
    return rows.filter(
      (item: any) =>
        item?.product_id?.toLowerCase().indexOf(inputText.toLowerCase()) !== -1
    );
  } else {
    return rows;
  }
}, [rows, inputText]);
abemscac
  • 181
  • 5
  • Thank you for your reply. I apologize for taking so long to get back to you. I have tried both of the solutions you provided, and unfortunately, it looks like the table is still not redrawn as I described in the initial question. The code I have provided successfully returns the correct result, but the table still is not redrawn, so ONLY that result is returned. Instead, all rows are returned with the correct result at the top. – Blake Lucey Aug 22 '22 at 13:53
  • That's strange. Maybe I didn't correctly understand the issue you described. Could you please provide a sandbox (StackBlitz, CodeSandbox, etc) to reproduce the issue? – abemscac Aug 22 '22 at 23:35
  • Could you see `react-query` as a potential fix to this? If so, why/why not? I ask because I'm not sure I am using `useEffect` correctly. – Blake Lucey Aug 30 '22 at 15:32