Run onChangeText doesnt update correctly TextInput value - react-native

I'm creating my own searchbar in react-native, so I want to allow user clear the text input. To achieve this I'm using useState to track when the text input value is empty or not. I'm executing onStateChange('') to clean the text input value, the problem is if I access to value this show the value before onStateChange('') not empty string ''.
e.g. If I type react on input and after that execute onStateChange('') I expect the input value property to be empty, but it is not, value contains react yet, If I press other key the value is '' and not the current one. i.e. it is not saving the last value entered but the previus one.
SearchBar Usage
import React, { useState } from 'react';
import {
TextInput,
TouchableWithoutFeedback,
View,
} from 'react-native';
function SearchBar(props) {
const {
onChangeText,
value = '',
...textInputProps
} = props;
// Access to native input
const textInputRef = React.createRef();
// Track if text input is empty according `value` prop
const [isEmpty, setIsEmpty] = useState(value ? value === '' : true);
const clear = () => {
// Clear the input value with native text input ref
textInputRef.current?.clear();
// Update the text input `value` property to `''` i.e. set to empty,
if (onChangeText) onChangeText('');
// this is where the problem lies, because If I check the input text `value` property, this show the previus one, not the current one.
console.log(value);
};
const handleOnChangeText = (text) => {
if (onChangeText) onChangeText(text);
// track if text input `value` property is empty
setIsEmpty(text === '');
};
// Render close icon and listen `onPress` event to clear the text input
const renderCloseIcon = (iconProps) => (
<TouchableWithoutFeedback onPress={clear}>
<View>
<Icon {...iconProps} name="close" pack="material" />
</View>
</TouchableWithoutFeedback>
);
return (
<TouchableWithoutFeedback onPress={focus}>
<View>
<TextInput
{...textInputProps}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
onChangeText={handleOnChangeText}
ref={textInputRef}
/>
// helper function to render a component with props pased to it
<FalsyFC
style={{ paddingHorizontal: 8 }}
// If text input `value` is `isEmpty` show the `x` icon
component={!isEmpty ? renderCloseIcon : undefined}
/>
</View>
</TouchableWithoutFeedback>
);
}
SearchBar Usage
function SomeComponent() {
const [search, setSearch] = useState('');
const updateSearch = (query: string) => {
setSearch(query);
};
return (
<SearchBar
onChangeText={updateSearch}
value={search}
/>
)
}

I think you are missing a value prop in the TextInput (see React Native doc for TextInput).
In your case you would set your value variable from the props object you pass to the SearchBar functional component:
<TextInput
{...textInputProps}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
onChangeText={handleOnChangeText}
ref={textInputRef}
// add the line below
value={value}
/>

Every state changes are asynchronous even Hooks. With useState() you can't define a callback function even onStateChange() is not called. In your case, you should use the function useEffect().
function SomeComponent() {
const [search, setSearch] = useState('');
useEffect(() => {
//Write your callback code here
//copy-paste what you wrote on function onStateChange()
}, [search]);
const updateSearch = (query: string) => {
setSearch(query);
};
return (
<SearchBar
onChangeText={updateSearch}
value={search}
/>
)
}
Here is a link that should help you :
https://dmitripavlutin.com/react-useeffect-explanation/#:~:text=callback%20is%20the%20callback%20function,dependencies%20have%20changed%20between%20renderings.

Related

Why my input closes when new Component is showing?

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.

React Native Picker, get selectedValue

