UPDATE
OK, so I got a bit further. I made it work, but it's ugly and buggy :)
What I want to do - I'm trying to share a search field between two screens. Almost identical to the way Yelp works, actually. The first screen is the map with the search field on top. When you click the search field, it should move into the second search screen where you type the search term or select some default search category.
I have an expo app with bottom tab navigator (TS). Let me paste only the important parts:
export default function BottomTabNavigator() {
return (
<BottomTab.Navigator initialRouteName="Search">
<BottomTab.Screen
name="Search"
component={SearchNavigator}
...
and then
const SearchStack = createStackNavigator<SearchParamList>();
function SearchNavigator() {
return (
<SearchStack.Navigator headerMode="none">
<SearchStack.Screen name="SearchScreen" component={SearchScreen} />
<SearchStack.Screen name="SearchFocusScreen" component={SearchFocusScreen} />
</SearchStack.Navigator>
);
}
SearchScreen
const [searchQuery, setSearchQuery] = useState('');
// If we returned from the second search screen with a search term, set it in state.
useEffect(() => {
if (route.params) {
setSearchQuery(route.params.searchTerm);
}
}, [route]);
const searchRef = useRef(null);
const onFocus = () => {
// If we have a search term, pass it to screen 2, then blur the input so we don't loop back.
if (route.params) {
setSearchQuery(route.params.searchTerm);
navigation.navigate('SearchFocusScreen', { searchTerm: searchQuery });
} else {
navigation.navigate('SearchFocusScreen');
}
searchRef.current.blur();
};
return (
<View>
<MapView ... />
<View>
<SafeAreaView>
<Searchbar
ref={searchRef}
placeholder="Search"
value={searchQuery}
onSubmitEditing={() => {
initSearch({ searchQuery, region });
}}
onFocus={onFocus}
/>
Second search screen
const [searchQuery, setSearchQuery] = useState('');
const searchRef = useRef(null);
useEffect(() => {
searchRef.current.focus();
if (route.params) {
setSearchQuery(route.params.searchTerm);
}
}, [route]);
return (
<SafeAreaView>
<View>
<Searchbar
ref={searchRef}
placeholder="Search"
onChangeText={(query) => setSearchQuery(query)}
value={searchQuery}
onSubmitEditing={() => {
navigation.navigate('SearchScreen', { searchTerm: searchQuery }); // Pass the search query back to Search page 1
}}
...
Does it work? Yes. But it feels wrong. Also - there's an issue that when I type with an error into the search field in search screen 2, I get auto-correct suggestions. If I then quickly click Search, it auto corrects the typo after sending the error to search screen 1. And it's visible.
If you guys have a better strategy to achieve this, please do share.
Because navigate is an object, useEffect only compares the reference value. If you would like changes to that object to trigger a useEffect, I would look at https://github.com/kentcdodds/use-deep-compare-effect
Related
I'm trying to open a simple page with React Native WebView.
It's a single page web, and when you do a search, it prints out some information about your search.
After that, if you want to search again, press the back button on the device to move to the search box.
Because it is a single page, I cannot use goBack, so I created a function called cancel.
The problem is that when I click the device's back button, the function called cancel defined on the web is not executed.
The cancel function deletes the searched information and returns to the search window.
I will upload my code.
Please advise.
export default function App() {
const webviewRef = useRef(null);
const backAction = () => {
setBackTapping((prev) => prev += 1);
webviewRef.current.injectJavaScript('window.cancel()')
return true;
}
useEffect(() => {
const timer = setInterval(() => {
setBackTapping(0)
}, 1000)
return () => clearInterval(timer);
}, [])
useEffect(() => {
const backHandler = BackHandler.addEventListener('hardwareBackPress',backAction);
return () => backHandler.remove()
}, [])
useEffect(() => {
if(backTapping >= 2){
return BackHandler.exitApp();
}
},[backTapping])
return (
<KeyboardAvoidingView style={{ flex: 1 }}>
<StatusBar hidden />
<WebView
ref={webviewRef}
textZoom={100}
originWhitelist={['*']}
javaScriptEnabled
source={{ uri: 'myhome.com'}}
startInLoadingState={true}
/>
</KeyboardAvoidingView>
);
}
Expected behavior:
The cancel function is executed, all open windows are closed, and you are returned to the search window.
in my case, calling is wrong.
instead of :
webviewRef.current.injectJavaScript('window.cancel()')
use :
const generateOnMessageFunction = (data) => `
(function(){
window.dispatchEvent(new MessageEvent('message',{data: ${JSON.stringify(data)}}));
})()
`;
webviewRef.current.injectJavaScript(generateOnMessageFunction('cancel'));
detail referance :
https://github.com/react-native-webview/react-native-webview/issues/809
I have a a Header there is a text input and I have a Main component there are the list of products of the searched text.
So If the input text is empty then I want to show him his last searched things. If he type anything then I want to show him the products with the same name as the input text. I make a condition like this:
const SearchRoot = ({ }: ISearchRoot) => {
const searchParam = useSelector((state: RootState) => state.Search.searchParam);
return (
<>
{ searchParam.length > 0 ?
<SearchList />
:
<LatestHistory />
}
</>
)
}
So if I type anything then my input are closing automatically. But if I press again the keyboard and typing then its not closing and working. So its only happend when the component changing from <LatestHistory to <SearchList only one time. So how can I make my keyboard always open when the component is chaning ?
Search
const Search = () => {
return (
<View style={s.container}>
<StatusBar backgroundColor='#fff' />
<SearchHeader />
<SearchRoot />
</View>
)
}
SearchHeader
const SearchHeader = () => {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParams>>();
const dispatch = useDispatch();
// filters
const [searchText, setSearchText] = useState<string>('');
const handleChangeText = (e: string) => {
dispatch(setSearch({searchParam: e}));
setSearchText(e)
};
const handleClearText = () => {
dispatch(setSearch({searchParam: ''}));
setSearchText('');
};
const handlePressSeach = () => {
searchText.length > 0 &&
navigation.navigate('Searched', {
searchText,
searchType: tabType
});
};
const handleGoBack = () => navigation.goBack();
return (
<View style={s.container}>
<View style={s.header}>
<View style={s.backContainer}>
<GoBackIcon
onPress={handleGoBack}
color='#555'
/>
</View>
<View style={s.inputContainer}>
<SearchInput
value={searchText}
onChangeText={handleChangeText}
onPressSearched={handlePressSeach}
onPressClearTextField={handleClearText}
autoFocus={true}
style={s.sInput}
/>
</View>
</View>
</View>
)
}
SearchRoot
const SearchRoot = ({ }: ISearchRoot) => {
const searchParam = useSelector((state: RootState) => state.Search.searchParam);
return (
<>
{ searchParam.length > 0 ?
<SearchMain />
:
<Text>Vorschläge</Text>
}
</>
)
}
I am very thankful for your help!!
I just read your problem statement and matched it with your Code.
✦First of all your sequence is wrong.
✦First Comes the Previous Searched Result.
✦Then when you press it then it should clear the previous search result.
✦Then On text change Property
✦Then OnPress Searched
Maybe that's Why it closes. If that's Not the case then!
(In my Opinion)
when then you click the search bar the previously searched result disappears and this is done by the function handleClearText. Now When the Function Completes it's Functionality the search bar closes. The Second time you open the search bar it works as their's no Previous Search result to clear.
So the Problem is with the handleClearText function.
My Proposed Solution in case 2 is
We can call the search method again (at the end) of handleClearText function.
then it will re open the search bar with the new state.
I hope it resolves your issue, Regards...
first of all const [searchText, setSearchText] = useState(''); this whole logic move to parent class search and from there pass it to siblings to avoid any confusion.whenever re-rendering happens values will be passed correctly to child.I think somewhere that is going wrong.Either use state and prop drilling or store, dont mix it up.
I will literally rip out my hair from my head because of how long I've been working on this. I cannot figure out why my filterArray (which is updating properly in my console and filtering itself based on the search from the searchUser function) is working but when I output it onto my screen in my App it keeps saying it's empty? I want it to continuously update the way my searchUser function does but I'm not sure how to do this. Should I call searchUser in my big return statement at the end? Otherwise my filterArray shows up as empty (which is what I'm actually trying to output), and response just copies the full name of PDF names and doesn't update on the screen or narrow down my list based on my search (which is what filterArray does). I think this is because my useEffect function runs once and grabs the filterArray when its empty and just keeps it empty and doesn't reinitialize it.
Sorry if this is confusing but I am just losing it. I also dont want the MyComponent function to return the array items as a component, but as a but if I change it my code tells me I cant return things that are text as a list. Please please help.
const searchUser = async (input) => {
var filterArray = [];
//nametoURL is a dictionary that stores a full list of pdf names from Firebase
Object.keys(nameToURL).map((item) => {
if (item.includes(input) && !filterArray.includes(item)) {
filterArray.push(item);
console.log("FOUND A MATCH");
console.log("MATCH IS: ", filterArray);
} else {
console.log("NO MATCH");
}
});
return { filterArray };
};
const Map = ({ navigation }) => {
var [filterArray, setFilterArray] = useState([]);
var [userInput, setInput] = useState([]);
var [] = useState([]);
var [result, setResult] = useState({});
function MyComponent({ response }) {
console.log(response);
return <Text>{Object.values(response)}</Text>;
}
useEffect(() => {
async function fetchData() {
// You can await here
const response = await searchUser(userInput);
setResult(response);
setFilterArray(filterArray);
// ...
}
fetchData();
}, []);
return (
<Container>
<Header searchBar rounded>
<Item>
<Icon name="search" />
<Input
placeholder="Search For A Map (Case Sensitive)..."
onChangeText={(text) => {
searchUser(text), setInput(text);
}}
autoCapitalize="none"
autoCorrect={false}
/>
</Item>
</Header>
<ScrollView>
<Text style={styles.titleStyle}> Camp Map </Text>
<MyComponent response={result} />
</ScrollView>
</Container>
);
};
The problem is that you're no setting the result state with the result from the searchUser call when the text changes. You can declare a function callback and do that there to have a cleaner code.
function MyComponent({ response }) {
console.log(response);
return <Text>{Object.values(response)}</Text>;
}
const Map = ({ navigation }) => {
var [filterArray, setFilterArray] = useState([]);
var [userInput, setInput] = useState([]);
var [] = useState([]);
var [result, setResult] = useState({});
const onChangeText = async (text) => {
setInput(text);
const response = await searchUser(text);
setResult(response);
// not sure when or were you are assigning filterArray though
setFilterArray(filterArray);
}
useEffect(() => {
async function fetchData() {
// You can await here
const response = await searchUser(userInput);
setResult(response);
setFilterArray(filterArray);
// ...
}
fetchData();
}, []);
return (
<Container>
<Header searchBar rounded>
<Item>
<Icon name="search" />
<Input
placeholder="Search For A Map (Case Sensitive)..."
onChangeText={onChangeText}
autoCapitalize="none"
autoCorrect={false}
/>
</Item>
</Header>
<ScrollView>
<Text style={styles.titleStyle}> Camp Map </Text>
<MyComponent response={result} />
</ScrollView>
</Container>
);
};
Making API calls every time the user types is not recommended, you can check out this pattern to debounce the call for usability https://gist.github.com/simonw/c29de00c20fde731243cbac8568a3d7f
When I open the application and if the isAuth variable is true, it means the user has authorization, then I want to go to the provider screen.
My authorization component.
export const LoginScreen: NavigationStackScreenComponent<NavigationParams> = observer(({ navigation }: NavigationParams) => {
useEffect(() => {
AuthState.checkAuthentication();
}, []);
if (AuthState.isAuth) {
navigation.navigate('Provider');
}
return <View style={styles.body}>{LoaderState.loading ? <LoaderComponent /> : <AuthComponent />}</View>;
});
My provider component.
export const ProvidersScreen: NavigationStackScreenComponent<NavigationParams> = observer(({ navigation }: NavigationParams) => {
useEffect(() => {
ProvidersState.setProviders();
}, []);
return (
<View style={styles.body}>
{LoaderState.loading ? (
<LoaderComponent />
) : (
<ItemListComponent itemsList={ProvidersState.providersList} />
)}
</View>
);
});
But I get a warning. I understand that it is associated with the react-navigation library.
How to switch to another screen using a conditional statement?
Functional components are basically the equivalent to the render() function in class components. You are calling navigate before the component gets render and that's a problem.
You can either call the navigate inside your useEffect or put the conditional logic in the parent view.
In the first case, navigate will be called AFTER the component renders
useEffect(() => {
AuthState.checkAuthentication();
if (AuthState.isAuth) {
navigation.navigate('Provider');
}
}, []);
so it will be visible for half a second and then change. If you'd like to avoid this, then go for the second option.
You can put the condition in the parent. Something like
<View>{AuthState.isAuth ? <ProvidersScreen /> : <LoginScreen />}</View>
I am working with react native.
I have component listing by using
And, when the state to give data to update the list change. It won't update immediately. It take few seconds to re-render.
so, how can I update the component immeidately
//Listcomponent
const ListGlossary = ({glossaries, onPressGlossary, navigation, searchField}) => {
return (
<FlatList
data={glossaries}
keyExtractor={(item) => item.key}
renderItem={({item}) =>
<TouchableHighlight
onPress = {() => navigation.navigate('DetailGlossaryScreen', { searchField: searchField, word: item.word, translate: item.translate})}>
<ListItem
key={`${item.key}`}
title={`${item.word}`}
/>
</TouchableHighlight>
}
/>
}
//And you can find here the home screen component
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
glossaries: [],
searchField: '',
}
}
componentDidMount () {
Promise.resolve().then(() => {this.setState({glossaries: JSONDataFromFile, isLoading: false})})
}
onSearchChange = (inputText) => {
this.setState({searchField: inputText});
}
render(){
return(
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
glossaries = this.state.glossaries;
for(let i = 0, l = glossaries.length; i < l; ++i) {
if(glossaries[i].word.toLowerCase().indexOf(searchField) === 0){
filteredWords.push(glossaries[i]);
}
}
}
{this.state.isLoading ?
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
:
<ListGlossary
navigation = {this.props.navigation}
glossaries = {filteredWords}
onPressGlossary={this.onPressGlossary}
searchField = {this.state.searchField}
/>
}
)
}
Please show the whole component, and give the length of the list.
--- Edit
I suspect you're doing too much work in the render function. You're filtering every time it gets called, and since you're passing in the navigation prop (I assume you're using React-Navigation), it'll get called frequently. If you're using a stack navigator, all the other screens are also getting re-rendered every time you navigate to a new screen. Avoid passing navigation as much as possible, or use a HOC composition to ignore it.
You probably don't need to be filtering glossaries every time the user changes the search value. Use the shouldComponentUpdate lifecycle method.