React Native for loop with Array not doing what I expected - react-native

I'm trying out some things in React Native for the first time and i'm trying to roll 3 dices (text based for now).
I'm using a for loop to go over an array of the 3 dices. However i'm only seeing one dice text being updated (the 3rd one).
Also when doing some alerts to check what's going on within that for loop, i'm seeing unexpected things? the first alert says 2, the second alert says 1 and then it usually no longer alerts, seldom it also alerts a third time with a 0.
My code so far:
(file: Game.js)
import React from 'react'
import { StyleSheet, Image, Text, View, Button } from 'react-native'
export default class Home extends React.Component {
state = {
dices: [null, null, null],
rollsLeft: 3,
keepDices: [false, false, false],
}
//When this component is mounted (loaded), do some defaults
componentDidMount() {
}
//Roll dices
rollDices = () => {
for(let i = 0; i < 3; i++){
alert('for loop at ' + i);
//Math random to get random number from rolling the dice
randomNumber = Math.floor(Math.random() * 6) + 1;
//Check if the user wanted to keep a dice's value before reassigning a new value
if(this.state.keepDices[i] === false){
//User want to roll this dice, assign new random number to it
//this.setState.dices[i] = randomNumber;
let newDices = [ ...this.state.dices ];
newDices[i] = randomNumber;
this.setState({ dices : newDices });
}
}
//Deduct 1 roll from total
this.setState.rollsLeft--;
//TODO: Check if rolls equals 0, then make player2 active!
}
render() {
return (
<View style={styles.container}>
<Text> {this.state.dices[0]} ONE </Text>
<Text> {this.state.dices[1]} TWO</Text>
<Text> {this.state.dices[2]} THREE</Text>
<Text>Turns left: {this.state.rollsLeft} </Text>
<Button
title="Roll 🎲"
onPress={this.rollDices} />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
},
})

In React Native the setState function is asynchronous.
Meaning that this.setState({ dices : newDices }); can end up setting dices to different values depending on which finishes first.
If you want to control what happens after you use setState, you can call a function after the set is done like this
this.setState({dices: newDices}, () => {
// Do something here.
});
There is some really useful information on calling function after the setState here: Why is setState in reactjs Async instead of Sync?
and some good explanations of how setState in react works and how to get around it here: https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3

Together with DCQ's valuable input for async setStates bundling I also noticed that i'm always resetting the copied dice array within the for loop and thus only saving my last dice correctly.
Next up the for loop was actually counting right from 0 to 2 however the alert boxes don't interrupt the code as i'm used to in the browser therefore it looked a bit off. When doing console.log (which is also cleaner and more correct logging) I noticed things did went right there.

Related

How to disable animations when the user scrolls (react-native-reanimated 2)

Here's a weekly mini calendar, that turns into a monthly mini calendar component.
When it turns from weekly to monthly we have some entering/exiting animations
So far so good.
Problem:
The problem is, that those animations (being entering/exiting animations) also take place while the user is scrolling.
As you can see in the gif, animations play when I scroll horizontally, which isn't what I want, I only want animations when the component changes from weekly to monthly (expands/collapses)
Code:
import Animated, {
FadeInDown,
FadeInUp,
SlideOutUp,
SlideOutDown,
} from 'react-native-reanimated';
const MiniCalendarItem = () => {
let animationEnter;
let animationExit;
if (this.props.itemRepresents === ITEM_REPRESENTS.MONTH) {
if (this.dayIsPartOfCurrentWeek(day)) {
animationEnter = FadeInUp;
} else {
animationEnter = FadeInUp.delay((weekIndex * 150)).duration(350)
}
animationExit = SlideOutDown.duration(400);
} else {
animationEnter = FadeInDown.duration(500);
animationExit = SlideOutUp.duration(400);
}
return (
<Animated.View
entering={animationEnter}
exiting={animationExit}
key={`dayData_${dayProps.id}`}
>
{...}
</Animated.View>
);
};
and here's the parent:
renderItem = () => {
return (
<MiniCalendarItem
animationsEnabled
key={itemKey}
mode={mode}
itemRepresents={visible ? ITEM_REPRESENTS.MONTH : ITEM_REPRESENTS.WEEK}
/>
)
}
}
Essentially the parent is a ScrollView (not a FlatList)
Question:
How can I stop react-native-reanimated#2 from playing any animations and when is it a good time to do that.
I added a animationsEnabled prop, but ideally I'd love to feed it with an Animated.Value(true) object. I'm just not sure how to conditionally disable animations based on that prop, from within the MiniCalendarItem.

