I have a Text Input in React Native and I want to display the typed input on real time (two way binding ) in a way that when typing each letter in the input, the text under the input field is automatically updated with the letter typed. I want to achieve this without the use of state but this code doesn't work
export default function App() {
const updateDisplay=(typedLetters)=> {return (<View><Text>typedLetters</Text></View>)}
return(
<TextInput
style={{height: 40,margin:20}}
placeholder="Search"
onChangeText={(text)=>updateDisplay(text)}
/>)
}
First, updateDisplay should be like this:
const updateDisplay = (typedLetters) => {
return (
<View>
// notice argument is in {}
<Text>{typedLetters}</Text>
</View>
);
};
In order to show the text, you have to call the updateDisplay inside the component:
return (
<View>
<TextInput
style={{ height: 40, margin: 20 }}
placeholder="Search"
onChangeText={(text) => updateDisplay(text)}
/>
{/* what parameter you are going to be passing to this function */}
{updateDisplay()}
</View>
The thing is when you defined the updateDisplay, it receives an argument. So somehow you need to extract the input value of TextInput component. That is why we need to use the state.
TextInput is actually a function and you cannot go inside of a function and grab a value. Inside a function, you mutate the state. we use setState because we are not setting the state, we are asking React and it decides when to set it.
export default function App() {
const [text, setText] = useState(null);
const updateDisplay = (typedLetters) => {
return (
<View>
<Text>{typedLetters}</Text>
</View>
);
};
return (
<View>
<TextInput
style={{ height: 40, margin: 20 }}
placeholder="Search"
// now save the input value to state.
onChangeText={(text) => setText(text)}
/>
{/* what parameter you are going to be passing to this function */}
{updateDisplay(text)}
</View>
);
}
It is not possible to do this without state. Using state you provide a hook for the UI to know when to re-render your component. Without it, your UI will not re-render, and you won't see any difference.
Related
Im trying to pass a function handleNewFavourite (which updates my favouriteList state array) from my HomeScreen to my DetailsScreen via navigation params but Im getting the following error: Non-serializable values were found in the navigation state
How should I pass functions that modified the state between different stack screens?
HomeScreen code:
<FlatList
data={checkCategory()}
renderItem={({item}) => (
<TouchableOpacity
onPress={() =>
navigation.navigate('Details', {
item,
handleNewFavourite,
})
}>
<LessonCard lesson={item} />
</TouchableOpacity>
)}
/>
DetailScreen code:
const LessonDetails = ({lesson, handleNewFavourite}: LessonProps) => {
const [favourite, setFavourite] = useState<boolean>(lesson.favourite);
return (
<LessonDetailsContainer>
<LessonDetailsInfoContainer>
<LessonDetailsCategoryHead>
<LessonDetailsCategory>{lesson.category}</LessonDetailsCategory>
<TouchableOpacity
onPress={() => {
setFavourite(!favourite);
handleNewFavourite(lesson);
}}>
<LessonDetailsFavouriteIcon>
{favourite ? '❤️' : '🤍'}
</LessonDetailsFavouriteIcon>
</TouchableOpacity>
</LessonDetailsCategoryHead>
<LessonDetailsTitle>{lesson.title}</LessonDetailsTitle>
<LessonDetailsAuthor>{lesson?.author}</LessonDetailsAuthor>
</LessonDetailsInfoContainer>
<LessonDetailsCardImage
source={{
uri: lesson.image,
}}
/>
<LessonDetailsContentContainer>
<LessonDetailsDescriptionText>
{lesson.content}
</LessonDetailsDescriptionText>
</LessonDetailsContentContainer>
</LessonDetailsContainer>
);
};
export default LessonDetails;
For situation like this, you should learn global state management. ( Context API - Redux etc. )
I think you are disrupting in the wrong way the parameters passed to DetailScreen it should be something like this:
const LessonDetails = ({route}: LessonProps) => {
const {lesson, handleNewFavourite} = route.params;
// The rest of your component here
}
As the documentation here suggested. But as #omerfarukose mentioned is not a bad idea to use state management in this particular scenario
I created a short test to get an explanation of how my code reacts.
Basically I want to call a function only when I press my button.
No problem, the code below works.
// Components/Test.js
import React, { useState } from "react";
import { View, StyleSheet, TextInput, Button } from "react-native";
function Test(props) {
const [text, setText] = useState("");
const myFunction = () => {
console.log("test");
};
return (
<View style={styles.container}>
<TextInput
style={{ borderWidth: 1, width: "70%" }}
onChangeText={(text) => setText(text)}
/>
<Button title="Button" onPress={myFunction} />
</View>
);
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
export default Test;
As I'm learning Javascript at the same time, I thought I'd put parentheses after my function. Technically, if I want to pass parameters to my function, I thought I should do it like this.
<Button title="Button" onPress={myFunction()} />
And now with this change, my "myFunction" is called systematically if I modify the text in TextInput.
Can someone explain what is going on? Thank you !
You should use arrow function
<Button title="Button" onPress={() => myFunction('parameter')} />
if you just call like
<Button title="Button" onPress={myFunction()} /> // its calling function directly
Its call the function directly
Arrow Functions:
We all love them, but should be cautious when using them with React. Arrow functions are great, they let us quickly create new javascript function short hand. The consequence of this is exactly that, it creates a new function every time it is executed. For the most part in Javascript this isn’t a real issue. When it comes to the React render method this can quickly become an issue.
render(){
return (
<View>
<TouchableOpacity onPress={() => console.log('Hello!')}>
Click Me
</TouchableOpacity>
</View>
)
}
So what could be the harm here, the button is simply just logging out a string on press. But in fact, when React executes this render method it will always see a new function. The arrow function returns a new function every time. This causes React to think something has changed in our view, when in fact nothing has.
logHello(){
console.log('Hello!');
}
render(){
return (
<View>
<TouchableOpacity onPress={this.logHello}>
Click Me
</TouchableOpacity>
</View>
)
}
Everytime you change the text, the state of the component also changes becuase, you've used onChangeText={(text) => setText(text)}, this causes your component to re-render.
During re-render, the following line is also executed. In this line you are calling myFunction.
<Button title="Button" onPress={myFunction()} />
In short, changing the text causes state update, which in turn causes a re-render, and during that re-render myFunction is called.
I'm trying to push text input into an array. When I modify the input text and push it, the array is wiped before the push (not showing my previous pushes in the console.log). What am I missing?
const [text, onChangeText] = React.useState('Useless Text');
let chatHistory = [];
function logHistory(text) {
chatHistory.push(text);
console.log(chatHistory);
}
return (
<View style={styles.container}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
/>
<Button title={'Ask the computer...'} onPress={() => logHistory(text)} />
</View>
</View>
);
I supposed that because you change state, it rerender this component and redeclare chatHistory as empty array.
use useState for chatHistory instead.
And instead of Array.push i recommend to use chatHistory = [...chatHistory, text],in useState case setChatHistory([...chatHistory,text]);
If you want the changes in chatHistory to be reflected on your component you need to use a state and not just a plain variable. Such variables do not survive through re-renders and anything you store in regular variables will be lost. Try out after making these changes
const [chatHistory, setChatHistory] = React.useState([]);
function logHistory(text) {
setChatHistory((history) => [...history, text]);
}
Also the console.log statement might not give you the result you are expecting. You might also want to display the chat history somewhere maybe in a flat list.
1.Here is my custom text input custom component i define props ref which i want to use in
parent component
export const InputField = ({
placeholder,
onChangeText,
placeholderTextColor,
showSecureIcon,
isSecure,
onPressIcon,
keyboardType,
returnKeyType,
maxLength,
secureIconColor,
handleUseCallBack,
ref,
value
}) => {
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder={placeholder}
onChangeText={onChangeText}
placeholderTextColor={placeholderTextColor}
autoCapitalize="none"
secureTextEntry={isSecure}
keyboardType={keyboardType}
returnKeyType={returnKeyType}
maxLength={maxLength}
value={value}
ref={ref}
/>
<View style={styles.iconContainer}>
{showSecureIcon ?
<TouchableOpacity
onPress={onPressIcon}
>
<Ionicons
name={isSecure ? "eye-off-sharp" : "eye-sharp"}
color={secureIconColor}
size={constants.vw(25)}
/>
</TouchableOpacity>
:
null
}
</View>
</View>
)
}
2-Now the part where i want to change my ref
in this field i create the text inputs field of password and confirm where i want to change
my focus
const passwordRef = useRef(null);
const confirmPasswordRef =
useRef(null);
const onSubmitEditing=()=>{
confirmPasswordRef.current.focus();
}
<View style={{ marginTop: constants.vh(66) }}>
<Components.InputField
placeholder={constants.ConstStrings.setANewPassword}
ref={passwordRef}
onSubmitEditing={()=>onSubmitEditing()}
onChangeText={(text) => setState({
...state,
password: text
})}
/>
</View>
<View style={{ marginTop: constants.vh(20) }}>
<Components.InputField
ref={confirmPasswordRef}
placeholder={constants.ConstStrings.confirmPassword}
onChangeText={(text) => setState({
...state,
confirm_password: text
})}
/>
</View>
This part is the end part qwertyuiopsdf;';dsyuiopoiuteweryuiopuytreep[gfjklvcvbnm,mvcxzxcewqwe[poiuyd
The problem with your code is that you've created a custom input component, but haven't given to it the instructions on how to handle different methods.
So, in your case, the generic Input component knows what method is focus, but your custom one doesn't.
What you should do:
Make your input component a forwardRef one so that you can pass a ref to it and then be able to do actions on it.
Use useImperativeHandle so that you can call internal methods of your ref.
Create a focus fn which in your custom input will basically call the focus method of your ref.
You cannot pass the ref in props as you are doing, that just doesn't work in React.
I suppose all of your components are functional, in that case:
export const InputField = React.forwardRef({
placeholder,
...
}, ref) => { // pass a ref to here, this way you will let React know to associate the ref from external component to an internal ref
const textInput = useRef(null); // Create a ref to pass to your input
useImperativeHandle(ref, () => ({ // To be able to call `focus` from outside using ref
focus,
});
const focus = textInput.current.focus();
return (
<View style={styles.container}>
<TextInput
ref={textInput}
style={styles.input}
...
/>
...
</View>
)
}
And then in your component you just have to pass a fresh ref created by useRef and then you'll be able to call focus on in.
Tell me whether that solves it for you!
I have a requirement of showing a tab like behavior inside a react native Modal
I have started by creating a state variable selectedSub which is initialized with value 'from'
Now my modal has 2 TouchableHighlight as below
_availabiltyModal() {
return (
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'from' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'from' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'from' && styles.activeText}>From</RkText>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'to' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'to' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'to' && styles.activeText}>To</RkText>
</TouchableHighlight>
{this._renderPicker()}
)}
These two are responsible to change the state param selectedSub as required.
Based on this state param i am conditionally showing another component i made and imported as below
_renderPicker() {
if (this.state.selectedSub == 'from') {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.from }} />
} else {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.to }} />
}
}
I have called this function in the Modal below the TouchableHighlight's
Now as per the RN docs when ever the state variable is updated with this.setState() method the component should re-render. Everything else is working fine (TouchableHighlight styles changing) and also the updates to the state are reflecting in the console. But the _renderPicker function does not return the changed view and is always stuck on the view which was set when the parent was initialized as pointed earlier.
Could somebody help me point the problem here which i might have been overlooking. Also on the side note all this calls are actually directly made outside the main render() method. Could that be a possible issue
------update------
here is the complete reference
render() {
return({this._availabiltyModal()}
<View style={appStyles.tagsWrapper}>
{this.state.week.map((day, i) => {
return (
<TouchableHighlight
key={i}
activeOpacity={0.9}
style={[appStyle.mr10, appStyle.mb10]}
onPress={() => {
this.setModalVisible(true, day);
}}>
<Text style={appStyle.tag}>{day}</Text>
</TouchableHighlight>
)
})}
</View>
)
}
Move the _renderPicker method inside the render() method like...
render() {
...
{this._renderPicker()}
...
}
Looking at the code of the MODAL component from react-native
render(): React.Node {
....
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
this.props.children
);
return (
<RCTModalHostView
....>
<View style={[styles.container, containerStyles]}>
{innerChildren}
</View>
</RCTModalHostView>
);
}
The state you are changing are of the component that use the modal component thats render its children through the upper function.. when the state update it only re render the component whose state is updated.. so somewhere down rendering child inside component it does not get re render until force re render is applied.
Here is an helpful article to further explain how exactly this functions, forcefully re-rendering the child component