在 onClick 函数中调用更新函数时 useState 不会重新渲染

Posted

技术标签:

【中文标题】在 onClick 函数中调用更新函数时 useState 不会重新渲染【英文标题】:useState not rerendering when update function is called inside onClick function 【发布时间】:2020-03-06 03:26:14 【问题描述】:

我花了几天时间查看类似的帖子,但没有解决方案。出于某种原因,当我使用 setPostState(myState.posts); 时,它不会重新渲染组件。

我正在使用反应 ^16.10.2

下面是我的代码:

import React, useState, useCallback from 'react';
import withStyles, makeStyles from '@material-ui/core/styles';
import Paper, TableRow, TableHead, TableCell, TableBody, Table, Badge, Fab from '@material-ui/core'
import myState from '../../PubSub/pub-sub'

import ThumbUpIcon from '@material-ui/icons/ThumbUp';
import ThumbDownIcon from '@material-ui/icons/ThumbDown';

const StyledTableCell = withStyles(...))(TableCell);

const StyledTableRow = withStyles(...))(TableRow);

const useStyles = makeStyles(theme => (...));

export default props => 
    console.log("++++++++++++++++Render Body+++++++++++++++++++++");
    const classes = useStyles();
    let [postState, setPostState] = useState(myState.posts);// why does setPostState not update badge count???? or re-render component???

    let upVote = (id) => 
        let objIndex = myState.posts.findIndex((obj => obj.id == id));
        return (
            <Fab key="upVote4309lk" +id color="primary" aria-label="add" className=classes.fab
                 onClick=() => 
                     myState.posts[objIndex].up_vote++;
                     setPostState(myState.posts);//why does this not update badge count???? or re-render component???
                 >
                <Badge key="Ubadge"+objIndex className=classes.margin badgeContent=postState[objIndex].up_vote color="primary"><
                    ThumbUpIcon> </ThumbUpIcon>
                </Badge>
            </Fab>
        )
    ;

    let downVote = (id) => 
        let objIndex = myState.posts.findIndex((obj => obj.id == id));
        return (
            <Fab key="downVote0940v" + id color="primary" aria-label="add" className=classes.fab
                 onClick=() => 
                     myState.posts[objIndex].down_vote++;
                     setPostState(myState.posts);//why does this not update badge count???? or re-render component???
                 >
                <Badge className=classes.margin badgeContent=myState.posts[objIndex].down_vote color="primary"><
                    ThumbDownIcon> </ThumbDownIcon>
                </Badge>
            </Fab>
        )
    ;

    function filter(name) 
        return name.toLowerCase().includes(props.searchData.title.toLowerCase());
    

    function createData(title, description, user, up_votes, down_votes, id) 
        if (filter(title, description, user, up_votes, down_votes)) 
            return (
                <StyledTableRow key=id + "tableKey">
                    <StyledTableCell>title</StyledTableCell>
                    < StyledTableCell>description</StyledTableCell>
                    <StyledTableCell>user</StyledTableCell>
                    <StyledTableCell>upVote(id)</StyledTableCell>
                    <StyledTableCell>downVote(id)</StyledTableCell>
                </StyledTableRow>
            )
        
    

    const rows = myState.posts.map(
        obj => createData(obj.title, obj.description, obj.user, obj.up_votes, obj.down_votes, obj.id)
    );

    return (
        <Paper className=classes.root>
            <Table className=classes.table aria-label="customized table">
                <TableHead>
                    <TableRow>
                        <StyledTableCell>Title</StyledTableCell>
                        <StyledTableCell>Description</StyledTableCell>
                        <StyledTableCell>User</StyledTableCell>
                        <StyledTableCell>Up Votes</StyledTableCell>
                        <StyledTableCell>Down Votes</StyledTableCell>
                    </TableRow>
                </TableHead>
                <TableBody>
                    rows.map(row => (row))
                </TableBody>
            </Table>
        </Paper>
    );

任何帮助都会很棒,谢谢!

【问题讨论】:

【参考方案1】:

在 React 中,组件仅在 state 更改时重新渲染,即 prevState !== currentState 是基于类的组件还是函数式组件。在您的情况下,您正在调用 setPosts 但它不会更改状态,因为您在设置状态时分配了相同的对象myState.posts。 React 不对对象执行深度相等检查,它只是比较对象在某个状态下的引用。在您的情况下,引用永远不会随着您的变化而改变,并且在调用 setPosts 后 prevState 保持等于 newState

为了避免这个问题,在 react 中使用 Objects / Arrays 设置状态时,您需要确保分配一个新的引用。所以比较 prevStatecurrState 返回 false。有关详细信息,请参阅相等检查示例

访问和设置状态的正确方法:

// Set the initial value using myState.posts and then use the variable
// postState to access the posts and not myState.posts
const [postState, setPostState] = useState(myState.posts)

const makeUpVote = (objIndex) => 
    // Make local variable posts to change and set posts while
    // using spread operator to make sure we get a new array created instead
    // of pointing to the same array in memory
    const posts = [...postState]
    posts[objIndex].up_vote++
    setPostState(posts)
 


let upVote = id => 
    // use postState to access instead of myState.posts
    let objIndex = postState.findIndex(obj => obj.id == id)
    return (
        <Fab
            // I would recommend creating separate functions to handle this
            // instead of writing them inline.
            onClick=() => makeUpVote(objIndex)
        ></Fab>
    )

平等检查示例:

// This snippet is just to give you an idea of mutation
const posts = [id: 1, upvote: 0, id: 2, upvote: 0]
const posts2 = posts

// using spread operator from ES6 to assign a new array with similar values to posts3
const posts3 = [...posts]
posts[0].upvote++
posts3[0].upvote++

// This statement will return true because posts and posts2 have
// the same address in memory (reference) even though we just
// changed posts variable.
// If we set posts2 in state and initial state was posts
// component will NOT re-render
console.log(posts === posts2)

// This will return false because we assigned a new object
// to posts3 using spread operator even though values are same
// If we set posts3 in state an initial state was posts
// component will re-render
console.log(posts === posts3)

// Now another thing to notice is that spread operator does not
// perform deep cloning and therefore the object at index 0 in
// posts has the same reference to object at index 0 in posts 3
// therefore we get upvote = 2
console.log("Posts: ", posts)
console.log("Posts3: ", posts3)

【讨论】:

谢谢,您的建议奏效了。我通过将 setPostState(myState.posts); 更改为 setPostState([...myState.posts]); 来修复它

以上是关于在 onClick 函数中调用更新函数时 useState 不会重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

当HTMLElement.onclick事件--javascript调用时,在其函数中获取对象的属性

无法在 html 中使用 onClick 调用 jquery 函数

如何使用 OnClick 事件调用 JS 函数

React Redux 表更新行

为啥我不能从 onclick 属性调用名为 clear 的函数?

调用函数onclick on some buttons而不影响单个onclick函数的按钮(已经为按钮分配) - JavaScript