Reputation: 379
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
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