You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 10:25:25