React Native saves old Text Input data - reactjs

So I have a text input that saves to a setState on each change. However if I hit the cancel button which takes me back a page, and then I go back to the first page, my changes are still on the first page.
I feel that this might be the phone that is saving cache or something on my page but how can I make this value reset?
I am also using react navigation for my page navigation
<TextInput
ref={ref => this.companyNameInput = ref}
onChangeText={text => { setCompanyNameInput(text); setSaveFlag(true);}}
style={{
position: 'absolute',
left: 150,
color: 'rgba(0, 159, 150, 1)'
}}
>
<Text12
style={{
color: 'rgba(0, 159, 150, 1)',
padding: 0,
marginBottom: 0
}}
>
{companyNameInput}
</Text12>
</TextInput>

You can reset your text input when you press cancel button
let companyNameInput = null; //if this is functional component else class then use this
const onCancel = () => {
companyNameInput.clear()
}
Or you can set event listener on blur of current screen like this
useEffect(() => {
const unsubscribe = props.navigation.addListener('blur', () => {
onCancel();
})
}. [props.navigation])
you can also clear your state variable by this
setCompanyNameInput('');
NOTE: if you are using class component then use componentWillUnmount instead of useEffect, "this" only works on class based component

Related

TextInput goes Missing, but Keyboard Stays After Every KeyStroke?

Trying to figure out an issue with the TextInput component I have which is acting weird. Basically, after every keystroke, the textinput seems to be losing its focus, but the keyboard seems to stay..
Im implementing it as a search bar that is triggered / animated when a search icon is touched by the user:
<TouchableHighlight
activeOpacity={1}
underlayColor={"#ccd0d5"}
onPress={onFocus}
style={styles.search_icon_box}
>
onFocus method:
const onFocus = () => {
setIsFocused(true);
const input_box_translate_x_config = {
duration: 200,
toValue: 0,
easing: EasingNode.inOut(EasingNode.ease)
}
const back_button_opacity_config = {
duration: 200,
toValue: 1,
easing: EasingNode.inOut(EasingNode.ease)
}
// RUN ANIMATION
timing(input_box_translate_x, input_box_translate_x_config).start();
timing(back_button_opacity, back_button_opacity_config).start();
ref_input.current.focus();
}
just a simple animation where when triggered the search bar will slide from the right side of the screen
<Animated.View
style={[ styles.input_box, {transform: [{translateX: input_box_translate_x}] } ]}
>
<Animated.View style={{opacity: back_button_opacity}}>
<TouchableHighlight
activeOpacity={1}
underlayColor={"#ccd0d5"}
onPress={onBlur}
style={styles.back_icon_box}
>
<MaterialIcons name="arrow-back-ios" size={30} color="white" />
</TouchableHighlight>
</Animated.View>
<SearchBar
placeholder='Search'
keyboardType='decimal-pad'
returnKeyType='done'
ref={ref_input}
value={searchText}
onChangeText={search}
onClear={onBlur}
onSubmitEditing={onBlur}
onFocus={() =>console.log("focus received" ) }
onBlur={() => console.log("focus lost") }
/>
</Animated.View>
Search
const search = (searchText) => {
setSearchText(searchText);
let filteredData = AnimalList.filter(function (item) {
return item.tag_number.toString().includes(searchText);
});
setFilteredData(filteredData);
}
So, when I clicked on the search icon, the search bar will present itself through animated view and the keyboard will automatically be focused. However, after entering a single character on the keyboard the searchbar just vanishes with keyboard still showing.
I tried to debug using onFocus={() =>console.log("focus received" ) } and it looks like the searchBar is still focused on, its just not showing
EDIT: Issue Video Here https://github.com/renwid/test/issues/1
You can show the full version of SearchBar to get more help
[Updated]
The initial step, Animated.Value must be
const input_box_translate_x = useRef(new Value(width)).current;
const back_button_opacity = useRef(new Value(0)).current;
instead of
const input_box_translate_x = new Value(width);
const back_button_opacity = new Value(0);
Because you using the function component and re-render might be re-create the component and the Animated.Value will be re-create too. So the Animated.Value can not keep the state and cause you issue

How to apply animation in react-native?