Populte WYSIWYG editor after react native fetch

I am trying to incorporate this WYSIWYG package into my react native project (0.64.3). I built my project with a managed workflow via Expo (~44.0.0).
The problem I am noticing is that the editor will sometimes render with the text from my database and sometimes render without it.
Here is a snippet of the function that retrieves the information from firebase.
const [note, setNote] = useState("");
const getNote = () => {
const myDoc = doc(db,"/users/" + user.uid + "/Destinations/Trip-" + trip.tripID + '/itinerary/' + date);
getDoc(myDoc)
.then(data => {
setNote(data.data()[date]);
}).catch();
}
The above code and the editor component are nested within a large function
export default function ItineraryScreen({route}) {
// functions
return (
<RichEditor
onChange={newText => {
setNote(newText)
}}
scrollEnabled={false}
ref={text}
initialFocus={false}
placeholder={'What are you planning to do this day?'}
initialContentHTML={note}
/>
)
}
Here is what it should look like with the text rendered (screenshot of simulator):
But this is what I get most of the time (screenshot from physical device):
My assumption is that there is a very slight delay between when the data for the text editor is actually available vs. when the editor is being rendered. I believe my simulator renders correctly because it is able to process the getNote() function faster.
what I have tried is using a setTimeOut function to the display of the parent View but it does not address the issue.
What do you recommend?
I believe I have solved the issue. I needed to parse the response better before assigning a value to note and only show the editor and toolbar once a value was established.
Before firebase gets queried, I assigned a null value to note
const [note, setNote] = useState(null);
Below, I will always assign value to note regardless of the outcome.
if(data.data() !== undefined){
setNote(data.data()[date]);
} else {
setNote("");
}
The last step was to only show the editor once note no longer had a null value.
{
note !== null &&
<RichToolbar
style={{backgroundColor:"white", width:"114%", flex:1, position:"absolute", left:0, zIndex:4, bottom: (toolbarVisible) ? keyboardHeight * 1.11 : 0 , marginBottom:-40, display: toolbarVisible ? "flex" : "none"}}
editor={text}
actions={[ actions.undo, actions.setBold, actions.setItalic, actions.setUnderline,actions.insertLink, actions.insertBulletsList, actions.insertOrderedList, actions.keyboard ]}
iconMap={{ [actions.heading1]: ({tintColor}) => (<Text style={[{color: tintColor}]}>H1</Text>), }}
/>
<RichEditor
disabled={disableEditor}
initialFocus={false}
onChange={ descriptionText => { setNote(descriptionText) }}
scrollEnabled={true}
ref={text}
placeholder={'What are you planning to do?'}
initialContentHTML={note}
/>
}
It is working properly.

Settings different Values of element on a state

So in my react native, I have a spinner which I am using to enter numbers. It has two buttons which increases or decreases a value. But the problem is that I have to set the value to a state and I have multiple elements. So if I change the value of one element, everything else changes too.
Here is the Package
And here is a sample code I am working with:
this.state = {
qty: null,
}
<InputSpinner
max={50}
min={1}
step={1}
width={100}
height={50}
colorMax={"#2a292d"}
colorMin={"#2a292d"}
buttonFontSize={13}
value={this.state.qty}
onChange={(num) => {
this.setState({qty: num});
}}/>
So on change I am settings the qty state. But I have multiple spinners and changing one changes everything because each uses the same state. What would be the better solution for this? Should I use an array to store each item qty?
For me the better solution is assign at each Spinner an ID and then create an object with key = spinnerID and value = num
this.state = {
qty: {},
}
<InputSpinner
max={50}
min={1}
step={1}
width={100}
height={50}
colorMax={"#2a292d"}
colorMin={"#2a292d"}
buttonFontSize={13}
value={this.state.qty['1'] || 1}
onChange={(num) => {
let qty = Object.assign({}, this.state.qty);
qty['1'] = num;
this.setState({qty});
}}/>
yes, obviously you need to maintain multiple states for each spinner, never use one state for that. I would recommend to use an array like
`
this.state = {
spinnerValues:[]
}
`
and onChange of that input spinner you can do somewhat like
`
onChange={(num) => {
let currentState = this.state.spinnerValues;
currentState[i] = num; // here i is the index which you will provide for the spinner num
this.setState({spinnerValues: currentState});
`
and for value of each spinner
value = {this.state.spinnerValues[i]}

React Native: Animation not working properly on android