I have a Picker component like this:
const reasons = [
{ name: 'A' },
{ name: 'B' },
{ name: 'Other' }
];
<Picker
selectedValue={state.reasonSelected}
onValueChange={value => {
changeTransactionReason(value);
}}
>
{reasons.map((reason, key) => (
<Picker.Item
key={key}
label={reason.name}
value={reason.name}
/>
))}
</Picker>
Then, I need to render an Input component only if the item "Other" is picked.
I could setState that value and then ask if:
{state.itemSelected === 'Other' && (
<Input onChangeText={text => { reduxActionToSaveValue(text) }}/>
)}
But I don't want to mix local state and redux. The values will be stored on redux, but
How can I compare the value 'Other' without losing it actual value? So I want to know if there's a way with Picker to get the selected value without setting local states
Why not compare the value directly with redux? if you are using react-redux you can map the value where selected item is stored using mapStateToProps (here is the documentation) and if you are directly using redux you can still do it. then once the action of choosing other has dispatched and the value is set your input will show.
if you don't want to use redux, you need to get the Other value from props and initialize the value on the picker.
Regards
Only with redux I would do this:
I would create a reducer reasons
const intialState = {
reasonSelected: '1',
reasons: [{name: '1'}, {name: '2'}, {name: '3'}],
};
const reasons = (state = intialState, {type, payload}) => {
switch (type) {
case 'CHANGE_SELECTED':
return payload;
default:
return state;
}
};
export default reasons;
In this reducer I would initialize values and the selecteddefault and I would create an action CHANGE_SLECTEd to change the value selected.
and in the component I would call the reducer, I called Dashboard.
import React from 'react';
import {View, Text, Picker} from 'react-native';
import {useSelector, useDispatch} from 'react-redux';
const Dashboard = () => {
const reasonObj = useSelector((state) => state.reasons);
const dispatch = useDispatch();
const onValuePickChange = (value) => {
dispatch({
type: 'CHANGE_SELECTED',
payload: {
reasonSelected: value,
reasons: reasonObj.reasons,
},
});
};
return (
<View>
<Text>Dashboard</Text>
<Picker
selectedValue={reasonObj.reasonSelected}
onValueChange={(value) => onValuePickChange(value)}>
{reasonObj.reasons.length > 0 &&
reasonObj.reasons.map((reason, key) => (
<Picker.Item key={key} label={reason.name} value={reason.name} />
))}
</Picker>
</View>
);
};
export default Dashboard;
inthis form you are not using local state and everything is immediately update to redux.
I hope this info will help you...
Regards

Passing state via react navigation

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

How to get input value from react-native-element

In React Native 16.
Get input value
<Input
...
onChange={event => getValue(event)}
/>
Output value
const getValue = event => {
console.log(event.nativeEvent.text);
};
The Input component from react-native-elements is basically rendering the TextInput component. So you can use onChangeText instead of onChange like
import React, { Component } from 'react';
import { Input } from 'react-native-elements';
export default function UselessTextInput() {
const [value, onChangeText] = React.useState('Useless Placeholder');
return (
<Input
onChangeText={text => onChangeText(text)}
value={value}
/>
);
}
Hope this helps
Set value to the state.
const getValue = event => {
this. setState({text: event.nativeEvent.text});
};

react-native reseting TextInput after ClearTextOnFocus

I am building a form in React-Native and have set ClearTextOnFocus to true as it is easier to handle dynamic formating for editing.
I am trying to add a reset function by setting all local state to the redux store, but if the user has not typed anything in a selected TextInput, the local state has not changed, and react native does not re-render the TextInput; leaving it blank.
Anyone have any thoughts on how I can unclear the TextInput or force React to re-render. Code is a work in progress, but here are the relevant bits.
Thanks
class GoalScreen extends Component {
componentWillMount = () => this.setPropsToState();
onReset = () => {
this.setPropsToState();
}
onChange = text => this.setState({ [text.field]: text.input });
setPropsToState = () => {
const { name } = this.props.goal;
this.setState({ name });
};
render() {
const { name } = this.state;
return (
<View style={styles.screenContainer}>
<Text style={styles.text}> Name </Text>
<TextInput
placeholder="a brand new bag"
keyboardType="default"
autoCorrect={false}
style={styles.inputField}
clearTextOnFocus
onChangeText={text => this.onChange({ input: text, field: 'rate' })}
value={name}
/>
</View>
}
}
So, I'm not using Redux, and my use case might be a bit different than yours, but I thought my solution might still be relevant here, if only to confirm that (after hours of wrangling with this) it appears that passing true to the clearTextOnFocus prop prevents further updates to a TextInput component.
I tried every conceivable workaround (like setNativeProps(), forceUpdate()) but nothing worked, so I ended up having to basically write my own logic for clearing and resetting the input text.
This component should 1) clear input text on focus and then 2) reset it to its previous value if the user hasn't pressed a key:
class ResettableInput extends Component {
state = {
Current: this.props.value,
Previous: ""
};
KeyPressed = false;
//cache current input value for later revert if necessary, and clear input
onFocus = () => {
this.setState({ Previous: this.state.Current, Current: "" });
};
//record whether key was pressed so input value can be reverted if necessary
onKeyPress = () => {
this.KeyPressed = true;
};
onChangeText = text => {
this.setState({ Current: text });
};
//if no key was pressed, revert input to previous value
onBlur = () => {
if (!this.KeyPressed) {
this.setState({ Current: this.state.Previous, Previous: "" });
}
};
render = () => {
return (
<TextInput
onChangeText={this.onChangeText}
value={this.state.Current}
onBlur={this.onBlur}
onFocus={this.onFocus}
onKeyPress={this.onKeyPress}
/>
);
};
}