英文:
Retrieving value from useState hook
问题
以下是您要翻译的内容:
I have a problem regarding my current function `checkResultOfCards`. This function is unable to retrieve an updated value (`currentAnswer`) from a `useState`-Hook which I update through a TextInput. My code sample may be long but consider that half of it are `useEffect`-hooks which render only once under a special condition. That said, here is my code so far:
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(() => {
/*Layout styling for the 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>
);
}
It probably has something to do with the State being updated but the function not recognizing the new value of the Hook. I tried to write the code from scratch and try out a demo on my own, here is a short example:
const MyComponent = () => {
const [text, setText] = useState('');
const myfunction = () =>{
alert(text)
};
return (
<View>
<TextInput
onChangeText={text => setText(text)//HERE I CHANGE THE STATE}
/>
<Button
title="Retrieve Text"
onPress={() => {
myfunction();//HERE I CALL THE FUNCTION, IT WORKS
}}
/>
</View>
);
};
It is the same logic with also a pressable object firing a function but in this case everything works perfectly! That's why I am confused and reaching out for your help and some tips on how I can fix my code to make it work like in the demo, the second example.
EDIT: [This is the Card-Package][1] I am using for the `<CardStack>` and `<Card>` elements. Also, it is **mandatory** that I somehow make the `useState`-hooks working. I plan to add features like changing the color of the `<Card>` and this won't be possible without rerendering, which should be triggered by a change in a State.
[1]: https://www.n
<details>
<summary>英文:</summary>
I have a problem regarding my current function `checkResultOfCards`. This function is unable to retrieve a updated value (`currentAnswer`) from a `useState`-Hook which I update through a TextInput. My codesample may be long but consider that half of it are `useEffect`-hooks which render only once under a special condition. That said, here is my code so far:
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>
);
}
It probably has something to do with the State beeing updated but the function not recognizing the new value of the Hook. I tried to write the code from scratch and try out a demo on my own, here is a short example:
const MyComponent = () => {
const [text, setText] = useState('');
const myfunction = () =>{
alert(text)
};
return (
<View>
<TextInput
onChangeText={text => setText(text)//HERE I CHANGE THE STATE}
/>
<Button
title="Retrieve Text"
onPress={() => {
myfunction();//HERE I CALL THE FUNCTION, IT WORKS
}}
/>
</View>
);
};
It is the same logic with also a pressable object firing a function but in this case everything works perfectly! Thats why I am confuced and reaching out for your help and some tips how I can fix my code to make it work like in the demo, the second example.
EDIT: [This is the Card-Package][1] I am using for the `<CardStack>` and `<Card>` elements. Also it is **mandatory** that I somehow make the `useState`-hooks working. I plan to add features like changing color of the `<Card>` and this wont be possible without rerendering, which should be triggered by an change in a State.
[1]: https://www.npmjs.com/package/react-native-card-stack-swiper
</details>
# 答案1
**得分**: 1
我复制了您的代码以重新创建错误,似乎可以正常工作。请查看下面的代码示例:
```jsx
function App() {
const loadedCardStack = [
{
q1: "What is your name?",
answer: "john"
}
];
const [currentAnswer, setCurrentAnswer] = useState("");
const [isAnswerRight, setIsAnswerRight] = useState(null);
const checkResultOfCard = (rightAnswer) => {
//if wrong question, mark card, else add to positive stack
setIsAnswerRight(
rightAnswer && currentAnswer.trim().toLowerCase() === rightAnswer.trim().toLowerCase()
);
};
return (
<View style={styles.app}>
{loadedCardStack.map((card, index) => (
<View key={index}>
<Text>{card.q1}</Text>
<TextInput
style={{ borderColor: "#ddd", borderWidth: 1, margin: 10 }}
placeholder="answer"
onChangeText={(text) => {
setCurrentAnswer(text); //这是我更改状态的地方
}}
/>
<TouchableOpacity
onPress={() => {
checkResultOfCard(card.answer); //这里我调用了可能失败的函数
}}
style={{ marginTop: 7, backgroundColor: "orange" }}
>
<Text>检查结果</Text>
</TouchableOpacity>
</View>
))}
<Text>答案: {isAnswerRight ? "正确" : "不正确"}</Text>
</View>
);
}
您可以在此链接中查看实际效果:https://codesandbox.io/s/distracted-sea-xn5ikq?file=/src/App.js:146-1373
我不确定为什么对您不起作用。除了 checkResultOfCard
函数之外,我几乎没有做任何更改。
请注意,对于您的代码,您必须以正确的大小写输入答案。例如:如果答案是 John
,而您输入 john
,它将被标记为不正确。因此,我更改了函数以在比较之前将字符串转换为小写。另外,我删除了两端的空格。如果有帮助,请告诉我。
英文:
I copied your code to recreate the error and it seems to work. See the code sample below:
function App() {
const loadedCardStack = [
{
q1: "What is your name?",
answer: "john"
}
];
const [currentAnswer, setCurrentAnswer] = useState("");
const [isAnswerRight, setIsAnswerRight] = useState(null);
const checkResultOfCard = (rightAnswer) => {
//if wrong question, mark card, else add to positive stack
setIsAnswerRight(
rightAnswer &&
currentAnswer.trim().toLowerCase() === rightAnswer.trim().toLowerCase()
);
};
return (
<View style={styles.app}>
{loadedCardStack.map((card, index) => (
<View>
<Text>{card.q1}</Text>
<TextInput
style={{ borderColor: "#ddd", borderWidth: 1, margin: 10 }}
placeholder="answer"
onChangeText={(text) => {
setCurrentAnswer(text); //THIS IS WHERE I CHANGE THE STATE
}}
/>
<TouchableOpacity
onPress={() => {
checkResultOfCard(card.answer); //HERE I CALL THE FUNCION WHICH FAILS ITS TASK
}}
style={{ marginTop: 7, backgroundColor: "orange" }}
>
<Text>Check Result</Text>
</TouchableOpacity>
</View>
))}
<Text>Answer: {isAnswerRight ? "Correct" : "Incorrect"}</Text>
</View>
);
}
You can see it live here: https://codesandbox.io/s/distracted-sea-xn5ikq?file=/src/App.js:146-1373
I'm not sure why it doesn't work for you. I barely made any changes except for the checkResultOfCard
function.
Note that for your code, you have to type the answer in the correct case. i.e.: If the answer is John
and you type john
it will be marked as incorrect. So I changed the function to convert the string to lowercase before comparing. Also, I trimmed the white spaces from both ends.
Let me know if this helps.
答案2
得分: 0
你需要在这种情况下使用数组。
// 放在全局
let arrayAnswer = Array();
// 更改checkResultOfCard函数
const checkResultOfCard = (rightAnswer, index) => {
// 如果答案错误,标记卡片,否则添加到正面堆栈
if (arrayAnswer[index] !== rightAnswer) {
setIsAnswerRight(false);
console.log('array : ', arrayAnswer[index]);
} else {
setIsAnswerRight(true);
console.log('好答案');
}
};
// 最后在TextInput上设置数组
<TextInput
style={styles.textEdit1}
placeholder="答案"
onChangeText={(text) => {
let data;
arrayAnswer[index] = text;
console.log('arrayAnswer', arrayAnswer, index)
setCurrentAnswer(text); //这是我更改状态的地方
}}
/>
英文:
you need to use array for this case.
// put this on global
let arrayAnswer = Array();
// change checkResultOfCard function
const checkResultOfCard = (rightAnswer, index) => {
//if wrong question, mark card, else add to positive stack
if (arrayAnswer[index] !== rightAnswer) {
setIsAnswerRight(false);
console.log('array : ', arrayAnswer[index]);
} else {
setIsAnswerRight(true);
console.log('good answer');
}
};
//and lastly put the set on array on textinput
<TextInput
style={styles.textEdit1}
placeholder=" answer"
onChangeText={(text) => {
let data;
arrayAnswer[index] = text;
console.log('arrayAnswer',arrayAnswer, index)
setCurrentAnswer(text); //THIS IS WHERE I CHANGE THE STATE
}}
/>
答案3
得分: 0
我已附上我的代码在 Snack 上。
https://snack.expo.dev/@jonasbeck.dev/retrieving-value-from-usestate-hook
我猜问题的原因可能是 Card 组件将状态值作为常数与初始值一起引用。
让我尝试用一个类似的案例来解释。
在下面的示例代码中,'t' 的值始终为1,而不是每秒递增1,因为 't' 在 'setInterval' 的参数函数中被引用为常数0。
const [t, setT] = useState(0)
setInterval(() => {
setT(t+1)
}, 1000)
解决方案是使用 setState 回调,像这样:
const [t, setT] = useState(0)
setInterval(() => {
setT(prevState => prevState+1)
}, 1000)
就像这样,我可以修复您的代码。
const checkResultOfCard = (rightAnswer) => {
// 如果问题错误,标记卡片,否则添加到正面堆栈
setRightAnswer(prevState => {
setIsAnswerRight(
rightAnswer &&
currentAnswer.trim().toLowerCase() === rightAnswer.trim().toLowerCase()
);
return prevState;
});
};
英文:
Attached my code on snack.
https://snack.expo.dev/@jonasbeck.dev/retrieving-value-from-usestate-hook
I guess the reason of issue would be Card component refer state value as constant with initial value.
Let me try to explain with a similar case.
With the example code below, the value of 't' is always 1 not increazing value per 1 sec because 't' is referred to constant 0 inside the parameter function of 'setInterval'.
> const [t, setT] = useState(0)
> setInterval(()=>{
> setT(t+1)
> }, 1000)
The solution is to use setState callback like this:
> const [t, setT] = useState(0)
> setInterval(()=>{
> setT(prevState=>prevState+1)
> }, 1000)
like that I can fix your code.
> const checkResultOfCard = (rightAnswer) => {
> //if wrong question, mark card, else add to positive stack
> setRightAnswer(prevState=>{
> setIsAnswerRight(
> rightAnswer &&
> currentAnswer.trim().toLowerCase() === rightAnswer.trim().toLowerCase()
> );
> return prevState;
> });
> };
答案4
得分: 0
一般来说,直接从输入中更新状态是一种不好的做法。创建一个具有文本输入状态更新的去抖处理函数。
英文:
In general it's a bad practise to directly update state from an input. Create a handle function with a debounce for the text input state update.
答案5
得分: 0
问题出在这里:https://github.com/lhandel/react-native-card-stack-swiper/blob/master/CardStack.js#L154
当顶部组件重新渲染(由于setCurrentAnswer
的调用),CardStack也重新渲染,并调用其componentDidUpdate
(因为其children
属性实际上发生了变化)。
但然后它确定子组件是相同的(因为数组长度相同,所有子组件的key
保持相同并且顺序相同)。因此,它跳过更新其状态(即cardA
和cardB
属性)。
而这些属性是在render
方法中使用的,而不是直接使用children
属性(https://github.com/lhandel/react-native-card-stack-swiper/blob/master/CardStack.js#L404)。
它将呈现旧的子组件集,而不是新的子组件集,因此TouchableOpacity
使用的是旧版本的checkResultOfCard
引用。请注意,对于输入框来说这并不是问题(输入的文本不会被替换),因为它是一个本机HTML元素,根据设计是不受控制的。
这基本上是react-native-card-stack-swiper的一个不良实现。您应该打开一个错误报告或提交一个修复。与此同时,作为一种解决方法,您可以尝试通过更改卡片的键(key={card.q1 + A_COUNTER_OR_SOMETHING_ELSE}
)来强制执行完整的重新渲染。或者更改checkResultOfCard
的实现,直接从输入框中获取答案值(使用引用)。
英文:
The problem is here: https://github.com/lhandel/react-native-card-stack-swiper/blob/master/CardStack.js#L154
When the top component re-renders (due to the setCurrentAnswer
call), the CardStack re-renders and its componentDidUpdate
is called (because its children
prop effectively changed).
But it then determines that the children are the same (because the array length is the same and all the childs' key
remain the same and in the same order). So it skips updating its state (namely the cardA
and cardB
properties).
And those are the properties used at the render
method, instead of directly the children
property (https://github.com/lhandel/react-native-card-stack-swiper/blob/master/CardStack.js#L404).
It will render the old set of childs instead of the new one, so the TouchableOpacity
is one with an old reference of checkResultOfCard
in its closure. Notice it is not a problem for the input (the inputted text doesn't get replaced) because it is a native html element, uncontrolled by design.
This basically is a poor implementation by react-native-card-stack-swiper. You should open a bug or submit a fix. Meanwhile, as a workaround, you may try to force the full re-render by changing the card key (key={card.q1 + A_COUNTER_OR_SOMETHING_ELSE}
). Or change the implementation of checkResultOfCard
for fetching the answer value directly from the input (using a ref).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论