I'm pretty new to react native and having some trouble with setting a new state. Everything works fine until the third textinput shows up, when i start writing inside it the counter is stuck at 1, when it should be at 2, and furthermore update the textInputList too two TextInput elements. I want to understand why the counter does not change, and how to solve this issue:)
import React from 'react';
import { useState } from 'react';
import { Button, View, StyleSheet, TextInput } from 'react-native';
import colour from '../constants/Colors';
import StartButton from '../components/Buttons/BackToBackButton';
function ShowNames(props) {
return (
<View style={styles.lineContainer}>
<TextInput
style = {{ width: '70%', height: 40, borderColor: 'white', borderWidth: 2 }}
placeholder='Legg in navn her'
placeholderTextColor='white'
selectionColor='black'
onChangeText={props.handleTextChange}
/>
</View>
)
}
export default function BackToBack(props) {
const [nameList, setList] = useState([]);
const [counter, setCounter] = useState(0);
const [textInputList, setInputList] = useState(null)
const handleTextChange = (text, id) => {
tempList = nameList
tempList[id] = text
setList(tempList)
if (id == counter) {
setCounter(counter + 1)
AddNameInputs()
}
}
function AddNameInputs()
var tempList = [];
for (let i = 0; i < counter; i++) {
console.log('i: ' + i)
tempList.push(
<View style={styles.lineContainer} key={i+2}>
<TextInput
style={{ width: '70%', height: 40, borderColor: 'white', borderWidth: 2 }}
placeholder='Legg in navn her'
placeholderTextColor='white'
selectionColor='black'
onChangeText={(text) => handleTextChange(text, i+2)}
/>
</View>
)
}
setInputList(tempList)
}
return (
<View style={styles.container}>
<ShowNames handleTextChange={(text) => handleTextChange(text, 0)} />
<ShowNames handleTextChange={(text) => handleTextChange(text, 1)} />
{textInputList}
<StartButton title={"Start!"} height={100} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colour.lightTurquoise,
alignItems: 'center',
justifyContent: 'flex-start',
paddingTop: 20,
paddingBottom: 20
// borderWidth: 4,
// borderColor: 'yellow'
},
lineContainer: {
flexDirection: 'row',
paddingBottom: 20
}
});
I think the problem is this line tempList = nameList. It would assume you are referencing the same object when you're setting state and not trigger the relevant updates. So the quickest fix would be to just clone it with the spread operator like tempList = [...nameList] ?
Related
I'm trying to save my data locally with AsyncStorage but there seems to be an issue when I use getData
const storeData = async (value: string) => {
//storing data to local storage of the device
try {
await AsyncStorage.setItem("#storage_Key", value);
} catch (e) {}
};
const getData = async () => {
try {
const value = await AsyncStorage.getItem("#storage_Key");
if (value !== null) {
// value previously stored
}
} catch (e) {}
};
...
<View>
<TextInput
editable
value={value}
/>
{storeData(value)}
{getData()}
</View>
I thought I would have my value back but I got a blank page. Any idea of how to use AsyncStorage ? I used https://react-native-async-storage.github.io/async-storage/docs/usage/ .
Instead of calling storeData function in the return, you should bind your async storage function to the textinput component. Below is an example code on how to use it.
// AsyncStorage in React Native to Store Data in Session
// https://aboutreact.com/react-native-asyncstorage/
// import React in our code
import React, { useState } from 'react';
// import all the components we are going to use
import {
SafeAreaView,
StyleSheet,
View,
TextInput,
Text,
TouchableOpacity,
} from 'react-native';
// import AsyncStorage
import AsyncStorage from '#react-native-community/async-storage';
const App = () => {
// To get the value from the TextInput
const [textInputValue, setTextInputValue] = useState('');
// To set the value on Text
const [getValue, setGetValue] = useState('');
const saveValueFunction = () => {
//function to save the value in AsyncStorage
if (textInputValue) {
//To check the input not empty
AsyncStorage.setItem('any_key_here', textInputValue);
//Setting a data to a AsyncStorage with respect to a key
setTextInputValue('');
//Resetting the TextInput
alert('Data Saved');
//alert to confirm
} else {
alert('Please fill data');
//alert for the empty InputText
}
};
const getValueFunction = () => {
//function to get the value from AsyncStorage
AsyncStorage.getItem('any_key_here').then(
(value) =>
//AsyncStorage returns a promise so adding a callback to get the value
setGetValue(value)
//Setting the value in Text
);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<Text style={styles.titleText}>
AsyncStorage in React Native to Store Data in Session
</Text>
<TextInput
placeholder="Enter Some Text here"
value={textInputValue}
onChangeText={(data) => setTextInputValue(data)}
underlineColorAndroid="transparent"
style={styles.textInputStyle}
/>
<TouchableOpacity
onPress={saveValueFunction}
style={styles.buttonStyle}>
<Text style={styles.buttonTextStyle}> SAVE VALUE </Text>
</TouchableOpacity>
<TouchableOpacity onPress={getValueFunction} style={styles.buttonStyle}>
<Text style={styles.buttonTextStyle}> GET VALUE </Text>
</TouchableOpacity>
<Text style={styles.textStyle}> {getValue} </Text>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
backgroundColor: 'white',
},
titleText: {
fontSize: 22,
fontWeight: 'bold',
textAlign: 'center',
paddingVertical: 20,
},
textStyle: {
padding: 10,
textAlign: 'center',
},
buttonStyle: {
fontSize: 16,
color: 'white',
backgroundColor: 'green',
padding: 5,
marginTop: 32,
minWidth: 250,
},
buttonTextStyle: {
padding: 5,
color: 'white',
textAlign: 'center',
},
textInputStyle: {
textAlign: 'center',
height: 40,
width: '100%',
borderWidth: 1,
borderColor: 'green',
},
});
export default App;
I create a simple math quiz app with React Native the problem is when I run this project on the web it's working fine as I expect however when I run this project with Expo on an Andriod device I cannot get any score increment but I see in a web browser my score is increased.
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
export default function App() {
const [inputValue, setInputValue] = useState('')
const [target, setTarget] = useState([])
const [score, setScore] = useState(0)
const newQuestion = () => {
const minimum = 1;
const maximum = 10;
const int1 = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
const int2 = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
setTarget([int1, int2])
}
useEffect(() => {
newQuestion()
}, [score])
const handleAnsewer = () => {
const total = target[0] + target[1];
// Check that total against the number
if (total === Number(inputValue)) {
setScore( score + 5)
} else {
if(score > 0) {
setScore( score - 5)
}
}
// Call the function again
newQuestion();
}
return (
<View style={styles.container}>
<Text style={{ fontSize: 32, fontWeight: 'bold' }}>Random Math Quiz</Text>
<Text style={{ fontSize: 16, }}> {target.join(' + ')} </Text>
<TextInput
style={{ height: 40 }}
placeholder="Answer The Question"
value={inputValue} onChange={(e) => setInputValue(e.target.value)}
/>
<Button title='Answer' onPress={handleAnsewer} />
<Text style={{ fontSize: 16, }}> Your Score: {score}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'flex-start',
paddingTop: 50
},
});
Unlike the web, you can't access the changed value using e.target.value under onChange props. It returns undefined if you do that which explained why your score didn't update.
Instead, you can used onChangeText props to access the changed value.
// do this instead
onChangeText={(textValue)=> setInputValue(textValue)}
// don't do this
onChange={(e) => setInputValue(e.target.value)}
I am creating simple countdown component with React-Native, for date formatting I am using moment.js, I am getting data from API call in seconds format, then providing to child component, I want to transform this kind of data 121 into 2:01, How to use moment to achieve this result and do not kill performance?
import { MaterialCommunityIcons } from "#expo/vector-icons";
import moment from "moment";
import React, { useEffect, useState} from "react";
import { View, Text} from "react-native";
const Timer: React.FC<{ expirationDate: number,focus:boolean, loading:boolean}> = ({ expirationDate, focus, loading}) => {
const [inactive, setIncative] = useState(false);
const [timerCount, setTimer] = useState<number>(0) //can be any number provided from props
useEffect(() => {
setTimer((prev:number)=> (expirationDate))
let interval = setInterval(() => {
setTimer(lastTimerCount => {
lastTimerCount <= 1 && clearInterval(interval)
return lastTimerCount - 1
})
}, 1000)
return () => clearInterval(interval)
}, [loading]);
return (
<View
style={{
backgroundColor: expirationDate < 300 || inactive ? "#edbcbc" : "#cee5f4",
width: 70,
borderRadius: 7,
paddingVertical: 6,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
<View style={{ position: "relative", left: 4 }}>
<MaterialCommunityIcons name="timer-outline" size={13} color={expirationDate < 300 || inactive ? "#f54c4c" : "#004978"} />
</View>
//line sholud be changed
<Text>{moment.duration(timerCount).format("h:mm")}</Text>
</View>
);
};
export default Timer;
I found solotion: using moment utc method and transform state value into miliseconds
import { MaterialCommunityIcons } from "#expo/vector-icons";
import moment from "moment";
import React, { useEffect, useState} from "react";
import { View, Text} from "react-native";
const Timer: React.FC<{ expirationDate: number,focus:boolean, loading:boolean}> = ({ expirationDate, focus, loading}) => {
const [inactive, setIncative] = useState(false);
const [timerCount, setTimer] = useState<number>(0)
useEffect(() => {
setTimer((prev:number)=> (expirationDate))
let interval = setInterval(() => {
setTimer(lastTimerCount => {
lastTimerCount <= 1 && clearInterval(interval)
return lastTimerCount - 1
})
}, 1000)
return () => clearInterval(interval)
}, [loading]);
return (
<View
style={{
backgroundColor: expirationDate < 300 || inactive ? "#edbcbc" : "#cee5f4",
width: 70,
borderRadius: 7,
paddingVertical: 6,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
<View style={{ position: "relative", left: 4 }}>
<MaterialCommunityIcons name="timer-outline" size={13} color={expirationDate < 300 || inactive ? "#f54c4c" : "#004978"} />
</View>
<Text>{moment.utc(timerCount * 1000).format("mm:ss")}</Text>
</View>
);
};
export default Timer;
I am trying to create dynamic TextInputs, and then saving the values of TextInput into another state by indexing through loops, but I am not getting the desired output.
I think the problem is in the loop because the console.log of the onChangeText for the value of i is always the number of TextInputs.
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View,TextInput, FlatList, Button, } from 'react-native';
export default function App() {
const [value,setValue]=useState(1)
const[textInput,setTextInput]=useState([])
const[inputData,setInputData]=useState([{index:0,text:''},{index:1,text:''},{index:2,text:''}])
useEffect(()=>{
addTextInput()
},[value])
const addTextInput=()=>{
var Input=[];
for(var i=0;i<value;i++){
Input.push(
<TextInput style={styles.textInput}
onChangeText={(text)=>{
var newarray = [...inputData]
var index = newarray.findIndex((dd) =>dd.index==i)
console.log('index',index,'i',i); //------>idk why but the value of i is always the no of text
// input pls hwlp me with
// these loop.i think i have done something wrong in here
newarray[index] = {index:i,text:text}
setInputData(newarray)} }
/>)
}
setTextInput(Input);
}
return (
<View style={styles.container}>
<View style={{height:50,color:'green',backgroundColor:'green',marginBottom:20}}/>
<View style={{flexDirection:'row',justifyContent:'space-around'}}>
<Text style={{fontSize:20,alignSelf:'center'}}>No Of Tenents:</Text>
</View>
<View style={{flexDirection:'row',justifyContent:'space-around'}}>
<Button title='1' onPress={()=>setValue(1)}/>
<Button title='2' onPress={()=>setValue(2)}/>
<Button title='3' onPress={()=>setValue(3)} />
</View>
<FlatList data={textInput}
renderItem={({item})=>{
return item
}}/>
</View>
);
}
const pickerSelectStyles = StyleSheet.create({
inputIOS: {
fontSize: 16,
paddingVertical: 12,
paddingHorizontal: 10,
borderWidth: 1,
borderColor: 'gray',
borderRadius: 4,
color: 'black',
paddingRight: 30,
},
inputAndroid: {
fontSize: 16,
paddingHorizontal: 10,
paddingVertical: 8,
borderWidth: 1,
borderColor: 'green',
borderRadius: 8,
color: 'black',
paddingRight: 50,
},
});
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
textInput: {
height: 40,
borderColor: 'black',
borderWidth: 1,
margin: 20
},
});
Use let in loop to declared i, because let can be only available inside the scope it's declared.[read this]
If you want remove loop,
const addTextInput = () => {
var Input = [...textInput];
if (Input.length < value) {
const InputIndex = Input.length;
Input.push(
<TextInput
style={styles.textInput}
onChangeText={text => {
var newarray = [...inputData];
const index = newarray.findIndex(dd => dd.index === InputIndex);
newarray[index] = {index: index, text: text};
setInputData(newarray);
console.log(newarray);
}}
/>,
);
} else {
Input.pop();
}
setTextInput(Input);
};
Using let instead of var solved my isuue.
I am using functional component with Hooks and I am getting user current location and assign it to MapView initialRegion but component doesn't rerender.
I tried to use reference ref with MapView component to call animateToRegion() but it's not working.
Is there is any thing that I miss?
here is my code sample:
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, Image, Platform, ScrollView, ActivityIndicator } from 'react-native';
import { DateMenu, Dropdown, DropdownTextInput, InputField} from '../../../GlobalReusableComponents/TextFields';
import { Space } from '../../../GlobalReusableComponents/Separators';
import { Button } from 'react-native-elements';
import axios from 'axios';
import MapView, { Marker } from 'react-native-maps';
import useReverseGeocoding from '../../../context/useReverseGeocoding';
import useCurrentLocation from '../../../context/useCurrentLocation';
import { getLocationAsync } from '../../../util/getCurrentLocation';
import { textStyles, buttonStyles } from '../../../globalStyles/styles';
import EStyleSheet from 'react-native-extended-stylesheet';
const latitudeDelta = 0.025
const longitudeDelta = 0.025
const JobLocationScreen = (props) => {
const [region, setRegion] = useState({
latitude: 38.907192,
longitude: -30.036871,
latitudeDelta,
longitudeDelta
});
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const [reverseGeocodingApi, reverseGeocodingdata, isReverseGeacodingDone, reverseGeocodingErrorMessage] = useReverseGeocoding();
let mapRef = useRef(null);
useEffect(() => {
getYourCurrentLocation();
},
[])
useEffect(() => {
animateToRegion();
},
[region])
const onMapReady = () => {
if(!isMapReady) {
setIsMapReady(true);
}
};
const getYourCurrentLocation = async () => {
const { location } = await getLocationAsync();
console.log(location);
setRegion(region);
}
const onRegionChangeComplete = (selectedRegion) => {
setRegion(selectedRegion);
reverseGeocodingApi(selectedRegion);
}
const animateToRegion = () => {
mapRef.animateToRegion(region, 1000);
}
const onNextButtonPress = () => {
props.navigation.state.params.setSelectedValue(jobTags);
props.navigation.pop();
}
const _renderMapWithFixedMarker = () => {
return (
<View style={{flex: 1}}>
<MapView
ref={ref => {
mapRef = ref
}}
onMapReady={onMapReady}
style={styles.map}
initialRegion={region}
onRegionChangeComplete={(selectedRegion) => onRegionChangeComplete(selectedRegion)}
/>
<View style={styles.pinBadge}>
<Text
style={{color: EStyleSheet.value('$primaryDarkGray')}}
>
Move to choose Location
</Text>
</View>
<View style={styles.markerFixed}>
<Image style={styles.marker} source={require('../../../assets/pin.png')} />
</View>
</View>
);
}
return (
<View style={styles.container}>
<View
pointerEvents='none'
style={styles.inputFieldContainer}
>
<InputField
maxLength={35}
placeholder='Selected Address'
value={isReverseGeacodingDone? reverseGeocodingdata.results[0].formatted_address : 'Loading ...'}
/>
</View>
{_renderMapWithFixedMarker()}
<View style={styles.bodyContainer}>
<View style={styles.buttonContainer}>
<Button
title="Confirm Your Location"
buttonStyle={buttonStyles.button}
onPress={() => onNextButtonPress()}
/>
</View>
</View>
</View>
);
}
JobLocationScreen.navigationOptions = ({navigation}) => {
return {
title: 'Select your Location'
};
};
export default JobLocationScreen;
const styles = EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor'
},
inputFieldContainer: {
backgroundColor: '#f8f9f9',
paddingVertical: 20,
paddingHorizontal: 20
},
map: {
flex: 1
},
pinBadge: {
position: 'absolute',
paddingVertical: 10,
paddingHorizontal: 15,
top: '38%',
alignSelf: 'center',
borderRadius: 25,
backgroundColor: '#ffffff',
shadowColor: '#acaeb4',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.5,
shadowRadius: 5,
elevation: 5
},
markerFixed: {
left: '50%',
marginLeft: -10,
marginTop: -6,
position: 'absolute',
top: '50%'
},
marker: {
width: 20,
height: 41
},
bodyContainer: {
marginHorizontal: 20
},
buttonContainer: {
position: 'absolute',
bottom: 20,
width: '100%'
}
})
You need to access the ref using refname.current to get access to the value.
<MapView ref={mapRef}
and then when want to access it, use .current:
const animateToRegion = () => {
mapRef.current.animateToRegion(region, 1000);
}
See the docs
Conceptual Demo
for react-native-maps-super-cluster
<MapView ref={mapRef}
const animateToRegion = () => {
mapRef.current.getMapRef().animateToRegion(region, 1000);
}