setState function not assigning boolean.. why? - reactjs

Im new to react native and i was not able to undestand this behaviour.
in a async function i am setting a loading boolean to the state before an api call.
Now what is strange: after recieving the response i cannot set the loading boolean back to false. it just keeps waiting at the setState function without reaching callback.
I already tryed to setState in multiple calls for every value. or all in one function call. i also tried to assign the whole response to state.
in react native i can console log the response after fetching it form backend. also i am able to assign the response to the state when leaving the loading boolean out (reaching callback).. however not when trying to set the loading boolean back to false.
hint (maybe):
in the json response i include an array. when leaving out the array (not assigning it to state) it behaves like it should...
please help!
// here my mistery
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
}
}
async onLogin() {
try {
this.setState({ loading: true }, function () {
console.log('loading is:' + this.state.loading)
}); //works great
const { name, password } = this.state;
//here the api call
const response =
await fetch(`http://10.0.2.2:3000/api/login/${name}&${password}`);
const answer = await response.json();
if (answer.user == 'OK') {
//now the strange function-call
this.setState({
tickets: answer.tickets, //this is the array..!
title: answer.event.title,
token: answer.token.token,
tokenDa: true,
//loading: false, //only reaching callback when commenting that out
}, function () {
console.log('tickets are :' + this.state.tickets[0].kategorie)
})
// same behaviour here
// this.setState({loading: false}, function(){
// console.log('loading ist ' + this.state.loading);
// });
}
}
catch{
console.log('in CATCH ERROR')
}
}
// here also the render function in case that matters:
render() {
if (this.state.loading) {
return (
<View style={styles.container}>
<ActivityIndicator size='large' />
</View>
);
}
return this.state.tokenDa ? (
<View style={styles.container}>
<Text>Event Title : {this.state.title} </Text>
<Button
title={'scan'}
style={styles.input}
/>
{this.state.tickets.map((ticket, i) => (
<div key={i}>
<Text >TicketKategorie : {ticket.kategorie}</Text>
<Text > Datum und Türöffnung {ticket.gueltig_am}</Text>
<Text >Verkauft {ticket.verkauft}</Text>
<Text >Eingescannt {ticket.abbgebucht}</Text>
</div>
))}
</View>
) : (
<View style={styles.container}>
<TextInput
value={this.state.name}
onChangeText={(name) => this.setState({ name })}
placeholder={'Username'}
style={styles.input}
/>
<TextInput
value={this.state.password}
onChangeText={(password) => this.setState({ password })}
placeholder={'Password'}
secureTextEntry={true}
style={styles.input}
/>
<Button
title={'Login'}
style={styles.input}
onPress={this.onLogin.bind(this)}
/>
</View>
)
};
};
the result is: a loading screen due to a loading attribute which i cannot set back to false. why is this happening!?

You need to wrap the rest of your code inside the callback for the first setState, like this...
this.setState({ loading: true }, async function () {
const { name, password } = this.state;
const response =
await fetch(`http://10.0.2.2:3000/api/login/${name}&${password}`);
const answer = response.json();
if (answer.user == 'OK') {
this.setState({
tickets: answer.tickets, //this is the array..!
title: answer.event.title,
token: answer.token.token,
tokenDa: true,
loading: false
});
}
});

