I have two components in the App file, the Header and the Table.
Based on the styleNumber, I have to filter the rows that match those styleNumber using the API endpoint.
In the Header, I have a searchbar, so when I type I can filter and get the rows with that styleNumber. So, I’m passing the styleNumber to both components and the state is in App.
My issue is that I have to type the entire styleNumber to do the rows’ table filtering.
Instead I’d like to start doing the filtering when I start typing and at the same time, the table has to show , on every input, the rows that have those numbers. It’s like a dynamic filtering as all the searchbar do, right?
How can I fix this issue?
This is my header component:
interface HeaderProps {
styleNumber: string;
setStyleNumber: React.Dispatch<React.SetStateAction<string>>;
}
export const Header = ({ styleNumber, setStyleNumber }: HeaderProps): ReactElement => {
const [inputValue, setInputValue] = useState<string>('');
const { login } = useContext(AuthorizationContext);
const [hasFocus, setHasFocus] = useState<boolean>(false);
const inputRef = useRef<ElementRef<'input'>>(null);
const { palette } = useTheme();
const theme = useTheme();
const { instance, accounts } = useMsal();
const isAuthenticated = useIsAuthenticated();
const account = useAccount(accounts[0] || {});
const [openDialog, setOpenDialog] = useState(false);
const [openCreateCertificateDialog, setOpenCreateCertificateDialog] = useState(false);
const iconSize = 24;
const boxHeightSearchBar = 56;
const fontFamilySeachBar = 18;
// I fetch the style number in the API
const fetchCertificatesByStyleNumber = async (
// eslint-disable-next-line @typescript-eslint/no-shadow
styleNumber: string,
// eslint-disable-next-line @typescript-eslint/no-shadow
setStyleNumber: React.Dispatch<React.SetStateAction<string>>
): Promise<void> => {
try {
// Convert styleNumber to a number
const numericStyleNumber = parseInt(styleNumber, 10);
// Check if it's a valid number before making the request
if (!isNaN(numericStyleNumber)) {
const response = await axios.get(
`https://hello.com/safety-certificate/certificate/certificate/${numericStyleNumber}`
);
const certificates = response.data.certificates;
// eslint-disable-next-line no-console
console.log('API Response:', response);
// Update the parent component's styleNumber state
setStyleNumber(styleNumber);
} else {
console.error('Invalid styleNumber:', styleNumber);
}
} catch (error) {
console.error('Error fetching certificates:', error);
}
};
// this manages the input value
const handleInputChange = async (event: ChangeEvent<HTMLInputElement>): Promise<void> => {
const value = event.target.value;
setInputValue(value);
// Assuming I want to fetch certificates when the input changes
await fetchCertificatesByStyleNumber(value, setStyleNumber);
};
// eslint-disable-next-line no-console
console.log(inputValue);
// // Function to clear the input
const handleClearInput = (): void => {
setInputValue('');
};
const handleShowDialog = (): void => {
setOpenDialog(true);
};
const handleCloseDialog = (): void => {
setOpenDialog(false);
};
const handleOnSignIn = (): void =>{
login();
};
const handleSignOut = (): void => {
instance.logoutRedirect({
account,
}).then(() => {
})
.catch((error) => {
// Handle logout error if needed
console.error('Logout error:', error);
});
};
const handleCreateCertificate = (): void => {
setOpenCreateCertificateDialog(true);
};
return (
<Box
sx={{
paddingLeft: 10,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 3,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}}
>
<Box
alignItems='center'
justifyContent='center'
display='flex'
sx={{
marginRight: '20px',
}}
>
<Typography variant="h1">BESTSELLER</Typography>
</Box>
<Box sx={{ flex: '5', display: 'flex', justifyContent: 'center', marginLeft: '12px' }}>
<TextField
variant="outlined"
placeholder='Search by style number'
value={inputValue}
sx={{
['& .MuiInputBase-root']: {
height: boxHeightSearchBar,
},
['& .MuiOutlinedInput-root']: {
mt: '0 !important',
pr: inputValue ? 2 : !hasFocus ? 5 : 0,
},
['& .MuiOutlinedInput-input']: {
fontSize: fontFamilySeachBar,
},
}}
InputProps={{
inputProps: {
id: innerInputId,
ref: inputRef,
},
startAdornment: (
<InputAdornment position="start" sx={{ mr: 0 }}>
<SearchIcon color={palette.text.primary} style={{ width: iconSize, height: iconSize }} />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
{inputValue && (
<IconButton onClick={handleClearInput}>
<CrossIcon
style={{ width: iconSize, height: iconSize }}
/>
</IconButton>
)}
</InputAdornment>
),
}}
fullWidth
// eslint-disable-next-line @typescript-eslint/no-misused-promises
**onChange={handleInputChange}**
onFocus={(): void => void setHasFocus(true)}
onBlur={(): void => void setHasFocus(false)}
/>
</Box>
My table component:
export interface CertificateData {
certificateNumber: string;
brandNumber: string;
styleNumber: number;
ceDocumentName: string;
ceDocumentUrl: string;
createdBy: string;
changedBy: string;
isDeleted: boolean;
createdDate: string;
}
export interface BrandData {
brandName: string;
brandNumber: string;
isDeleted: boolean;
}
interface FilteredTable {
styleNumber: string;
}
export const TableComponent = ({ styleNumber }: FilteredTable) => {
const [certificates, setCertificates] = useState<Array<CertificateData>>([]);
const [brands, setBrands] = useState<Array<BrandData>>([]);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const isAuthenticated = useIsAuthenticated();
const [isDialogOpen, setDialogOpen] = useState(false);
const [selectedCertificate, setSelectedCertificate] = useState<CertificateData | null>(null);
useEffect(() => {
// Fetch certificates
axios
.get('https://hello.com/safety-certificate/certificate/certificates')
.then((response) => {
console.log('Certificates Response:', response.data);
setCertificates(response.data.certificates);
})
.catch((error) => {
console.error('Certificates Error:', error);
});
// Fetch brands
axios
.get('https://hello.com/safety-certificate/brand/brands')
.then((response) => {
console.log('Brands Response:', response.data);
setBrands(response.data.brands);
})
.catch((error) => {
console.error('Brands Error:', error);
});
}, []);
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleEditIconClick = (event: React.MouseEvent<HTMLButtonElement>, certificate: CertificateData): void => {
setSelectedCertificate(certificate);
setDialogOpen(true);
};
// Convert styleNumber to a number
const numericStyleNumber = parseInt(styleNumber, 10);
// Filter certificates based on the numericStyleNumber
const filteredCertificates = !isNaN(numericStyleNumber)
? certificates.filter((certificate) => certificate.styleNumber === numericStyleNumber)
: certificates;
return (
<Box>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="safety-certificates table">
<TableHead>
<TableRow>
<StyledTableCell align="right">Style Number</StyledTableCell>
<StyledTableCell align="right">Brand Name</StyledTableCell>
{isAuthenticated && <StyledTableCell align="right">Created By</StyledTableCell>}
<StyledTableCell align="right">CE-document</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredCertificates.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((certificate) => (
<StyledTableRow key={certificate.certificateNumber}>
<StyledTableCell align="right">{certificate.styleNumber}</StyledTableCell>
<StyledTableCell align="right">
{brands.find((brand) => brand.brandNumber === certificate.brandNumber)?.brandName ?? 'Brand Not Found'}
</StyledTableCell>
{isAuthenticated && (
<StyledTableCell align="right">{certificate.createdBy}</StyledTableCell>
)}
<StyledTableCell align="right">
<Box>
<a
href={`https://buying-dev.bestseller.com/safety-certificate/certificate/certificatePdf/${certificate.certificateNumber}`}
target="_blank"
download={certificate.ceDocumentName}
rel="noreferrer"
style={{ color: 'black', textDecoration: 'none' }}
>
<span>{certificate.ceDocumentName}</span>
</a>
{isAuthenticated && (
<IconButton onClick={(event): void => void handleEditIconClick(event, certificate)}>
<EditIcon />
</IconButton>
)}
</Box>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 50]}
component="div"
count={certificates.length ?? 0}
rowsPerPage={rowsPerPage}
page={page}
labelRowsPerPage="Documents per page"
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</TableContainer>
My app file:
const [styleNumber, setStyleNumber] = useState<string>('');
return (
<MsalProvider instance={msalService.msalInstance}>
<AuthenticationProvider>
<ThemeProvider theme={theme}>
<Header styleNumber={styleNumber} setStyleNumber={setStyleNumber} />
<TableComponent styleNumber={styleNumber} />
</ThemeProvider>
</AuthenticationProvider>
</MsalProvider>
);