Blake Lucey
Blake Lucey

Reputation: 379

React.js -- Table not redrawn when data is filtered

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.

Upvotes: 0

Views: 157

Answers (1)

abemscac
abemscac

Reputation: 181

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]);

Upvotes: 1

Related Questions