As you may have already deduced, there is an issue with the asynchronous nature of your setStates. To workaround this, we can create another function to handle the the actual API request and use that in the call-back for the first set-state.
setLoading = () => {
this.setState({ loading: true }, () => this.onLogin())
}
onLogin = async () => {
const { name, password } = this.state;
//here the api call
const response = await fetch(`http://10.0.2.2:3000/api/login/${name}&${password}`);
const answer = await response.json();
if (answer.user == 'OK') {
this.setState({
tickets: answer.ticketsm
title: answer.event.title,
token: answer.token.token,
tokenDa: true,
loading: false
}, function () {
console.log('tickets are :' + this.state.tickets[0].kategorie)
})
}

Related

React state is not updated immediately after deleting data

I have a problem to update the view in React Native after deleting a POST.
I think it could be a problem with the "state" but don't know how to fix it.
This is my list of Posts.
When I press on an item, it ask us to confirm the action.
When we confirm the action of delete, POST is deleted from Firebase but the view is not updated (Still 2 items in the list but only one in database. if I refresh and re-enter to the component, the list is updated)
This is my code :
class GetPosts extends React.Component {
static navigationOptions = ({navigation}) => {
const {params} = navigation.state;
};
constructor(props) {
super(props);
this.state = {
data: {},
data2: [],
posts: {},
newArray: [],
postsCount: 0,
};
}
componentDidMount() {
var f_id = this.props.identifier;
firebase
.database()
.ref('/posts/')
.orderByKey()
.on('value', snapshot => {
snapshot.forEach(el => {
if (el.val().film_id == f_id) {
this.state.data = [
{
email: el.val().email,
puid: el.val().puid,
username: el.val().username,
time: el.val().time,
text: el.val().text,
},
];
this.setState({
data2: this.state.data2.concat(this.state.data),
});
}
});
this.state.data2.forEach(obj => {
if (!this.state.newArray.some(o => o.puid === obj.puid)) {
this.state.newArray.push({...obj});
}
});
this.setState({
posts: this.state.newArray,
postsCount: _.size(this.state.newArray),
});
console.log('valeur finale POSTS=' + this.state.posts);
});
}
renderPosts() {
const postArray = [];
_.forEach(this.state.posts, (value, index) => {
const time = value.time;
const timeString = moment(time).fromNow();
postArray.push(
<TouchableOpacity
onLongPress={this._handleDelete.bind(this, value.puid)}
key={index}>
<PostDesign
posterName={value.username}
postTime={timeString}
postContent={value.text}
/>
</TouchableOpacity>,
);
//console.log(postArray);
});
_.reverse(postArray);
return postArray;
}
_handleDelete(puid) {
const email = firebase.auth().currentUser.email;
let user_email = firebase.database().ref('/posts');
user_email.once('value').then(snapshot => {
snapshot.forEach(el => {
console.log('Userdb :' + el.val().email);
if (email === el.val().email) {
Alert.alert(
'Supprimer le message',
'Are you sure to delete the post?',
[
{text: 'Oui', onPress: () => this._deleteConfirmed(puid)},
{text: 'Non'},
],
);
//console.log('Userdb :' + el.val().email);
} else {
//
console.log('Usercur :' + email);
}
});
});
}
_deleteConfirmed(puid) {
const uid = firebase.auth().currentUser.uid;
firebase
.database()
.ref('/posts/' + uid + puid)
.remove();
this.setState({
posts: this.state.newArray.filter(user => user.puid !== puid),
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.profileInfoContainer}>
<View style={styles.profileNameContainer}>
<Text style={styles.profileName}>{this.props.email}</Text>
</View>
<View style={styles.profileCountsContainer}>
<Text style={styles.profileCounts}>{this.state.postsCount}</Text>
<Text style={styles.countsName}>POSTS</Text>
</View>
</View>
<ScrollView styles={styles.postContainer}>
{this.renderPosts()}
</ScrollView>
</View>
);
}
}
Thank you in advance !!
Several places in your code you are accessing this.state inside of setState, which can cause problems like this. You should be using a function with prevProps whenever you are accessing state within setState.
For example, within _deleteConfirmed:
this.setState({
posts: this.state.newArray.filter(user => user.puid !== puid),
});
should be changed to:
this.setSate(prevState => ({
posts: prevState.newArray.filter(user => user.puid !== puid),
});

[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'currentUser.uid')]

i get this warning when i try to store details to firebase using the currently logged in user.
i followed a online tutorial but i get this problem, is there any alternate method to use to achieve this?
i read about this problem but couldn't find a fix, i saw that its due to the user not being logged in but i did log in
var currentUser
class DecodeScreen extends Component {
addToBorrow = async (booktitle, bookauthor, bookpublisher, bookisbn) => {
currentUser = await firebase.auth().currentUser
var databaseRef = await firebase.database().ref(currentUser.uid).child('BorrowedBooks').push()
databaseRef.set({
'title': booktitle,
'author': bookauthor,
'publisher': bookpublisher,
'isbn': bookisbn
})
}
state = {
data: this.props.navigation.getParam("data", "NO-QR"),
bookData: '',
bookFound: false
}
_isMounted = false
bookSearch = () => {
query = `https://librarydb-19b20.firebaseio.com/books/${9781899606047}.json`,
axios.get(query)
.then((response) => {
const data = response.data ? response.data : false
console.log(data)
if (this._isMounted){
this.setState({ bookData: data, bookFound: true })
}
})
}
renderContent = () => {
if (this.state.bookFound) {
return(
<View style={styles.container2}>
<View style={styles.htext}>
<TextH3>Title :</TextH3>
<TextH4>{this.state.bookData.title}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Author :</TextH3>
<TextH4>{this.state.bookData.author}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Publisher :</TextH3>
<TextH4>{this.state.bookData.publisher}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Isbn :</TextH3>
<TextH4>{this.state.bookData.isbn}</TextH4>
</View>
</View>
)
}
else {
return(
<View style={styles.loading}>
<ActivityIndicator color="blue" size="large" />
</View>
)
}
}
componentDidMount(){
this._isMounted = true
}
componentWillUnmount(){
this._isMounted = false
}
render() {
{this.bookSearch()}
return (
<View style={styles.container}>
{this.renderContent()}
<Button title='Borrow' onPress={() => this.addToBorrow(this.state.bookData.title, this.state.bookData.author, this.state.bookData.publisher, this.state.bookData.isbn)} />
</View>
);
}
}
First, firebase.auth().currentUser is not a promise, and you can't await it. It's either null or a user object.
Second, it's not guaranteed to be populated with a user object on immediate page load. You will need to use an auth state observer to know when the user object first becomes available after a page load.

React Select with Custom MultiValueContainer and Apollo Mutation not Cooperating

I have a custom component that uses Apollo and React-Select and has two mutations (see below). The react-select is multivalue and needs to be custom because I need an "isSelected" checkbox on it. Not shown in this code, but the initial options list is passed in from the parent container.
The parent Select and its mutation work as expected. However, I'm running into a couple of odd problems with the custom MultiValueContainer. The first is that the first time I select any of the check-boxes, I get an error saying that "Can't call setState (or forceUpdate) on an unmounted component." Note below that I have an empty componentWillUnmount function just to see if it gets called, which it doesn't. But (I assume) as a result of that, when the "toggleThing" mutation is called, the state doesn't have the vars necessary to complete the request. The second time I click it works as expected, with the exception of the second issue.
The second issue is that the onCompleted function on the MultiValueContainer mutation never fires, so even though the server is returning the expected data, it never seems to get back to the mutation and therefore never to the component. The onCompleted function on the parent Select works as expected.
Thanks in advance for any insights anyone might have. Perhaps needless to say, I am relatively new to react/apollo/react-select and apologize in advance for any newbie mistakes. Also, I've tried to scrub and simplify the code so apologies also for any renaming mistakes.
const UPDATE_THINGS = gql`
mutation UpdateThings(
$id: ID!
$newThings: [ThingInput]
) {
updateThings(
id: $id
newThings: $newThings
) {
id
}
}
`;
const TOGGLE_THING = gql`
mutation ToggleThing($id: ID!, $isChecked: Boolean) {
toggleThing(
id: $id
isChecked: $isChecked
) {
id
}
}
`;
class ThingList extends Component {
stylesObj = {
multiValue: base => {
return {
...base,
display: 'flex',
alignItems: 'center',
paddingLeft: '10px',
background: 'none',
border: 'none'
};
}
};
constructor(props) {
super(props);
this.state = {
selectedThings: [],
selectedThingId: '',
selectedThingIsChecked: false
};
}
onUpdateComplete = ({ updateThings }) => {
console.log('onUpdateComplete');
console.log('...data', updateThings );
this.setState({ selectedThings: updateThings });
};
onToggleThing = (thingId, isChecked, toggleThing) => {
console.log('onToggleThing, thingId, isChecked');
this.setState(
{
selectedThingId: thingId,
selectedThingIsChecked: isHighPisCheckedoficiency
},
() => toggleThing()
);
};
onToggleThingComplete = ({ onToggleThing }) => {
console.log('onToggleThingComplete ');
console.log('...data', onToggleThing );
this.setState({ selectedThings: onToggleThing });
};
handleChange = (newValue, actionMeta, updateThings) => {
this.setState(
{
selectedThings: newValue
},
() => updateThings()
);
};
isThingSelected = thing=> {
return thing.isSelected;
};
getSelectedThings = selectedThings => {
console.log('getSelectedSkills');
return selectedThings ? selectedThings.filter(obj => obj.isSelected) : [];
};
componentWillUnmount() {
console.log('componentWillUnmount');
}
render() {
const self = this;
const MultiValueContainer = props => {
// console.log('...props', props.data);
return (
<Mutation
mutation={ TOGGLE_THING }
onCompleted={self.onToggleThingComplete}
variables={{
id: self.state.selectedThingId,
isChecked: self.state.selectedThingIsChecked
}}>
{(toggleThing, { data, loading, error }) => {
if (loading) {
return 'Loading...';
}
if (error) {
return `Error!: ${error}`;
}
return (
<div className={'option d-flex align-items-center'}>
<input
type={'checkbox'}
checked={props.data.isChecked}
onChange={evt => {
self.onToggleThing(
props.data.id,
evt.target.checked,
toggleIsHighProficiency
);
}}
/>
<components.MultiValueContainer {...props} />
</div>
);
}}
</Mutation>
);
};
return (
<Mutation
mutation={UPDATE_THINGS}
onCompleted={this.onUpdateComplete}
variables={{ id: this.id, newThings: this.state.selectedThings}}>
{(updateThings, { data, loading, error }) => {
if (loading) {
return 'Loading...';
}
if (error) {
return `Error!: ${error}`;
}
return (
<div>
<Select
options={this.props.selectedThings}
styles={this.stylesObj}
isClearable
isDisabled={this.props.loading}
isLoading={this.props.loading}
defaultValue={this.props.selectedThings.filter(
obj => obj.isSelected
)}
isOptionSelected={this.isOptionSelected}
isMulti={true}
onChange={(newValue, actionMeta) =>
this.handleChange(
newValue,
actionMeta,
updateThings
)
}
components={{
MultiValueContainer
}}
/>
</div>
);
}}
</Mutation>
);
}
}
export default ThingsList;
You are redefining MultiValueContainer on every render, which is not a good practice and may cause unexpected behavior. Try moving it into separate component to see if it helps.

How i can limit the items in the FlatList and add load more?

My skills is basic, and i'm newbie in React native, what i want to do is limit the posts in 12 and when the user scroll automatically load more posts.
My Code:
export default class Posts extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,};}
componentDidMount() {
return fetch(ConfigApp.URL+'json/data_posts.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataPosts: responseJson
}, function() {
});
})
.catch((error) => {
});}
render() {
return (
<FlatList
data={ this.state.dataPosts }
numColumns={2}
renderItem={({item}) =>
<TouchableOpacity activeOpacity={1} style={{flex: 1}}>
<View style={{margin: 5, marginLeft: 4}}>
<ImageBackground source={{uri: ConfigApp.IMAGESFOLDER+item.post_image}}>
<LinearGradient colors={['rgba(0,0,0,0.3)', 'rgba(0,0,0,0.8)']}>
<Text numberOfLines={2}>{item.post_title}</Text>
</LinearGradient>
</ImageBackground>
</View>
</TouchableOpacity>
}
keyExtractor={(item, index) => index}
/>
);}}
If your requirement is to append the existing list from already pulled data in a chunk of 12, then you may consider following strategy which uses onEndReached and onEndThreshold to handle the scroll and add 12 records at a time.
Set current page number to 0 in constructor
constructor(props){
super(props);
this.state = {
... ,
page: 0,
posts: []
}
}
Inside componentDidMount you need to pull all data from the server and store it in the local state (which you are currently doing), then call the function which will read first 12 records.
componentDidMount() {
return fetch(ConfigApp.URL+'json/data_posts.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
page: 0,
dataPosts: responseJson
}, function() {
// call the function to pull initial 12 records
this.addRecords(0);
});
})
.catch((error) => {
});
}
Now add the function which will add records from this.state.dataPosts
addRecords = (page) => {
// assuming this.state.dataPosts hold all the records
const newRecords = []
for(var i = page * 12, il = i + 12; i < il && i <
this.state.dataPosts.length; i++){
newRecords.push(this.state.dataPosts[i]);
}
this.setState({
posts: [...this.state.posts, ...newRecords]
});
}
Now add the scroll handler
onScrollHandler = () => {
this.setState({
page: this.state.page + 1
}, () => {
this.addRecords(this.state.page);
});
}
Render function
render() {
return(
...
<FlatList
...
data={this.state.posts}
renderItem={({item}) => ... }
keyExtractor={(item, index) => index}
onEndReached={this.onScrollHandler}
onEndThreshold={0}
/>
...
);
}
Hope this will help!
You Can add the slice(start,end) method while fetching jsondata in datasource. This trick may solve your problem.
dataPosts: responseJson.slice(0,10) replace this line with yours.
In FlatList prop Data you will use slice(start, end)
exp:
data={Data.slice(0,4)}
in above exp flatlist will show only the first 4 object

AsyncStorage not working

console.log returns undefined.
AsyncStorage doesn't work. It doesn't save and does not register.
Please help me.
if (this.props.zamqiDetay == 1) {
this.setState({ number0State: this.props.processDetayDeger });
AsyncStorage.setItem('loover', 'sadfasd');
}
Render:
render() {
AsyncStorage.getItem('loover').then((value) => {
this.setState({ loover: value });
}).done();
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
console.log(this.state.loover);
return ();
You can do something like this.
let storage = async () => await AsyncStorage.getItem('item');
storage().then((res)=>{
if(res) {
//we have out data
}
}).catch((err)=>{
// oops
});
You need to declare await keyword while doing setItem and getItem in asynchronous storage
await AsyncStorage.getItem('loover').then((value) => {
this.setState({ loover: value });
}).done();
await AsyncStorage.setItem('loover', 'sadfasd');

Resources