使用useEffect和setState时React状态未更新问题排查
分页功能状态更新异常问题
我编写了两个React组件:带输入框的模态框MessagesForm,以及实现分页功能的Pagination组件。按照逻辑,监听currentPage的useEffect钩子应该更新filteredMessages状态,从而刷新页面显示的消息列表,但实际页面并未更新。控制台打印newMessages能得到正确数据,不清楚为何页面状态不更新。
MessagesForm组件代码
export const MessagesForm = (props) => { const [filteredMessages, setFilteredMessages] = useState(props.subject.messages); const [currentPage, setCurrentPage] = useState(1); const [recordsPerPage] = useState(10); const [messages, setMessages] = useState(props.subject.messages); const indexOfLastRecord = currentPage * recordsPerPage; const indexOfFirstRecord = indexOfLastRecord - recordsPerPage; const nPages = Math.ceil(messages.length / recordsPerPage) const { handleSubmit, reset, register, control, getValues } = useForm({ mode: 'onSubmit', defaultValues: { author: '', message: '', } }); useEffect(() => { const newMessages = messages.slice(indexOfFirstRecord, indexOfLastRecord); setFilteredMessages(newMessages); }, [currentPage]) const { remove } = useFieldArray({ control, name: 'messages', }); const handleDelete = (id) => { subjectService.deleteMessage(props.subject['_id']['$oid'], id); let filters = filteredMessages.filter((el) => el.id !== id); setFilteredMessages(filters); } const handleEdit = (index) => { const updatedMessage = getValues(`messages.${index}.message`); const updatedAuthor = getValues(`messages.${index}.author`); const messageId = getValues(`messages.${index}.id`); const body = { message: updatedMessage, author: updatedAuthor, id: messageId, }; subjectService.updateMessage(props.subject['_id']['$oid'], body); } const onSubmit = async (formData) => { const body = { author: formData.author, message: formData.message, } subjectService.createMessage(props.subject['_id']['$oid'], body) .then(res => { Swal.fire({ title: 'Success', text: 'Message updated successfully', icon: 'success', confirmButtonText: 'Close', confirmButtonColor: '#5CBDA5' }).then(props.onHide) }) } return ( <Modal {...props} animation size="lg" centered > <Modal.Header> <Modal.Title> Mensajes </Modal.Title> </Modal.Header> <Modal.Body> <Pagination nPages={nPages} currentPage={currentPage} setCurrentPage={setCurrentPage} /> { filteredMessages.map((message, index) => { return ( <div key={index} className="message-row" > <Form.Group className='row tes' > <div className='col'> <Form.Label>Author</Form.Label> <Form.Control name='id' style={{ display: 'none' }} {...register(`messages.${index}.id`)} defaultValue={message.id} /> <Form.Control type="text" name='author' {...register(`messages.${index}.author`)} defaultValue={message.author} /> </div> <div className='col'> <Form.Label>Message</Form.Label> <Form.Control as="textarea" name='msg' {...register(`messages.${index}.message`)} defaultValue={message.message} /> </div> </Form.Group> <div className='action-buttons'> <button className='btn btn-info' type="button" onClick={() => { Swal.fire({ title: '¿Estás seguro?', text: "No podrás revertir esto", icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Sí, guardarlo!' }).then((result) => { if (result.value) { handleEdit(index); } }) }}><BiSave /></button> <button className='btn btn-danger' type="button" onClick={() => { Swal.fire({ title: '¿Estás seguro?', text: "No podrás revertir esto!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Sí, borrarlo!' }).then((result) => { if (result.value) { remove(index); handleDelete(message.id); } }) }}><Trash /></button> </div> </div> ) }) } </Modal.Body> <Modal.Footer> <Button type="submit" variant="primary" form="hook-form" onClick={() => { if (props.action !== 'NEW') props.onHide(); }} >OK</Button> </Modal.Footer> </Modal> ) }
Pagination组件代码
import React from 'react' const Pagination = ({ nPages, currentPage, setCurrentPage }) => { const pageNumbers = [...Array(nPages + 1).keys()].slice(1) const nextPage = () => { if (currentPage !== nPages) setCurrentPage(currentPage + 1) } const prevPage = () => { if (currentPage !== 1) setCurrentPage(currentPage - 1) } return ( <nav> <ul className='pagination justify-content-center'> <li className="page-item"> <a className="page-link" onClick={prevPage} href='#' > Previous </a> </li> {pageNumbers.map(pgNumber => ( <li key={pgNumber} className={`page-item ${currentPage == pgNumber ? 'active' : ''} `} > <a onClick={() => setCurrentPage(pgNumber)} className='page-link' href='#' > {pgNumber} </a> </li> ))} <li className="page-item"> <a className="page-link" onClick={nextPage} href='#' > Next </a> </li> </ul> </nav> ) } export default Pagination
问题原因及修复方案
1. 使用数组索引作为key导致React无法识别元素变化
在filteredMessages.map中,你使用数组索引作为元素的key:
<div key={index} className="message-row">
分页切换时,新数组的元素不同但索引仍从0开始,React会复用旧组件,不会触发重新渲染。必须使用每个消息的唯一标识message.id作为key:
<div key={message.id} className="message-row">
2. useEffect依赖数组不完整
分页逻辑的useEffect仅监听currentPage,但还依赖messages、分页索引等变量,这些变量变化时钩子不会触发。需要补充依赖,或直接在钩子内部计算分页索引:
useEffect(() => { const indexOfLastRecord = currentPage * recordsPerPage; const indexOfFirstRecord = indexOfLastRecord - recordsPerPage; const newMessages = messages.slice(indexOfFirstRecord, indexOfLastRecord); setFilteredMessages(newMessages); }, [currentPage, messages, recordsPerPage])
3. 未同步父组件props更新到状态
当父组件的props.subject.messages变化时,messages状态不会自动更新,需添加钩子监听:
useEffect(() => { setMessages(props.subject.messages); // 重置到第一页并更新过滤后的消息 const indexOfLastRecord = 1 * recordsPerPage; const indexOfFirstRecord = indexOfLastRecord - recordsPerPage; setFilteredMessages(props.subject.messages.slice(indexOfFirstRecord, indexOfLastRecord)); setCurrentPage(1); }, [props.subject.messages, recordsPerPage])
4. 删除消息后未更新messages状态
handleDelete中仅更新了filteredMessages,但messages状态仍保留已删除的消息,导致后续分页计算错误:
const handleDelete = (id) => { subjectService.deleteMessage(props.subject['_id']['$oid'], id); let updatedFiltered = filteredMessages.filter((el) => el.id !== id); setFilteredMessages(updatedFiltered); // 同步更新messages状态 let updatedMessages = messages.filter((el) => el.id !== id); setMessages(updatedMessages); }
内容的提问来源于stack exchange,提问作者Miguel V
相关产品推荐
相关产品推荐