I want to add animation to this search bar.
when the user touches the search bar it size decreases and again increases and gets to its default size(like animation in popups)
This is my code
<View style={{flexDirection:'row',alignSelf:'center'}}>
<TextInput
onChangeText={(text) => setSearch(text)}
onFocus={()=>{
setSize('92%');
setInterval(()=>{setSize('95%')},1000)
}}
placeholder="Search"
style={{...styles.searchbox,width:size}}
></TextInput>
</View>
I am currently trying to change width..
Firstly, I suggest you to take a look at RN animated documentation, maybe it will help you to understand better how the animations work.
Also, it depends on what you're having there: a class component or a function component.
If you're using a function component, you could do it like this:
Creating a custom hook, called, let's say useAnimation(), which would look something like this
export const useAnimation = ({ doAnimation, duration, initialValue, finalValue }) => {
const [animation, setAnimation] = useState(new Animated.Value(initialValue))
useEffect(() => {
Animated.spring(animation, {
toValue: doAnimation ? initialValue : finalValue,
duration,
bounciness: 8,
useNativeDriver: false
}).start();
}, [doAnimation]);
return animation
}
As it is said in the documentation, you could animate only Animated components, and for example if you want to have an animated View, the tag will be <Animated.View> {...} </Animated.View, but for the <TextInput> we have to create the animated component:
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
and combining the first 2 steps
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
const [collapsed, setCollapsed] = useState(true)
const animation = useAnimation({ doAnimation: collapsed, duration: 300, initialValue: 20, finalValue: 200 });
const onFocusText = () => setWidth(false)
return (
<AnimatedTextInput
onFocus={onFocusText}
placeholder={"Search something"}
style={{width: animation, height: 50, borderColor: 'gray', borderWidth: 1, borderRadius: 4, padding: 10}}
/>
)
Also, if you're having a class component, you could have a method which will start the animation (but don't forget about the step 2 which is essential)
private size: Animated.Value = new Animated.Value(COLLAPSED_VALUE)
get resizeInputWidth(): Animated.CompositeAnimation {
return Animated.timing(this.size, {
toValue: EXPANDED_VALUE,
duration: 500,
})
}
startAnimation = () => this.resizeInputWidth.start()
<AnimatedTextInput
onFocus={this.startAnimation}
style={{ width: this.size }}
/>

React native no re-render after updating state array