I have been trying to fix issue for the past 2 days, it works fine on iOS
constructor(){
super();
this.animation = new Animated.Value(0)
this.state = {
expanded: false,
};
}
toggle(){
let initialValue = this.state.expanded? 1 : 0 ,
finalValue = this.state.expanded? 0 : 1
this.setState({
expanded: !this.state.expanded,
});
this.animation.setValue(initialValue)
Animated.timing(
this.animation,
{
toValue: finalValue,
}
).start();
}
render(){
return(
<Animated.View style={{{flex: 1, marginTop: 28, paddingLeft: 25, transform: [{ scale: this.animation }, {perspective: 1000 }]}}}>
....
</Animated.View>
);
}
This component is child , used in parent like this: <FeedBack ref={ref => this.feedback = ref}/> without any conditions to check to show or not (because animation scale is set to Zero in the constructor, so no need)
the toggle() method is being called from parent when a button pressed.
Now this works fine on iOS , when component loads, the view is not there until a button is pressed (then scaled). but on android when the component loads, the view already there. When I press the button, the animated view disappears and re-appears (with animation scaling) and subsequent toggles work fine. The problem in android is that even though initialValue of the scale is Zero, the view is still there when it first loads.
This has been an issue with react-native on the android side for a while now (sigh). It seems that setting the value to 0 strips it of its characteristics, basically deeming it as null and then reverting to using its actual value once it had animated to > 0
A work around is to set animation like so
this.animation = new Animated.Value(0.01);
You can follow the issue here

How do i fill and remove an item into an array depending on its state?

i am developing an React Native Android App.
I am receiving data (id and name) from my API. Now i am using a ListView with MKIconToggle (react-native-material-kit) to display my list data.
With MKIconToggle i can give my displayed items two different states (clicked = color is black / unclicked = grey). Now i want to send the list of clicked items back to my server. But i just can´t figure out how i put the clicked items for example into an array or something and only send clicked items to server.
My RenderMethod with the ListView looks like this:
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderList}
horizontal={true}
renderHeader={this.renderHeader}
/>
RenderList:
<View
style={{justifyContent: 'flex-start', alignItems: 'center', padding: 10, flexDirection: 'column'}}>
<MKIconToggle
checked={this.state.initialChecked}
onCheckedChange={()=>this._onIconChecked(data)}
onPress={this._onIconClicked}
style={{flexDirection: 'row', justifyContent: 'center', alignItems: 'center'}}>
<Text state_checked={this.state.checkedState}
pointerEvents="none"
style={styles.titleChecked}
numberOfLines={3}>{data.name}</Text>
<Text pointerEvents="none"
style={styles.title}
numberOfLines={3}>{data.name}</Text>
</MKIconToggle>
</View>
Now i should handle my clicked Items in _onIconChecked:
_onIconChecked: function (data) {
// Put data.id into an array, if it way clicked (state is true)
// If date.id is unclicked again, then remove from array
},
I hope i could explain my issue clearly, otherwise just let me know. I am new in programming and writing stackoverflow issues/questions, so please give me hints if i made something wrong.
From your information I will have to make a few assumptions in order to be able to answer your question.
For now I will assume that that checked handler properly returns the 'id' of an item that is the same id as in your array of items.
So assuming your array is:
[{name: 'Luke', id: 1'}, {name: 'Darth', id: 2'}]
If I were to click on 'Luke' _onIconChecked would receive a data object that at least has id: 1 in it.
The second assumption is that you have an array somewhere where you can store those clicked items. I would just put that outside of your component seeing as MK would already take care of properly rendering a checked item. So:
var _checkedItems = []
var myComponent = React.create...
The last assumption is that the data object passed to _onIconChecked also contains information on the state of the checkbox, so date.checked is either true or false.
The precise implementation might be different for all these items, but this is what I can work off of.
Now what you could do is:
_onIconChecked: function (data) {
var id = data.id;
var checked = data.checked;
var currentIndex = _checkedItems.indexOf(id)
if(checked) {
if(currentIndex == -1) { _checkedItems.push(id) }
// the 'else' would mean the item is already in the array, so no need to add
} else {
if(currentIndex > -1) {
// This means that the item is in the array, so lets remove it:
_checkedItems.splice(currentIndex, 1) // This removes the id from the array.
}
}
}
What you'd do now to only get the items from your this.state.items array that have their ids in the checked array:
getCheckedItems: function() {
return this.state.items.map(function(item) {
if(_checkedItems.indexOf(item.id) > -1){
return item
}
})
}
I am not sure about your setup so I made lots of assumptions and probably over engineered some things, but it might get you going in the right direction.