React兄弟组件通信实现:上传/删除文件后自动更新文件表格组件
Hey there! To get your FilesTable to automatically refresh after an upload or delete operation, we can use state lifting and callback functions to sync data across your components. Here's a straightforward implementation:
Step 1: Move Data Management to the Parent Component (filesPage.js)
We'll handle the file list state and data-fetching logic in the parent UploadFiles component. This way, both upload and delete actions can trigger a refresh of the file list.
import React, { useState, useEffect } from 'react'; import UploadFile from '../components/UploadFile' import DeleteFile from '../components/DeleteFile' import FilesTable from '../components/FilesTable' import axios from 'axios'; function UploadFiles() { // Parent component manages the file list state const [fileList, setFileList] = useState([]); // Reusable function to fetch the latest file list const fetchLatestFiles = () => { axios.get("api/tfa") .then((res) => setFileList(res.data)) .catch((err) => console.error('Failed to fetch files:', err)); }; // Fetch files on component mount useEffect(() => { fetchLatestFiles(); }, []); return ( <div className="filesPage"> {/* Pass refresh callback to UploadFile */} <UploadFile onUploadSuccess={fetchLatestFiles} /> {/* Pass refresh callback to DeleteFile */} <DeleteFile onDeleteSuccess={fetchLatestFiles} /> {/* Pass the latest file list to FilesTable */} <FilesTable fileList={fileList} /> </div> ) } export default UploadFiles;
Step 2: Update FilesTable to Use Props Instead of Internal State
Modify FilesTable to accept the file list as a prop, so it automatically re-renders when the prop changes. We'll remove the internal useEffect for data fetching since the parent now handles that.
import React, { useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Paper from '@material-ui/core/Paper'; 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 TablePagination from '@material-ui/core/TablePagination'; import TableRow from '@material-ui/core/TableRow'; function FilesTable({ fileList }) { const columns = [ {id: 'fileId', label: '#', minWidth: 100}, {id: 'name', label: 'name', minWidth: 100}, {id: 'date', label: 'upload date', minWidth: 100} ]; // Generate rows directly from the passed fileList prop const rows = fileList.map((tfa, index) => ({ fileId: (index + 1), name: tfa.fileName, date: tfa.dateTime.slice(0,24) })); const useStyles = makeStyles({ root: { width: '50%', position: 'absolute', right: '10%', top: '15%', }, container: { maxHeight: 322 }, headerCell: { background: '#F5F5F5', fontSize: '16px', zIndex: '0' } }); const classes = useStyles(); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(5); const handleChangePage = (event, newPage) => { setPage(newPage); }; const handleChangeRowsPerPage = (event) => { setRowsPerPage(+event.target.value); setPage(0); }; return ( <> <Paper className={classes.root}> <TableContainer className={classes.container}> <Table stickyHeader aria-label="sticky table"> <TableHead> <TableRow> {columns.map((column) => ( <TableCell className={classes.headerCell} key={column.id} style={{ minWidth: column.minWidth }}> {column.label} </TableCell> ))} </TableRow> </TableHead> <TableBody> {rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row, index) => { return ( <TableRow hover role="checkbox" tabIndex={-1} key={index}> {columns.map((column) => { const value = row[column.id]; return ( <TableCell key={column.id}> {value} </TableCell> ); })} </TableRow> ); })} </TableBody> </Table> </TableContainer> <TablePagination rowsPerPageOptions={[5, 10, 15]} component="div" count={rows.length} rowsPerPage={rowsPerPage} page={page} onChangePage={handleChangePage} onChangeRowsPerPage={handleChangeRowsPerPage} /> </Paper> </> ) } export default FilesTable;
Step 3: Update UploadFile and DeleteFile to Trigger Refresh
Modify your upload and delete components to call the parent's refresh callback after their respective axios requests succeed:
Example UploadFile Component:
import React from 'react'; import axios from 'axios'; function UploadFile({ onUploadSuccess }) { const handleFileUpload = (e) => { const file = e.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); axios.post('api/your-upload-endpoint', formData) .then(() => { console.log('File uploaded successfully!'); // Trigger parent to refresh file list onUploadSuccess(); }) .catch(err => console.error('Upload failed:', err)); }; return ( <div> <input type="file" onChange={handleFileUpload} /> </div> ); } export default UploadFile;
Example DeleteFile Component:
import React from 'react'; import axios from 'axios'; function DeleteFile({ onDeleteSuccess }) { // Replace with your actual delete logic (e.g., pass file ID from UI) const handleDelete = (fileId) => { axios.delete(`api/your-delete-endpoint/${fileId}`) .then(() => { console.log('File deleted successfully!'); // Trigger parent to refresh file list onDeleteSuccess(); }) .catch(err => console.error('Delete failed:', err)); }; return ( <div> {/* Example: Delete button (adjust based on your UI) */} <button onClick={() => handleDelete('target-file-id')}>Delete Selected File</button> </div> ); } export default DeleteFile;
Why This Works:
- State Lifting: By moving the file list state to the parent component, we create a single source of truth for the file data.
- Callback Communication: The child components (Upload/Delete) notify the parent when their operations complete, triggering a fresh data fetch.
- Prop-Driven Re-Render: FilesTable receives the updated file list as a prop, so React automatically re-renders it with the latest data.
This approach keeps your components focused, follows React's unidirectional data flow, and is easy to maintain.
内容的提问来源于stack exchange,提问作者IdanGonen

