简体   繁体   中英

Functional components disappearing with useMemo / React.memo

I'm trying to get my head around useMemo (or React.memo ) to optimize some components rendering.

I'm having an issue I can't explain.

I have the following code:

[...]
    const [ cards, setCards ] = useState<Card[]>([])


    function addCard(){
        setCards([...cards, {name: 'card-' + cards.length, counter: 0, type: 'memo'}])
    }

    function onPressCard(index: number){
        cards[index].counter += 1
        setCards([...cards])
    }

   return (
     [...]
    {
      cards.map((x, index) => 
        <Card key={index} {...x} onPress={() => onPressCard(index)}/>
    }
    [...]
)

and Card defined as


const Card: FC<CardProps> = function({ name, counter, onPress }){
    const counterRef = useRef(0)

    const item = useMemo(() => {
        counterRef.current +=1

        return (
        <RectButton onPress={onPress} style={[styles.card, { backgroundColor: 'lightcoral' }]}>
            <Text style={styles.text}>{ name }</Text>
            <Text style={styles.counter}> { `counter ${counter}` }</Text>
            <Text style={styles.counter}>{ `render: ${counterRef.current}`}</Text>
        </RectButton>
        )
    }, [name, counter])

    return item
}

Why, when i press an item in the list (except the last one), all the following items disappear ? 在此处输入图片说明

Edit: the same happens with Card defined as

const areEqual = function(prevProps: Card, nextProps: Card){
    return (
        (prevProps.name === nextProps.name) &&
        (prevProps.counter === nextProps.counter)
    )
}

const Card = React.memo<CardProps>(({ name, counter, onPress }) => {
    const counterRef = useRef(0)

    counterRef.current +=1

    return (
        <RectButton onPress={onPress} style={[styles.card, { backgroundColor: 'lightcoral' }]}>
            <Text style={styles.text}>{ name }</Text>
            <Text style={styles.counter}> { `counter ${counter}` }</Text>
            <Text style={styles.counter}>{ `render: ${counterRef.current}`}</Text>
        </RectButton>
        )
}, areEqual)

The issue is that the memoized component includes a reference to an old version of onPress . That old onPress has an old version of cards in its closure. Hitting the button thus calls the old function, which updates the parent's state based on that old state, and that old state has fewer items in it.

One option to fix this is to use the function version of setCards, so that you are basing the update off the latest state. Also, i updated the code to no longer mutate the old card:

function onPressCard(index: number){
  setCards(oldCards => {
    const newCards = [...oldCards];
    newCards[index] = {...oldCards[index]};
    newCards[index].counter += 1;
    return newCards;
  })
}

Another option is to add onPress to the conditions for useMemo, but since the onPress function changes all the time, you end up not really gaining anything from memoization. This could be improved if onPress itself was memoized, using useCallback:

const onPressCard = useCallback((index: number) => {
  cards[index].counter += 1;
  setCards([...cards]);
}, [cards])

// ...

const item = useMemo(() => {
  counterRef.current +=1

  return ( /* jsx omitted for brevity */ )
}, [name, counter, onPress])    

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM