我的 useState 钩子有问题,每当我调用 setSate 时,它都不会改变 state 的值。这个问题我在我创建的自定义 SearchBar 中得到了它,每当用户引入某些内容时 SearchBar 都会给他一些建议,就像普通的 SearchBar 一样,这是通过调用一个函数来完成的miniSearch搜索引擎搜索数据(名为“FruitsAndVeggies.js”)并将其保存在suggestions状态中,这些建议将显示在屏幕中的列表中。问题是列表没有显示,因为 suggestions 状态为空,我用 console.log() 检查了它,它似乎没有更新。有人可以帮我解决这个问题吗?
顺便说一句,我在 ProfileScreen 组件中创建了一个临时 SearchBar,它工作得很好,但在另一个组件上则不然。
这是给我带来这个问题的SearchBar:
import React, { useState, useEffect } from "react";
import { View, TextInput, FlatList, Text } from "react-native";
import MiniSearch from "minisearch";
import { ScaledSheet, verticalScale, scale } from "react-native-size-matters";
/**
* Generic SearchBar Component.
*
* A reusable and customizable search bar component with suggestions for React Native applications.
*
* @component
* @param {boolean} [border=false] - Determines whether to show a border around the search bar.
* @param {StyleSheet.NamedStyles} [addStyles] - Additional styles for the search bar.
* @param {string} [placeholder="Search"] - Placeholder text displayed in the search bar.
* @param {Function} [handleOnFocus] - Function to call when the search bar gains focus.
* @param {Function} [onChangeText] - Function to call when the text in the search bar changes.
* @param {Array} [data] - An array of suggestion items to display.
* @param {Function} [onSuggestionPress] - Function to call when a suggestion item is pressed.
* @param {Function} [keyExtractor] - Function to extract a unique key for each suggestion item. Default is 'id'
* @returns {React.Element} Generic SearchBar component.
*/
export default function SearchBar({
border = false,
addStyles,
placeholder = "Search",
handleOnFocus,
onChangeText,
data,
onSuggestionPress,
keyExtractor = (item) => item.id,
}) {
const [showSuggestions, setShowSuggestions] = useState(false);
const [suggestions, setSuggestions] = useState([]);
// MiniSearch engine for data
const miniSearch = new MiniSearch({
fields: ["name"],
idField: "id",
storeFields: ["name"],
});
useEffect(() => {
if (suggestions.length > 0)
console.log("Suggestions UseEffect: " + JSON.stringify(suggestions));
}, [suggestions]);
// useEffect(() => {
// if (data) {
// miniSearch.addAll(data, ["name"]);
// console.log("SearchBar -> Data added");
// }
// }, [data]);
// useEffect(() => {
// if (suggestions)
// console.log("Search suggestions: " + JSON.stringify(suggestions) + "\n");
// }, [suggestions]);
/**
* Handles the change in the search bar text.
*
* @param {string} newText - The updated text in the search bar.
*/
const handleChangeText = async (text) => {
//checking if text is empty
if (!text) {
setShowSuggestions(false);
setSuggestions([]);
return;
}
let suggestion = await miniSearch.autoSuggest(text);
if (suggestion) {
setSuggestions(suggestion);
setShowSuggestions(true);
console.log(
"SearchBar -> suggestions: " + JSON.stringify(suggestions) + "\n"
);
}
};
return (
<View
style={[
styles.searchbarView,
border && styles.searchBarBorder,
addStyles,
]}
>
<TextInput
style={{
flex: 1,
fontSize: 16,
}}
placeholder={placeholder}
onChangeText={handleChangeText}
onFocus={handleOnFocus}
blurOnSubmit={true}
/>
{showSuggestions && suggestions && (
<FlatList
data={suggestions}
renderItem={({ item }) => (
<View style={styles.suggestionItem}>
<Text onPress={() => onSuggestionPress(item)}>{item.name}</Text>
</View>
)}
keyExtractor={keyExtractor}
/>
)}
</View>
);
}
const styles = ScaledSheet.create({
searchbarView: {
height: verticalScale(35),
justifyContent: "flex-start",
alignItems: "center",
flexDirection: "row",
marginTop: verticalScale(30),
paddingHorizontal: scale(10),
marginHorizontal: scale(20),
},
searchBarBorder: {
borderColor: "#1ACE2B",
borderWidth: 1,
borderRadius: scale(7),
},
suggestionItem: {
borderBottomWidth: 1,
borderColor: "#e0e0e0",
padding: verticalScale(10),
},
});
这就是我所说的:
import { View, Text, ScrollView } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import SliderComponent from "../Components/SliderComponents";
import ProductComponent from "../Components/ProductHorizontalComp";
import Stores from "../Data/Stores";
import FruitsAndVeggies from "../Data/FruitsAndVeggies";
import Akcijos from "../Data/Akcijos";
import SearchBar from "../Components/Generic/SearchBar";
/**
*
* @returns Component with the main page where all the stores and the basic prodcuts are shown
*/
export default function StoreScreen({ navigation }) {
/**
* Header that shows the title on the left and a Button on the right
* @param {string} titleText What will be shown on the title
* @param {Function} handleOnPress Function that will be called when pressed on the Button
* @param {string} buttonTitle Title of the
* @returns
*/
const viewAllHeader = (titleText, handleOnPress, buttonTitle = "ViewAll") => {
return (
<View
style={{
flex: 1,
paddingTop: 20,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Text
style={{
paddingHorizontal: 20,
fontSize: 24,
fontWeight: "700",
}}
>
{titleText}
</Text>
<Text
style={{ paddingHorizontal: 20, color: "#00da34" }}
onPress={handleOnPress}
>
{buttonTitle}
</Text>
</View>
);
};
const consl = () => console.log("Focused");
return (
<SafeAreaView style={{ flex: 1 }}>
<SearchBar
border={true}
handleOnFocus={consl}
onChangeText={(text) => console.log(text)}
data={FruitsAndVeggies}
keyExtractor={(item) => item.name}
/>
{/* Horizontal SrollView */}
<ScrollView>
{/* Horizontal scrollView of all the stores */}
{viewAllHeader("Stores", () =>
navigation.navigate("ViewAll", {
array: Stores,
title: "Stores",
type: 0,
})
)}
<ScrollView
style={{ marginTop: 10 }}
horizontal={true}
showsHorizontalScrollIndicator={false}
>
{Stores.map((store) => (
<SliderComponent
key={store.id} // Make sure to provide a unique key for each SliderComponent
name={store.name}
image={store.Logo}
/>
))}
</ScrollView>
{/* Fruits and veggies */}
{viewAllHeader("Fruits and Veggies", () =>
navigation.navigate("ViewAll", {
array: FruitsAndVeggies,
title: "Fruits and vegetables",
type: 1,
})
)}
<ScrollView
style={{ marginTop: 10 }}
horizontal={true}
showsHorizontalScrollIndicator={false}
>
{FruitsAndVeggies.map((item) => (
<ProductComponent
image={item.image}
store={item.store}
name={item.name}
quantity={item.quantity}
quantityType={item.quantityType}
price={item.price}
isCardDiscounted={item.isCardDiscounted}
discountPrice={item.discountPrice}
navigation={navigation}
soldBy={item.soldBy}
key={item.id}
/>
))}
</ScrollView>
{/* Actual discounts */}
{viewAllHeader("Other discounts", () =>
navigation.navigate("ViewAll", {
array: FruitsAndVeggies,
title: "Fruits and vegetables",
type: 1,
})
)}
{/* Discount Posts */}
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={{ marginTop: 10, paddingRight: 20 }}
>
{Akcijos.map((item) => (
<ProductComponent
key={item.id}
navigation={navigation}
image={item.image}
store={item.store}
name={item.name}
quantity={item.quantity}
price={item.price}
discountPrice={item.discountPrice}
isCardDiscounted={item.isCardDiscounted}
soldBy={item.soldBy}
/>
))}
</ScrollView>
</ScrollView>
</SafeAreaView>
);
}
这是运行良好的 scratch SearchBar:
import React, { useState } from "react";
import { Text, TextInput, FlatList, View } from "react-native";
import MiniSearch from "minisearch";
import FruitsAndVeggies from "../Data/FruitsAndVeggies";
import genericStyles from "../Styles/genericStyles";
export function ProfileScreen() {
const [searchResults, setSearchResults] = useState([]);
const [suggestions, setSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState([]);
const searchEngine = new MiniSearch({
fields: ["name"],
idField: "id",
storeFields: ["name"],
});
searchEngine.addAll(FruitsAndVeggies, ["name"]);
const onChangeText = (text) => {
if (!text) {
setSuggestions(false);
setSearchResults([]);
return;
}
let suggestions = searchEngine.autoSuggest(text);
if (suggestions) {
setSuggestions(suggestions);
setShowSuggestions(true);
console.log("Search suggestions: " + JSON.stringify(suggestions) + "\n");
}
// let searchResults = searchEngine.search(text);
// if (searchResults) setSearchResults(searchResults);
};
return (
<View style={{ flex: 1, ...genericStyles.genericCenteredAlignment }}>
<TextInput
style={{
marginTop: "50%",
width: "80%",
height: 50,
...genericStyles.genericBorder,
}}
onChangeText={onChangeText}
onBlur={() => setShowSuggestions(false)}
/>
{showSuggestions && suggestions && (
<View style={{ flex: 1 }}>
<Text style={{ fontWeight: "600" }}>Suggestions</Text>
<FlatList
data={suggestions}
renderItem={({ item }) => (
<Text
style={{ ...genericStyles.genericBorder, flex: 1 }}
onPress={() => onSuggestionPress(item)}
>
{item.suggestion}
</Text>
)}
keyExtractor={(item) => item.suggestion}
/>
</View>
)}
{searchResults && (
<View style={{ flex: 1 }}>
<FlatList
data={suggestions}
renderItem={({ item }) => (
<Text
style={{ ...genericStyles.genericBorder, flex: 1 }}
onPress={() => onSuggestionPress(item)}
>
{item.suggestion}
</Text>
)}
keyExtractor={(item) => item.suggestion}
/>
</View>
)}
</View>
);
}
这是搜索到的数据:
const FruitsAndVeggies = [
{
name: "Bananai",
quantity: 1,
quantityType: 2,
discountPrice: null,
isCardDiscounted: false,
price: 1.19,
image:
"https://storage.googleapis.com/download/storage/v1/b/lastmile-ui/o/images%2Fchain_products%2FIKI%2Fthumb_iNqZIWhnRVjabieALQrW.jpg?generation=1654518838165052&alt=media",
soldBy: 2,
store: 1,
id: 1,
},
{
name: "Batatai",
quantity: 1,
quantityType: 2,
discountPrice: true,
isCardDiscounted: false,
price: 1.9,
soldBy: 2,
store: 1,
id: 7,
},
{
name: "Citrinos",
quantity: 1,
quantityType: 2,
discountPrice: 1.69,
isCardDiscounted: false,
price: 2.49,
image:
"https://storage.googleapis.com/download/storage/v1/b/lastmile-ui/o/images%2Fproducts%2Fthumb_H83PY5twSuOZBcPevKFy.jpg?generation=1631695789675754&alt=media",
soldBy: 2,
store: 1,
id: 2,
},
{
name: "Mandarinai",
quantity: 1,
quantityType: 2,
discountPrice: 1.49,
isCardDiscounted: true,
price: 2.99,
image:
"https://storage.googleapis.com/download/storage/v1/b/lastmile-ui/o/images%2Fproducts%2Fthumb_Hy7HIb8PQCGqEvATZw8b.jpg?generation=1631695783290806&alt=media",
soldBy: 2,
store: 1,
id: 3,
},
{
name: "Avokadai",
quantity: 1,
quantityType: 5,
discountPrice: null,
isCardDiscounted: false,
price: 0.69,
image:
"https://storage.googleapis.com/download/storage/v1/b/lastmile-ui/o/import%2Fphotos%2Fconverted%2Fproduct%2Fglobal_1RA2MejrFpM61j0dpP43_cTAkEjfDTeMFXf0e6W4JKKhwXXA3_cIQ08jOtIths09SLOkmW_thumb.jpg?generation=1665477260007150&alt=media",
soldBy: 5,
store: 1,
id: 4,
},
{
name: "Sveriami obuoliai ŠAMPION",
quantity: 1,
quantityType: 2,
discountPrice: 0.69,
isCardDiscounted: false,
price: 0.99,
image:
"https://storage.googleapis.com/download/storage/v1/b/lastmile-ui/o/images%2Fproducts%2Fthumb_hEyrjLkWqfDN7Ce8b3K5.jpg?generation=1589636330241009&alt=media",
soldBy: 2,
store: 1,
id: 5,
},
{
name: "Besėklės žaliosios vynuogės",
quantity: 500,
quantityType: 1,
discountPrice: 1.49,
isCardDiscounted: false,
price: 2.49,
image:
"https://storage.googleapis.com/download/storage/v1/b/lastmile-ui/o/images%2Fchain_products%2FIKI%2Fthumb_hcmNGxBKsp0jcdhciqfK.jpg?generation=1654374772066054&alt=media",
soldBy: 5,
store: 1,
id: 6,
},
];
export default FruitsAndVeggies;
如您所见,我尝试了 useEffect() 挂钩,但它不起作用。
是否有任何解决方案可以让 setState 立即更新其值?
感谢您的帮助:)!!
我认为从 TextInput 组件调用 onChangeText 内的所有逻辑不是一个好主意。最好让该函数只承担一项职责,那就是设置存储用户插入的文本的状态。关于该方法的另一件事是,每次用户输入一个字符或删除它时,它都会向 miniSearch 引擎发出请求,想象一下会有多少次调用,它会降低应用程序的性能,并可能会弄乱渲染和渲染之间的数据。更新周期。
如果我要编写类似的代码,我会让 onChangeText 函数仅负责设置存储键入文本的状态。之后,我将对 miniSearch 引擎进行去抖调用,这样做后,miniSearch 引擎只会在所需的延迟后被调用。我能够做到你想要的,我希望它有帮助。我知道它的 ReactJS 示例,但如果你将 input 替换为 TextInput,将 span 替换为 Text,它应该可以正常工作。
import React, { useEffect, useState } from "react";
import MiniSearch from "minisearch";
import { documents } from "./documents";
function App() {
const [inputValue, setInputValue] = useState("");
const [suggestions, setSuggestions] = useState([]);
let miniSearch = new MiniSearch({
fields: ["title", "text"],
storeFields: ["title", "category"],
});
miniSearch.addAll(documents);
const onChangeText = (event: any) => {
setInputValue(event.target.value);
};
useEffect(() => {
const timer = setTimeout(() => {
let suggestions = miniSearch.autoSuggest(inputValue);
setSuggestions(suggestions);
}, 500);
return () => clearTimeout(timer);
}, [inputValue]);
return (
<>
{suggestions.map((current) => (
<span key={current.score}>{current.suggestion}</span>
))}
<br></br>
<input placeholder="Type here" onChange={onChangeText} />
</>
);
}
export default App;
没有什么会立即改变,每个生命周期方法之间都有很多渲染和更新周期。有时我们会弄乱渲染之间的数据。