I have the following code (full example):
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Button, StyleSheet, Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
const App = () => {
const [blocks, setBlocks] = useState([]);
const CreateBlockHandler = () => {
let array = blocks;
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks();
};
const MoveBlockHandler = (index, event) => {
Animated.spring(blocks[index].x, { toValue: event.nativeEvent.x }).start();
Animated.spring(blocks[index].y, { toValue: event.nativeEvent.y }).start();
};
const RenderBlocks = () => {
return blocks.map((item, index) => {
return (
<PanGestureHandler key={index} onGestureEvent={event => MoveBlockHandler(index,event)}>
<Animated.View style={[styles.block, {
transform: [
{ translateX: item.x },
{ translateY: item.y }
]
}]} />
</PanGestureHandler>
)
});
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.pancontainer}>
<RenderBlocks />
</View>
<Button title="Add block" onPress={CreateBlockHandler} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
pancontainer: {
width: '95%',
height:'75%',
borderWidth: 1,
borderColor: 'black'
},
block: {
width: 50,
height: 50,
backgroundColor: 'black'
}
});
export default App;
What does this code do? It's a big square, and a button below it. When I click on the button, a new black square (50x50) is made in the big square. I do this by creating a new array element (the array = blocks). This is done in the function CreateBlockHandler. This does not work correctly!
The function MoveBlockHandler makes the little squares movable. This works!
What does not work? When I create a new black square, the black square is not rendered on the screen. Only when I refresh, the square is rendered. The square is created through CreateBlockHandler, because when I do a console.log(blocks) in that function, I can see that a new array element is added.
How can I force this code to do a full re-render with all the array elements? I tried to wrap the render of the square in a separate function (RenderBlocks) and I'm calling this function every time a new square is made (last line in CreateBlockHandler). The function is called (I can check this with a console.log()) but no squares are rendered.
When you assign blocks to array the reference gete copied which mutates the state, so it doesn't re-render on setState.
const CreateBlockHandler = () => {
let array = [...blocks];
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks
There are multiple issues with your code.
As kooskoos pointed out, your state remains referentially equal (it's the same array, only the elements change). This will not trigger re-render.
Also, you are manipulating state of the App component. RenderBlocks component's props and state remain unchanged which implies that they don't need to be re-rendered. Since the component is an anonymous function and is recreated during every render of App, it probably gets re-rendered anyways.
In addition, you are directly calling RenderBlocks, which looks like a component. That is unnecessary and will do nothing here, but if it had any hooks, it would cause problems.
You should probably also conform to the convention that components are PascalCase capitalised and callbacks snakeCase capitalised.

React Native: How to stop map markers from re-rendering on every state update

I have a component that has a map with multiple custom markers for various locations and a carousel with cards for those same locations. When a user presses a marker, it should show the callout and show the location's name next to the marker (but outside of the callout).
However, because I update the state in onRegionChangeComplete, if the user moves the map and then quickly presses the marker (before the state finishes updating from calling setState in onRegionChangeComplete), then the markers will re-render before firing the onPress event, and the event is never fired.
One solution might be to use shouldComponentUpdate, however, the docs state that it should only be used for performance optimization and not to prevent re-renders (https://reactjs.org/docs/react-component.html#shouldcomponentupdate), but more importantly, my componentDidUpdate function has some conditional logic dependent on the region set in shouldComponentUpdate, as well as other conditional actions, so I don't want to prevent re-rendering of the entire component, just unnecessary re-rendering of the markers.
I'm also using the performance optimization mentioned in https://github.com/react-native-community/react-native-maps/issues/2082 of wrapping the makers in a component that implements shouldComponentUpdate and getDerivedStateFromProps, however, I'm not entirely sure this is doing anything because it seems like the parent component is just recreating all of my optimized markers rather than using their optimizations to handle re-rendering. Also, even if I don't use a wrapped marker but a conventional custom marker, I still have the same issues.
I've also opened an issue for this on react-native-maps but haven't gotten a response yet: https://github.com/react-native-community/react-native-maps/issues/2860
My 'onRegionComplete' function that updates state when map is moved. I removed a few other conditional state updates for brevity:
onRegionChangeComplete = (region) => {
const nextState = { };
nextState.region = region;
if (this.state.showNoResultsCard) {
nextState.showNoResultsCard = false;
}
.
.
.
this.setState({ ...nextState });
this.props.setSearchRect({
latitude1: region.latitude + (region.latitudeDelta / 2),
longitude1: region.longitude + (region.longitudeDelta / 2),
latitude2: region.latitude - (region.latitudeDelta / 2),
longitude2: region.longitude - (region.longitudeDelta / 2)
});
}
MapView using the more conventional marker (not the optomized version):
<MapView // show if loaded or show a message asking for location
provider={PROVIDER_GOOGLE}
style={{ flex: 1, minHeight: 200, minWidth: 200 }}
initialRegion={constants.initialRegion}
ref={this.mapRef}
onRegionChange={this.onRegionChange}
onRegionChangeComplete={this.onRegionChangeComplete}
showsUserLocationButton={false}
showsPointsOfInterest={false}
showsCompass={false}
moveOnMarkerPress={false}
onMapReady={this.onMapReady}
customMapStyle={mapStyle}
zoomTapEnabled={false}
>
{this.state.isMapReady && this.props.places.map((place, index) => {
const calloutText = this.getDealText(place, 'callout');
return (
<Marker
tracksViewChanges
key={Shortid.generate()}
ref={(ref) => { this.markers[index] = ref; }}
coordinate={{
latitude: place.getLatitude(),
longitude: place.getLongitude()
}}
onPress={() => { this.onMarkerSelect(index); }}
anchor={{ x: 0.05, y: 0.9 }}
centerOffset={{ x: 400, y: -60 }}
calloutOffset={{ x: 8, y: 0 }}
calloutAnchor={{ x: 0.075, y: 0 }}
image={require('../../Assets/icons8-marker-80.png')}
style={index === this.state.scrollIndex ? { zIndex: 2 } : null}
>
{this.state.scrollIndex === index &&
<Text style={styles.markerTitle}>{place.getName()}</Text>}
<Callout onPress={() => this.onCalloutTap(place)} tooltip={false}>
<View style={{
borderColor: red,
width: 240,
borderWidth: 0,
borderRadius: 20,
paddingHorizontal: 8,
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center'
}}
>
<Text style={styles.Title}>Now:</Text>
<View style={{
width: 240,
flexDirection: 'column',
justifyContent: 'space-evenly',
alignItems: 'flex-start',
paddingHorizontal: 8,
flex: 1
}}
>
{calloutText.Text}
</View>
</View>
</Callout>
</Marker>
);
})
}
</MapView>
My function for the marker's on press event:
onMarkerSelect(index) {
this.setState({ scrollIndex: index });
this.carousel._component.scrollToIndex({
index,
animated: true,
viewOffset: 0,
viewPosition: 0.5
});
this.markers[index].redrawCallout();
}
Updating state and then quickly pressing a marker will cause the onPress event not to fire. Also, the markers are re-rendered/recreated every time a parent component is updated. (I say recreated because it seems like the markers are re-rendering without even firing shouldComponentUpdate or componentDidUpdate).
Is there any way to update state in onRegionChangeComplete without forcing the markers to re-render?
For anyone else who happens to have this problem, the issue was that I was randomly generating the keys for the markers, causing the parent component to create new markers each time it was re-rendered.
Specifically, the line key={Shortid.generate()} was the problem.

React-Native: How to either a) clear a text input or b) disable the text that is typed from being displayed

I am trying to create a way for the user to type in a five digit numeric code. The problem is that I want whatever number they type in to be transformed into an astrice. My first attempt was to set the value to display text but that did absolutely nothing. So then, I decided to make a text component who's text is the transformed text and then to just make the text input text transparent. This is obviously far from ideal but it gets the job. Ideally I would like some way of either
a) having and input box that doesn't actually display input letting my text component handle the display
b) have a way to clear the text in the input (less ideal but at least this would prevent having long transparent strings in the background)
c) someone offer me new approach altogether and tell me im an idiot for even trying to do it this way.
generateDisplayText is responsible for transforming current input based on astrices. It just uses the length of the current input to determine how many astrices and underscores to show.
onPress updates the state of current input so far
onChange calls check match which checks if there is a match
import React from 'react';
import {Button} from 'react-native-elements';
import ScreenNames from './ScreenNames';
import {Common, Colors, Sizes} from '../themes/Styles';
import {View, TextInput, Text} from 'react-native';
import Base from './Base';
const CODE_LENGTH = 5;
const MATCH = false;
const INITIAL_STATE = {
text: [],
displayText: generateDisplayText(0),
value: '',
};
function generateDisplayText(textLength) {
let ret = '* '.repeat(textLength) + '_ '.repeat(CODE_LENGTH - textLength);
return ret;
}
export default class Password extends Base {
constructor(props) {
super(props);
this.state = INITIAL_STATE;
}
componentDidMount() {
this.refs.textInput.focus();
}
pressHandler = event => {
let key = event.nativeEvent.key;
key === 'Backspace'
? this.setState(oldState => ({text: oldState.text.slice(0, -1)}))
: this.setState(oldState => ({text: oldState.text.concat(key)}));
this.setState(oldState => ({
displayText: generateDisplayText(oldState.text.length),
}));
};
checkForMatch = () => {
if (this.state.text.length >= CODE_LENGTH) {
if (MATCH) {
//go to next screen
//
}
this.refs.textInput.clearImmediate();
this.setState(() => INITIAL_STATE);
}
};
render() {
return (
<View
style={{
flex: 1,
width: '100%',
height: '100%',
marginTop: 20,
}}>
<View
justification="center"
alignment="center"
style={{
justifyContent: 'center',
alignItems: 'center',
flex: 1,
marginTop: 10,
}}>
<TextInput
style={{fontSize: 20, color: 'green'}}
selectionColor="transparent"
ref="textInput"
placeholderTextColor="black"
keyboardType="number-pad"
sectureTextEntry={true}
onKeyPress={this.pressHandler}
onChange={this.checkForMatch}
autoCorrect={false}
onChangeText={text => this.setState({value: text})}
value={this.state.value}
/>
<Text style={{fontSize: 60}}>
{' '}
{this.state.displayText}{' '}
</Text>
</View>
</View>
);
}
}

Resources