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

useState钩子更新值无法被函数获取的问题排查与解决

问题:checkResultOfCard函数无法获取TextInput更新后的currentAnswer状态值

我遇到的问题是:checkResultOfCard函数无法获取通过TextInput更新后的currentAnswer useState钩子值。已确认每次输入TextInput时组件都会重新渲染(控制台有"RERENDER"日志),但函数始终拿不到最新的状态值。

完整代码

const PracticeScreen = props => {
  const dispatch = useDispatch();

  const myLibrary = useSelector(state => state.myLibrary.myLibrary);
  const myBottomTab = useSelector(state => state.myOtherReducers.showBottomTab);

  const backActionRef = useRef(null);
  const swiperRef = useRef(null);

  const [loadedCardStack, setLoadedCardStack] = useState([]);
  const [modalVisible, setModalVisible] = useState(false);
  const [currentAnswer, setCurrentAnswer] = useState("");

  const [wrongCards, setWrongCards] = useState([]);
  const [goodCards, setGoodCards] = useState([]);
  const [isAnswerRight, setIsAnswerRight] = useState(null);

  useEffect(() => {
    /**listener sub and unsub when changing screen
     * dispatching of bottom tab nav happening here*/
    const backAction = () => {
      Alert.alert("Hold on!", "Are you sure you want to exit the course?", [
        {
          text: "Cancel",
          onPress: () => null,
          style: "cancel"
        },
        { text: "YES", onPress: () => {
          dispatch(SpecialActions.setBottomTabVisibility("flex"));
          props.navigation.goBack()
        }}
      ],{cancelable: false});
      return true;
    };

    backActionRef.current = backAction;

    const backHandler = () => {
      backActionRef.current();
      return true;
    };

    // event listener when the screen comes into focus
    const focusListener = props.navigation.addListener('focus', () => {
      BackHandler.addEventListener("hardwareBackPress", backHandler);
    });

    // removing of the listener when the screen goes out of focus
    const blurListener = props.navigation.addListener('blur', () => {
      dispatch(SpecialActions.setBottomTabVisibility("flex"));
      BackHandler.removeEventListener("hardwareBackPress", backHandler);
    });

    return () => {
      focusListener();
      blurListener();
      BackHandler.removeEventListener("hardwareBackPress", backHandler);
    }
  }, [props.navigation]);

  useLayoutEffect(() => {
    /*Layoutstyling for header must be here, not in Navigator.js*/
    props.navigation.setOptions({
      headerTitle: 'Practice Screen',
      headerStyle: {
        backgroundColor: '#7dbae5',
        borderBottomWidth: 0.5,
        borderBottomColor: 'black',
      },
      headerTitleStyle: {
        color: 'black',
      },
      headerLeft: () => (
        <TouchableOpacity onPress={() => {
          Alert.alert("Hold on!", "Are you sure you want to exit the course?", [
            {
              text: "Cancel",
              onPress: () => null,
              style: "cancel"
            },
            { text: "YES", onPress: () => {
              dispatch(SpecialActions.setBottomTabVisibility("flex"));
              props.navigation.goBack()}}
          ],{cancelable: false});
        }}>
          <AntDesign name="caretleft" size={24} color="black" />
        </TouchableOpacity>
      ),
      headerRight: () => (
        <TouchableOpacity onPress={() => {
          setModalVisible(!modalVisible)
        }}>
          <AntDesign name="infocirlce" size={24} color="black" />
        </TouchableOpacity>
      )
    });
  }, [props.navigation]);

  useEffect(() => {
    /*loading up the Cards */ 
    loadCardStack();
  }, []);


  const loadCardStack = () => {
    setLoadedCardStack(props.route.params.courseData.cards);
    console.log("here is your stack :",loadedCardStack)
  };
  console.log("RERENDER")//checking if after each typing into TextInput this log triggers, and yes it does and so must the useState be updated
  const checkResultOfCard = (rightAnswer) => {
    //if wrong question, mark card, else add to positive stack
    if(currentAnswer !== rightAnswer){
      setIsAnswerRight(false);
      console.log("wrong answer: ", currentAnswer, "also the type: ", typeof(currentAnswer))
    }else{
      setIsAnswerRight(true);
      console.log("good answer")
    }
  };

  return (
    <View style={{flex:1}}>
      <KeyboardAvoidingView 
        style={styles.container} 
        behavior='height' 
        keyboardVerticalOffset={-(Dimensions.get('window').height * 0.18)} 
        enabled
      >
        {loadedCardStack.length > 0 && (
        <CardStack 
          style={styles.CardStackStyle} 
          ref={swiperRef}
          loop={true}
          verticalSwipe={false}
        >
          {loadedCardStack.map((card, index) => (
            <Card key={card.q1} style={styles.cardStyle1}>
              <View style={styles.splitCard}>
                <Text style={styles.question1}>{card.q1}</Text>
                <TextInput 
                  style={styles.textEdit1}
                  placeholder=" answer"
                  onChangeText={text => {
                    setCurrentAnswer(text)//THIS IS WHERE I CHANGE THE STATE
                  }}
                />
                <TouchableOpacity
                  style={styles.buttonStyle}
                  onPress={() => {
                    checkResultOfCard(card.answer); //HERE I CALL THE FUNCION WHICH FAILS ITS TASK
                  }}
                >
                <Text>Check Result</Text>
                </TouchableOpacity>
              </View>
              <View style={styles.splitCard}>
               
              </View>
            </Card>
          ))}
        </CardStack>
        )}
        <StatusBar style="auto" />
      </KeyboardAvoidingView>
    </View>
  );
}

