Component in React Native wrongly rendered after state update

Viewed 49

I'm having a really weird problem with my React Native component. Component's state is correctly updated, but it is not reflected on what is rendered by the component.

Here is what my component looks like (I didn't include imports and other parts of the code that are irrelevant):

export default function MessageActionsModal({ 
  message,
  actions,
  onClose
}) {
  const [dimensions, setDimensions] = useState(null);
  const [messageActions, setMessageActions] = useState([]);

  useEffect(() => {
    if (message !== null) {
      setMessageActions(actions.map((a) => ({
        ...a,
        onPress: () => {
          a.onPress(message?.item);
          onClose();
        }
      })))
    } else {
      setMessageActions([]);
    }
  }, [message, actions, onClose]);
  
  if (message === null) {
    return null;
  }

  return (
    <Portal>
      <TouchableOpacity
        style={styles.messageActionsModal}
        activeOpacity={0}
        onPress={onClose}
      >
        <Portal style={styles.messageActionsModalInnerContainer}>
          <MessageListItem
            style={[
              styles.message,
              getMessagePositionAndDimensions(message)
            ]}
            {...message.item}
          />
          <View
            style={[
              styles.messageActionsMenu,
              getMenuPosition(dimensions, message)
            ]}
            {...dimensions === null && {
              onLayout: ({ nativeEvent }) => setDimensions({
                width: nativeEvent.layout.width,
                height: nativeEvent.layout.height
              })
            }}
          >
            <Text>{'LEN'}{messageActions.length}</Text>
            <Text>{'LOG'}{console.log(messageActions.length)}</Text>

            {messageActions.map((a) => (
              <Button
                key={a.title}
                style={styles.messageActionButton}
                title={a.title}
                variant="text"
                startIcon={a.icon}
                startIconProps={{
                  style: styles.messageActionButtonIconWrapper,
                  iconStyle: styles.messageActionButtonIcon,
                  fill: Colors.yellow
                }}
                onPress={a.onPress}
              />
            ))}
          </View>
        </Portal>
      </TouchableOpacity>
    </Portal>
  );
}

So basically, after messageActions are updated in the useEffect, component renders incorrectly and neither message action is rendered (although one item should be rendered).

Another interesting thing is that the following code renders "LEN 0", but it logs "LOG 1" in the console.

<Text>{'LEN'}{messageActions.length}</Text>
<Text>{'LOG'}{console.log(messageActions.length)}</Text>

What am I doing wrong here?

1 Answers

You can try memoizing actions instead of using useEffect. Here's an example:

const messageActions = useMemo(() => {
    if (message === null) {
      return [];
    }
    
    return actions.map((a) => ({
        ...a,
        onPress: () => {
          a.onPress(message?.item);
          onClose();
        }
    }));
}, [message, actions, onClose]);
Related