React Native useState not working on a AsyncStorage fuction - react-native

My code is the following:
function ArScreen({ navigation }) {
const [open_tabLugar, setOpen_tabLugar] = useState(false);
const [titulo, setTitulo] = useState('');
const updateAsyncStorage = async () => {
AsyncStorage.getItem('data')
.then((res) => {
//console.log({ res : JSON.parse(res) });
console.log(JSON.parse(res).open_tabLugar);
console.log(JSON.parse(res).titulo);
setOpen_tabLugar(JSON.parse(res).open_tabLugar);
setTitulo(JSON.parse(res).titulo);
console.log(titulo);
console.log(open_tabLugar);
});
}
useEffect(() => {
updateAsyncStorage()
const interval=setInterval(()=>{
updateAsyncStorage()
},10000)
return()=>clearInterval(interval)
}, []);
Output:
console.log(JSON.parse(res).open_tabLugar); ====== true
console.log(JSON.parse(res).titulo); ===== hello
after setOpen_tabLugar and setTitulo
console.log(titulo); ===== ""
console.log(open_tabLugar); ===== false
Why does that happen? I am changing the state (or trying to change it), but it doesn't work. What am I doing wrong?
If you notice, I have a bucket that updates every 10 seconds in useEffect. But, that's not the problem, that works fine (in the update I retract myself, that is the problem, only I don't know how to solve it). The problem is in the change of states.
What am I doing wrong?
UPDATE 1:
If I put this:
setTitulo("HEEEEEEEEEEEEY");
console.log(titulo);
Doesn't work, keeps coming up blank.
UPDATE 2:
I think the problem is inside useEffect(() => {
Because I just created a button to run updateAsyncStorage manually and it did work.
<TouchableOpacity
style={{width: 50, borderRadius: 50, height: 50, backgroundColor: 'white', alignItems: 'center', justifyContent: 'center'}}
onPress={() => updateAsyncStorage()} >
<Text style={{color: 'black', fontSize: 20, fontWeight: 'bold'}}>Load</Text>
</TouchableOpacity>
The button does work fine. Yes the state changes. How do I make it work automatically with useEffect or another alternative?
UPDATE 3:
My new code:
const [open_tabLugar, setOpen_tabLugar] = useState(false);
const [tabLugar, setTabLugar] = useState(false);
useEffect(() => {
AsyncStorage.getItem('data')
.then((res) => {
//console.log({ res : JSON.parse(res) });
console.log(JSON.parse(res).open_tabLugar);
console.log(JSON.parse(res).tipo);
console.log(JSON.parse(res).titulo);
console.log(JSON.parse(res).descripcion);
console.log(JSON.parse(res).distance);
console.log(JSON.parse(res).imagen);
console.log(JSON.parse(res).imagen_1);
// string to boolean in AsyncStorage
setOpen_tabLugar(JSON.parse(res).open_tabLugar);
setTipo(JSON.parse(res).tipo);
setTitulo(JSON.parse(res).titulo);
setDescripcion(JSON.parse(res).descripcion);
setDistance(JSON.parse(res).distance);
setImagen(JSON.parse(res).imagen);
setImagen_1(JSON.parse(res).imagen_1);
if(open_tabLugar){
console.log('-------------- OPEN MODAL --------------');
setTabLugar(true);
setOpen_tabLugar(false);
console.log(open_tabLugar);
}
});
},[open_tabLugar, tipo, titulo, descripcion, distance, imagen, imagen_1]);
return(
...
<Modal animationType="slide" transparent={true} visible={tabLugar}>
<View style={styles_modal.modalViewTabL}>
<TouchableOpacity onPress={() => setTabLugar(!tabLugar)} style={[styles_modal.button, styles_modal.buttonClose]} >
<Text style={styles_modal.buttonClosetextStyle}>X</Text>
</TouchableOpacity>
<Text style={styles_modal.place_description} numberOfLines={7}>
La Casa de la Cultura es una de las más importantes obras de arte de la ciudad de Madrid.
</Text>
</View>
</Modal>
That works halfway, the problem is that it runs infinitely, it has no end. That started when I put: [open_tabLugar, type, title, description, distance, image, image_1]
After I open my modal with setTabLugar(true); I write setOpen_tabLugar(false);
That should prevent it from opening it again, but then again it runs as true, infinitely.
UPDATE 4:
My error is in the following snippet. Since it runs endlessly. If I remove it the loop stops. How do I fix that problem? Remember that I want the modal to open, but not infinitely in a loop.
if(open_tabLugar){
console.log('-------------- OPEN OPEN OPEN TAB LUGAR --------------');
setTabLugar(true);
setOpen_tabLugar(false);
console.log(open_tabLugar);
}

Unfortunately, hooks cannot be used this way. After the set operation is done, the rendering process takes place and the new value you have assigned will then be available. If you have processes that will work according to these values, you should create useEffect for this and give the variable you will use to the dependency part.
For example, you can print the current values in the following way.
useEffect(() => {
console.log(titulo);
console.log(open_tabLugar);
},
[titulo, open_tabLugar],
);
You can show it through a real example for better help.

Related

React Native - Appended component in hook not responding as expected

I am working on a project that uses Google autocomplete to set locations. The project allows users to set pickup and destination location, and then they can also enter stop-by places up to additional 3, making it a total of 5.
Here's my sample code:
const placesRef = useRef([]);
const [stopspots, setStopSpots] = useState([]);
const [state, setState] = useState({
defaultPlacesInput: 'flex',
//and others
});
useEffect(() => {
placesRef.current = placesRef.current.slice(0, 5);
}, []);
const placesComponent = (i, placeholder) => {
return (<PlacesFrame key={i}>
...
<GooglePlacesAutocomplete
placeholder={placeholder}
minLength={2}
ref={el => placesRef.current[i] = el}
onPress={(data, details = null) => {
placesRef.current[i]?.setAddressText(data?.structured_formatting?.main_text);
setState({...state, defaultPlacesInput: 'flex'})
}}
enablePoweredByContainer={false}
fetchDetails
styles={{
textInput: [styles.input1,{paddingLeft:30}],
container: [styles.autocompleteContainer,{display:placesRef.current[i]?.isFocused() ? 'flex' : state.defaultPlacesInput}],
listView: styles.listView,
listView: styles.listView,
row: styles.row,
predefinedPlacesDescription: {
color: '#1faadb',
},
}}
query={{
key: GOOGLE_PLACES_API_KEY,
language: profile.language,
components: 'country:' + profile.iso,
}}
textInputProps={{
//value: '',
onChangeText: alterOtherFields
}}
renderRow={(data) => <PlaceRow data={data} />}
/>
...
</PlacesFrame>)
}
const stopByLocation = () => {
var counter = stopspots.length, obj = placesComponent(counter + 2, 'Drop off location');
setStopSpots([...stopspots, {
id: counter,
place: obj
}
])
}
And here is how the autocomplete component is rendered
return(
...
<View>
{placesComponent(0, 'Pick up location')}
{placesComponent(1, 'Drop off location')}
</View>
...
)
The output look like this
Everything works perfect when I call the placesComponent() function directly. But like I mentioned earlier, I want the users to be able to add up to 3 additional stop by locations, and because it is optional, additional fields is added by appending to hook, and then rendered. the code looks like this.
return(
...
<View>
{placesComponent(0, 'Pick up location')}
{placesComponent(1, 'Drop off location')}
//This will append more placed fields
{stopspots != '' ?
stopspots.map((item : {}) => ((item.place)))
: null}
<ClickableButton>
<TouchableOpacity activeOpacity={0.6} onPress={() => stopByLocation()}><AddPlaces><AntDesign name="plus" size={10} color="#444" /> Add</AddPlaces></TouchableOpacity>
</ClickableButton>
</View>
...
)
The outcome looks like this
I observed that each component binded to the hooks takes the early properties, and does not effect additional changes. While the first two fields rendered by calling the function directly does.
When I make changes to state.defaultPlacesInput (observe this in styles property of GooglePlacesAutocomplete), the changes only effect on the two components called directly.
Is there a module, or a systematic way to append the renderer function call, without using useState hooks to append the 3 additional fields?
Is it possible to expose stored properties in useState hooks to respond as the other two which observe the state changes? If yes, how?
Any contribution, suggestion will be accepted

React Native conditionally setting state

I have an array of "favorite" listings per user. I want to set a state variable depending on if it is rendering a favorite item or a regular (non-favorite) item. This is a little heart icon that is either filled or unfilled. It is incorrectly setting the state on first render. Correctly sets it after refresh.. I'm doing something wrong and have tried many things to no avail so if someone could just look at this code and tell me if you see any glaring flaws. If not then I will look elsewhere for the problem.
Behavior when app loads: I am doing console.log just after the state is set to show whether it's a favorite or not, and the contents of the favorite state variable (an image url, but in the console it shows it as either 21 for non-fav, or 22 for a fav). I can see that it is correctly pulling the array of favorites, and correctly identifying those that are and are not favorited (1 means its a favorite). It is however not setting the state variable correctly. Furthermore, it IS setting it correctly for only some of the listings. Currently I have all listings marked as a favorite, and it only messes up the first 10. After that they all set right. MORE bizarre, is upon refreshing the screen, it correctly sets all of them.
MainHeader.js (pulling data from db, setting initial array of favorite listings, and passing it to the messagesScreen component)
const [favsArray, setFavsArray] = useState("");
useEffect(() => {
lookupUser()
.then((snapshot) => {
if (snapshot.hasChildren()) {
snapshot.forEach(function(childSnapshot) {
let favs = childSnapshot.child("favorites").val();
setFavsArray(favs);
})
}
})
.catch((error) => {console.error('Error:', error)});
}, []);
return (
<NavigationContainer>
<View style={styles.headerContainer}>
<Image
style={styles.image}
source={require("../assets/newheader4.png")}
/>
</View>
<Tab.Navigator
tabBarOptions={{
activeTintColor: "blue",
inactiveTintColor: "black",
style: {},
tabStyle: {
width: "auto",
backgroundColor: "#e0d5f3",
borderTopWidth: 3,
borderBottomWidth: 3,
borderRightColor: "gray",
},
labelStyle: {
fontSize: 14,
fontWeight: "bold",
},
scrollEnabled: true,
}}
>
<Tab.Screen name="All Deals" children={()=><MessagesScreen favsArray={favsArray} setFavsArray={setFavsArray}/>} />
</Tab.Navigator>
</NavigationContainer>
MessagesScreen, receives favsArray and renders a FlatList with component Card which it feeds favsArray to.
<FlatList
data={messagesShow}
keyExtractor={(messagesShow) => messagesShow.id.toString()}
renderItem={({ item }) => (
<Card
price={item.currentPrice}
title={item.title}
image={item.image}
posted={item.postedDate}
discAmount={item.discountAmount}
discType={item.discType}
expiration={item.expiration}
promoCode={item.promoCode}
affLink={item.amzLink}
indexStore={item.indexStore}
store={item.store}
favsArray = {favsArray}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
contentContainerStyle={styles.messagesList}
refreshing={refreshing}
onRefresh={() =>
db.ref('deals').once('value', (snapshot) =>{
let testData = [];
snapshot.forEach((child)=>{
// if (child.val().hasOwnProperty('title')){
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
currentPrice: child.val().price,
discountAmount: child.val().discAmt,
discType: child.val().discType,
promoCode: child.val().promoCode,
expiration: child.val().expDate,
postedDate: child.val().postDate,
image: { uri: child.val().imageLink},
amzLink: child.val().affLink,
category: child.val().category,
indexStore: child.val().indexStore,
store: child.val().store
})
// }
checkMessages(testData);
})
})
.then()
.catch((error) => {console.error('Error:', error)})
}
/>
Card component, this is in a FlatList where favsArray is passed as a prop (correctly verified by console), along with the individual listing data. If it finds the listing in the fav array, it should set to HeartFilled (1), if not set to HeartEmpty (0).
let test = [];
test = favsArray.split(',');
let isFav = 0;
let found = test.find(function (element) {
return element == indexStore;
});
if (found != undefined){
isFav = 1;
}
const [heartFilled, setHeartFilled] = useState( isFav == 1 ? require('../assets/heartFilled.png') : require('../assets/heartEmpty.png'));
console.log(isFav + ' ' + heartFilled);
Looking at my console, you can see it correctly shows each listing as a favorite, but for the first 10 listings it sets the state to the wrong image (21, shown in red). These should all be 22.

React Native: Extra empty space on top of the screen

I have a bug where a user clicks on a survey and then opens up what is called supporting information that expands the UI further, then the user selects his or her answer and clicks on the NEXT QUESTION button, at that point the whole top part of the screen drops down exposing this huge gap. This is the code I believe governs all that behavior:
class BallotSurveyDetails extends PureComponent {
componentDidUpdate(prevProps) {
if (prevProps.currentWizardPage !== this.props.currentWizardPage) {
this.scroll.props.scrollToPosition(0, 0, true);
}
}
render() {
const {
currentWizardPage,
selectedSurvey,
handleNextQuestionButtonPress,
handleResponseChanged,
loading,
responses,
handleSubmitButtonPress,
saving,
wizardPages
} = this.props;
if (!saving && loading) {
return <Loading />;
}
const isWizard = selectedSurvey.Layout !== "Wizard";
const isList = selectedSurvey.Layout !== "List";
const displayNextQ = isWizard && currentWizardPage < wizardPages;
const displaySubmit =
isList || (isWizard && currentWizardPage === wizardPages);
const sortedGroups = (selectedSurvey.QuestionGroups || []).sort(
(a, b) => a.Order - b.Order
);
const wizardGroup = isWizard ? sortedGroups[currentWizardPage - 1] : null;
return (
<SafeAreaView style={styles.container}>
{isWizard && wizardPages.length > 1 && (
<Card style={styles.pagination}>
<RadioPagination
numberOfPages={wizardPages}
currentPage={currentWizardPage}
/>
</Card>
)}
<KeyboardAwareScrollView
showsVerticalScrollIndicator={false}
extraScrollHeight={45}
innerRef={ref => {
this.scroll = ref;
}}
enableOnAndroid={true}
contentContainerStyle={{ paddingBottom: 90 }}
>
<View style={styles.headerContainer}>
<Text style={styles.ballotTitle}>{selectedSurvey.Name}</Text>
<Text style={styles.ballotSubtitle}>
{selectedSurvey.Description}
</Text>
</View>
{isList &&
What I tried to do to resolve this was add automaticallyAdjustContentInsets={false} inside the KeyboardAwareScrollView, did nothing to resolve the bug. Any ideas anyone?
I'm not sure what's causing this for you, but here are a few things that have corrected similar problems I've had in the past:
It can help to wrap every screen in a container with flex:1.
I had a similar case with conditionally rendering a search bar above a FlatList and I used this to fix it:
I added this to the top of my file.
import { Dimensions, other stuff you need} from 'react-native';
const deviceHieght = Dimensions.get('window').height;
and then I wrapped my FlatList in a view like this
<View style={this.state.showBar === false ? styles.containFlatlist : styles.containSearchFlatlist}>
and this is the styling it was referencing
containFlatlist: {
height: deviceHieght
},
containSearchFlatlist: {
height: deviceHieght-100
},
In a different similar case I had an issue like this with a screen that displayed photos on click within a scrollview. In that case I did this:
<ScrollView
ref={component => this._scrollInput = component}
>
Then in componentDidMount I put
setTimeout(() => {
this._scrollInput.scrollTo({ x: 0, animated: false })
}, 100)
I was also using react navigation in this case so I also did
return(<View style={styles.mainFlex}>
<NavigationEvents
onWillBlur={payload => this._scrollInput.scrollTo({x:0})}
/>
Followed by the rest of my code.
I hope one of those helps. Given that you're also dealing with a scrollview, my best guess is that the third fix is most likely to work in your situation.
So the appear is with this code snippet here:
componentDidUpdate(prevProps) {
if (prevProps.currentWizardPage !== this.props.currentWizardPage) {
this.scroll.props.scrollToPosition(0, 0, true);
}
}
In particular, this.scroll.props.scrollToPosition(0, 0, true);. In removing the whole component lifecycle method, the bug went away.

How to make setState update before other function with useState

I am building a form to confirm the pin code of telephone number after user entered it.
I started code with
const [code, setCode] = useState("");
and I use Smooth pin code input like that
<SmoothPinCodeInput
cellStyle={{
width: 50,
borderBottomWidth: 2,
borderColor: "gray"
}}
cellStyleFocused={{
borderColor: "black"
}}
animated
onFulfill={() =>
{
setIsCodeCompleted(true)
confirmUserPin()
}
}
value={code}
onTextChange={code => setCode(code)}
/>
the problem is that setCode(code) works after confirmUsePin() that is called with onFilfill() so my pin code is 4 numbers but only 3 numbers get sent, this happend because setCode doesn't update code immediately.
I solved it, I used useEffect so every time length of code is 4 it calls the function. Here is how
useEffect( () =>{
if(code.length == 4){
confirmUserPin()
}
}, [code] )

How to solve blink image in react-native-snap-carousel?

How to solve blink image when back to first item in react-native-snap-carousel ? I try to look for many examples but fail all.
This is my script :
renderSlider ({item, index}) {
return (
<View style={styles.slide}>
<Image source={{uri: item.cover}} style={styles.imageSlider} />
</View>
);
}
<Carousel
ref={(c) => { this._slider1Ref = c; }}
data={data}
renderItem={this.renderSlider}
sliderWidth={width}
itemWidth={(width - 30)}
itemWidth={(width - 30)}
inactiveSlideScale={0.96}
inactiveSlideOpacity={1}
firstItem={0}
enableMomentum={false}
lockScrollWhileSnapping={false}
loop={true}
loopClonesPerSide={100}
autoplay={true}
activeSlideOffset={50}
/>
the comple documentation you can find here and about the plugin api you can find here.
Please anyone help me.
Thanks.
I had the same issue when loop={true} was set.
We came up with this workaround:
We maintained the activeSlide value in a state, and created a reference of Carousel refCarousel.
const [activeSlide, setActiveSlide] = useState(0);
const refCarousel = useRef();
Then we added code in useEffect to manually move the carousel item to the first one back when it reaches the end with a delay of 3500 milliseconds which is also set to autoplayInterval props.
This way, we achieved the looping effect.
useEffect(() => {
if (activeSlide === data.length - 1) {
setTimeout(() => {
refCarousel.current.snapToItem(0);
}, 3500)
}
}, [activeSlide]);
Below is the Carousel component declaration. Only the relevant props are shown here.
<Carousel
ref={refCarousel}
...
//loop={true}
autoplay={true}
autoplayDelay={500}
autoplayInterval={3500}
onSnapToItem={(index) => setActiveSlide(index)}
/>
use React Native Fast Image if you are facing blinking issue.