可正常运行的简化Demo

const MyComponent = () => {
  const [text, setText] = useState('');
  const myfunction = () =>{
    alert(text)
  };

  return (
    <View>
      <TextInput
        onChangeText={text => setText(text)}
      />
      <Button
        title="Retrieve Text"
        onPress={() => {
          myfunction();
        }}
      />
    </View>
  );
};

补充说明

我使用的卡片组件是react-native-card-stack-swiper。必须让useState正常工作,因为后续要添加卡片变色等依赖状态变化的功能。


解决方案

问题核心在于react-native-card-stack-swiper的缓存机制:该组件会缓存内部的卡片子组件,导致卡片里的TextInput和按钮捕获的是组件初始化时的闭包状态,而非最新的currentAnswer。

推荐修复方案:给每个卡片绑定独立状态

共享单个currentAnswer本身逻辑就不合理(多个卡片的输入会互相覆盖),正确的做法是给每个卡片维护独立的输入状态:

  1. 将currentAnswer改为对象,存储每个卡片对应的输入值:
const [currentAnswers, setCurrentAnswers] = useState({});
  1. 更新TextInput的绑定逻辑,关联对应卡片的输入值:
<TextInput 
  style={styles.textEdit1}
  placeholder=" answer"
  onChangeText={text => {
    setCurrentAnswers(prev => ({...prev, [card.q1]: text})); // 用card的唯一标识作为key
  }}
  value={currentAnswers[card.q1] || ""} // 绑定当前卡片的输入值
/>
  1. 修改checkResultOfCard函数,接收当前卡片的输入值:
const checkResultOfCard = (rightAnswer, userAnswer) => {
  if(userAnswer !== rightAnswer){
    setIsAnswerRight(false);
    console.log("wrong answer: ", userAnswer, "also the type: ", typeof(userAnswer))
  }else{
    setIsAnswerRight(true);
    console.log("good answer")
  }
};
  1. 调用时传入当前卡片的输入值:
<TouchableOpacity
  style={styles.buttonStyle}
  onPress={() => {
    checkResultOfCard(card.answer, currentAnswers[card.q1] || "");
  }}
>
<Text>Check Result</Text>
</TouchableOpacity>

备选方案:强制CardStack刷新(不推荐)

如果一定要共享状态,可以给CardStack添加动态key,当currentAnswer变化时强制组件重新渲染:

<CardStack 
  style={styles.CardStackStyle} 
  ref={swiperRef}
  loop={true}
  verticalSwipe={false}
  key={currentAnswer} // 添加该key触发重渲染
>

此方法会导致每次输入都刷新整个卡片栈,性能较差,且多个卡片输入会互相覆盖,仅作为临时 workaround 使用。

Demo正常运行的原因

Demo中只有单个TextInput和按钮,没有使用类似CardStack的缓存型第三方组件,闭包可以正常捕获最新的状态值,因此逻辑生效。


内容的提问来源于stack exchange,提问作者Kubaguette The Testobun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 06:48:10