Async Storage Error Unhandled Promise Rejection in Switch - react-native

I'm new to react native and now i have a problem.
I'm trying to save my data into local storage using Async Storage, I'm following this tutorial ,
it worked properly but not in my switch code.
This is my constructor
constructor(props) {
super(props)
this.state = {prevBtn: undefined}
this.onPrevValueChange = this.onPrevValueChange.bind(this);
}
then i get the item in my didMount:
componentDidMount() {
this.getQuestionnaire();
AsyncStorage.getItem('prevBtn').then((value) => this.setState({ 'prevBtn': value }));
}
set the value when it change:
onPrevValueChange = (value) => {
AsyncStorage.setItem('prevBtn', value);
this.setState({ prevBtn: value });
}
and render method:
render(){
const {}
return(
<Switch
value={prevBtn}
backgroundActive={'red'}
onValueChange={this.onPrevValueChange}
/>
);
}
Can anybody help please?

issue with setState in componentDidMount, when you setting value for prevBtn using single quotation mark ` so it is behaving like String not a state, use it as below:
componentDidMount() {
this.getQuestionnaire();
AsyncStorage.getItem('prevBtn').then((value) => this.setState({ prevBtn: value }));
}
In render you are using prevBtn directly, use it as this.state.prevBtn.
render(){
const {}
return(
<Switch
value={this.state.prevBtn}
backgroundActive={'red'}
onValueChange={this.onPrevValueChange}
/>
);
}

You need to change your componentDidMount to this:
Note that AsyncStorage.getItem always returns a string, you'll have to convert it to a boolean before using it. And as the other commenter pointed out, you can't have quotes around the state property.
componentDidMount() {
this.getQuestionnaire();
AsyncStorage.getItem('prevBtn').then((value) => {
value === 'true'
? this.setState({ prevBtn: true })
: this.setState({ prevBtn: false })
});
}

Related

React Native: Getting data from Firebase

I'm simply trying to retrieve data from the database in Firebase, and here's what I've got
var userList = [];
firebase.database()
.ref('/users/')
.once('value')
.then(snapshot => {
snapshot.forEach((doc) => {
userList.push(doc.val());
});
});
console.log(userList);
Even though I copy and pasted this code from a tutorial, the userList is empty outside of the snapshot. Can you tell me why that is?
The request to firebase is asynchronous so console.log(userList); is called before userList.push(doc.val()); gets called.
You should make userList a component state variable so that when you update it your component will re render.
Something like the following should work:
class UserListComponent extends Component {
constructor(props) {
super(props);
this.state = {
userList: [],
};
}
componentDidMount() {
this.getUsers();
}
getUsers() {
firebase
.database()
.ref('/users/')
.once('value')
.then((snapshot) => {
snapshot.forEach((doc) => {
this.setState({
userList: [...this.state.userList, doc.val()],
});
});
});
}
render() {
return (
<View>
{this.state.userList.map((item) => {
return (
<View>
<Text>{item.name}</Text>
</View>
);
})}
</View>
);
}
}

React Native: TypeError: this.state.schedule.map is not an object

Hey I am new to React Native and currently I'm trying to put data in a picker using data from API. I'm confused that it got error say TypeError: null is not an object (evaluating this.state.schedules.map). Is there something wrong with the state or is there any concept that I misunderstood
Here is fetch API
export function getSchedule (token, resultCB) {
var endpoint = "/api/getList"
let header = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Bearer " + token
};
return dispatch => {
return fetchAPI(endpoint, 'GET', header)
.then((json) => {
dispatch({ type: t.SCHEDULE, schedules: json.datas.description });
resultCB(json.schedules)
})
.catch((error) => {
dispatch({ type: types.EMPTY_SCHEDULE });
resultCB(error)
})
}
}
this is where i put my picker
export const mapStateToProps = state => ({
token: state.authReducer.token,
message: state.authReducer.message,
schedules: state.authReducer.schedules
});
export const mapDispatchToProps = (dispatch) => ({
actionsAuth: bindActionCreators(authAction, dispatch)
});
class Change extends Component {
constructor(){
super();
this.state={
staffId: "",
schedule: '',
type_absen: 1,
schedules: null
}
}
componentDidMount(){
this.props.actionsAuth.getSchedule(this.props.token);
}
render() {
return (
<View style={styles.picker}>
<Picker
selectedValue={this.state.schedule}
style={{backgroundColor:'white'}}
onValueChange={(sch) => this.setState({schedule: sch})}>
{this.state.schedules.map((l, i) => {
return <Picker.Item value={l} label={i} key={i} /> })}
</Picker>
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Change);
This isn’t a React Native specific error. You initialized schedules to null so on first render, you try to call .map on null. That’s what is causing your error.
You fetch your data correctly in componentDidMount but that lifecycle method will fire after the initial render.
One common way to fix this is to initialize schedules to an empty array.
First initialise schedules: [] in the state with empty array, not with the null.
Fetching data in componentDidMount() is correct. ComponentDidMount() will be called after the first render of component so you have to update the state in the component from the updated store.
you can check whether props is changing or not in componentWillReceiveProps (depreciated) or in the latest alternative of componentWillReceiveProps method that is getDerivedStateFromProps().
Below is the syntax for both
componentWillReceiveProps(nextProps) {
if (this.props.schedules !== nextProps.schedules) {
this.setState({ schedules: nextProps.schedules });
}
}
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.schedules !== prevState.schedules) {
return { schedules: nextProps.schedules };
}
else return null; // Triggers no change in the state
}
Make sure your component should connected to store using connect

React Native search-box with componentDidUpdate

I'm having issue with my componentDidUpdate always updating the render()/state.
My state by default has applicantsData
this.state = {
applicantsData: []
};
ComponentDidMount and ComponentDidUpdate call my method that loads data for the state
componentDidMount() {
this.getApplicants();
}
componentDidUpdate(prevProps) {
// needs to be a unique value
if (prevProps.applicantsData !== this.props.applicantsData) {
this.getApplicants();
}
}
getApplicants = async () => {
AsyncStorage.getItem('CurrentPropertyID')
.then((value) => {
const propID = JSON.parse(value);
this.props.getApplicants(propID);
this.setState({ applicantsData: this.props.applicantsData });
});
}
Then, I have a search box and a FlatList to create my component. I'm using react-native-search-box' for the search box
<Search
ref="search_box"
onSearch={this.onSearch}
onChangeText={this.onChangeText}
/>
<FlatList
extraData={this.state}
data={this.state.applicantsData}
keyExtractor={(item) => {
return item.id.toString();
}}
renderItem={this.renderItem}
/>
My onChangeText method:
onChangeText = (searchText) => {
return new Promise((resolve) => {
let data = this.props.applicantsData;
if (searchText !== '') {
data = data.filter((item) =>
item.name.toUpperCase().includes(searchText.toUpperCase()) ||
item.Email.toUpperCase().includes(searchText.toUpperCase()) ||
item.Phone.toUpperCase().includes(searchText.toUpperCase())
).map(({ id, name, dtEntered, approve, Email, Phone, image }) =>
({ id, name, dtEntered, approve, Email, Phone, image }));
this.setState({ applicantsData: data });
} else {
this.setState({ applicantsData: this.props.applicantsData });
}
resolve();
});
}
Everything is working fine. The data is loaded and displayed in the screen. However, the componentDidUpdate is called all the name and keep updating the props, so when I use the search box to filter my state.applicantsData, it is quickly filtered, then all the data is loaded again because the this.getApplicants was called inside the componentDidUpdate().
Does it make sense? How can I fix this issue?
Thanks
This part is the problem.
componentDidUpdate(prevProps) {
// needs to be a unique value
if (prevProps.applicantsData !== this.props.applicantsData) {
this.getApplicants();
}
}
You have to use deep compare method to compare applicantsData.
You can use https://www.npmjs.com/package/deep-equal or isEqual from lodash

Multiple Components with same state name, how to change one state of one component without affecting the others?

In my react native program I have component (Bell) that is called for every item/list called from an API. The bell is used to make a notification (and cancel it)on press and I made a state (isBellActive) to determine if the Bell has been toggled on or not. If it is, isBellActive is true and false otherwise. Im storing the data via asyncStorage.
The problem im having is that if I change the state of one bell(on one item/list) and then close the app and relaunch, that state change from that bell will affect all the other bell component's isBellActive state. How to make it so the state (even though they all share the same name) are kept for one specific item/list
Bell Component Class
export default class Bell extends React.Component {
constructor(props) {
super(props);
this.state = {
isBellActive: null,
};
}
componentDidMount = ()=>{
AsyncStorage.getItem('isBellActive').then(value => this.setState({ isBellActive: JSON.parse(value) }));
}
setTrue(){
AsyncStorage.setItem('isBellActive', JSON.stringify(true)).then(() => {
this.setState({ isBellActive: true});
});
}
setFalse(){
AsyncStorage.setItem('isBellActive', JSON.stringify(false)).then(() => {
this.setState({ isBellActive: false});
});
}
render() {
return (
<Ionicons
name={this.state.isBellActive? "md-notifications":"md-notifications-off"}
color={"white"}
size={30}
style={styles.NotifIcon}
onPress={() => {
Vibration.vibrate()
if(this.state.isBellActive == false){
PushNotificationIOS.scheduleLocalNotification({
userInfo:{
ID: this.state.ID
},
alertTitle: "Launching Soon:",
alertBody: this.state.alertBody,
fireDate: this.state.fireDate // in 30 mins
});
this.setTrue()
this.setState({Key:true})
}
else if(this.state.isBellActive != false){
PushNotificationIOS.cancelLocalNotifications({ID:this.state.ID});
this.setFalse()
}
}
}
}}
/>
);
}
}
Class that calls Component
export default class LaunchingScreen extends React.Component{
let launches = this.state.dataSource.map((item, key) => {
..
<View>
..
<Bell />
..
</View>
..
}
}
UPDATE
This is in class that calls components, it gets a JSON that has all the info:
componentDidMount(){
return fetch("https://launchlibrary.net/1.4/launch/next/20")
.then(response => response.json())
.then(responseJson => {
this.setState({
isLoading: false,
dataSource: responseJson.launches
});
})
.catch(error => {
console.log(error);
});
}
As per the response from the API shared by you, it looks like that id property is unique and you can use that property to uniquely define key for Bell component and used that key to store/retrieve data from AsyncStorage. Please consider following code snippets
Change LaunchingScreen to add key={item.id}
export default class LaunchingScreen extends React.Component{
let launches = this.state.dataSource.map((item, key) => {
..
<View>
..
<Bell key={item.id}/>
..
</View>
..
}
}
Now in the Bell component, use the key property to access data from AsyncStorage
export default class Bell extends React.Component {
constructor(props) {
super(props);
this.state = {
isBellActive: null
};
this.accessKey = `${props.key}-isBellActive`;
}
componentDidMount = ()=>{
AsyncStorage.getItem(this.accessKey).then(value => this.setState({ isBellActive: JSON.parse(value) }));
}
setTrue(){
AsyncStorage.setItem(this.accessKey, JSON.stringify(true)).then(() => {
this.setState({ isBellActive: true});
});
}
setFalse(){
AsyncStorage.setItem(this.accessKey, JSON.stringify(false)).then(() => {
this.setState({ isBellActive: false});
});
}
render() {
return (
<Ionicons
name={this.state.isBellActive? "md-notifications":"md-notifications-off"}
color={"white"}
size={30}
style={styles.NotifIcon}
onPress={() => {
Vibration.vibrate()
if(this.state.isBellActive == false){
PushNotificationIOS.scheduleLocalNotification({
userInfo:{
ID: this.state.ID
},
alertTitle: "Launching Soon:",
alertBody: this.state.alertBody,
fireDate: this.state.fireDate // in 30 mins
});
this.setTrue()
this.setState({Key:true})
}
else if(this.state.isBellActive != false){
PushNotificationIOS.cancelLocalNotifications({ID:this.state.ID});
this.setFalse()
}
}
}
}}
/>
);
}
}
In the above component, we are first deciding the access key using this.accessKey = ${props.key}-isBellActive; and then using this.accessKey instead of isBellActive.
Hope this will help!!!
If you are using the same component multiple times and want to have a different state for each of them then you have to set key property for each component.
Keys help React identify which items have changed, are added, or are removed.
For this kind of situation where you want to use the same component multiple times, you have to set key property. Otherwise, each component will have the same state.

this.setState inside Promise cause strange behavior

Simplified issue. Calling this.setState inside a Promise, renders before ends pending Promise.
My problems are:
The this.setState is not immediatly returned
I expected it to be async, so that the pending promise will be closed first.
If something will break inside the render function, the catch inside the Promise is called.
Maybe same issue as 1) that it seems like the render is still in context of the promise in which the this.setState was called.
import dummydata_rankrequests from "../dummydata/rankrequests";
class RankRequestList extends Component {
constructor(props) {
super(props);
this.state = { loading: false, data: [], error: null };
this.makeRankRequestCall = this.makeRankRequestCall.bind(this);
this.renderItem = this.renderItem.bind(this);
}
componentDidMount() {
// WORKS AS EXPECTED
// console.log('START set');
// this.setState({ data: dummydata_rankrequests.data, loading: false });
// console.log('END set');
this.makeRankRequestCall()
.then(done => {
// NEVER HERE
console.log("done");
});
}
makeRankRequestCall() {
console.log('call makeRankRequestCall');
try {
return new Promise((resolve, reject) => {
resolve(dummydata_rankrequests);
})
.then(rankrequests => {
console.log('START makeRankRequestCall-rankrequests', rankrequests);
this.setState({ data: rankrequests.data, loading: false });
console.log('END _makeRankRequestCall-rankrequests');
return null;
})
.catch(error => {
console.log('_makeRankRequestCall-promisecatch', error);
this.setState({ error: RRError.getRRError(error), loading: false });
});
} catch (error) {
console.log('_makeRankRequestCall-catch', error);
this.setState({ error: RRError.getRRError(error), loading: false });
}
}
renderItem(data) {
const height = 200;
// Force a Unknown named module error here
return (
<View style={[styles.item, {height: height}]}>
</View>
);
}
render() {
let data = [];
if (this.state.data && this.state.data.length > 0) {
data = this.state.data.map(rr => {
return Object.assign({}, rr);
});
}
console.log('render-data', data);
return (
<View style={styles.container}>
<FlatList style={styles.listContainer1}
data={data}
renderItem={this.renderItem}
/>
</View>
);
}
}
Currrent logs shows:
render-data, []
START makeRankRequestCall-rankrequests
render-data, [...]
_makeRankRequestCall-promisecatch Error: Unknown named module...
render-data, [...]
Possible Unhandled Promise
Android Emulator
"react": "16.0.0-alpha.12",
"react-native": "0.46.4",
EDIT:
wrapping setTimeout around this.setState also works
setTimeout(() => {
this.setState({ data: respData.data, loading: false });
}, 1000);
EDIT2:
created a bug report in react-native github in parallel
https://github.com/facebook/react-native/issues/15214
Both Promise and this.setState() are asynchronous in javascript. Say, if you have the following code:
console.log(a);
networkRequest().then(result => console.log(result)); // networkRequest() is a promise
console.log(b);
The a and b will get printed first followed by the result of the network request.
Similarly, this.setState() is also asynchronous so, if you want to execute something after this.setState() is completed, you need to do it as:
this.setState({data: rankrequests.data}, () => {
// Your code that needs to run after changing state
})
React Re-renders every time this.setState() gets executed, hence you are getting your component updated before the whole promise gets resolved. This problem can be solved by making your componentDidMount() as async function and using await to resolve the promise:
async componentDidMount() {
let rankrequests;
try {
rankrequests = await this.makeRankRequestCall() // result contains your data
} catch(error) {
console.error(error);
}
this.setState({ data: rankrequests.data, loading: false }, () => {
// anything you need to run after setting state
});
}
Hope it helps.
I too am having a hard time understanding what you are attempting to do here so I took a stab at it.
Since the this.setState() method is intended to trigger a render, I would not ever call it until you are ready to render. You seem to relying heavily on the state variable being up to date and able to be used/manipulated at will. The expected behaviour here, of a this.state. variable, is to be ready at the time of render. I think you need to use another more mutable variable that isn't tied to states and renders. When you are finished, and only then, should you be rendering.
Here is your code re-worked to show this would look:
import dummydata_rankrequests from "../dummydata/rankrequests";
class RankRequestList extends Component {
constructor(props) {
super(props);
/*
Maybe here is a good place to model incoming data the first time?
Then you can use that data format throughout and remove the heavier modelling
in the render function below
if (this.state.data && this.state.data.length > 0) {
data = this.state.data.map(rr => {
return Object.assign({}, rr);
});
}
*/
this.state = {
error: null,
loading: false,
data: (dummydata_rankrequests || []),
};
//binding to 'this' context here is unnecessary
//this.makeRankRequestCall = this.makeRankRequestCall.bind(this);
//this.renderItem = this.renderItem.bind(this);
}
componentDidMount() {
// this.setState({ data: dummydata_rankrequests.data, loading: false });
//Context of 'this' is already present in this lifecycle component
this.makeRankRequestCall(this.state.data).then(returnedData => {
//This would have no reason to be HERE before, you were not returning anything to get here
//Also,
//should try not to use double quotes "" in Javascript
//Now it doesn't matter WHEN we call the render because all functionality had been returned and waited for
this.setState({ data: returnedData, loading: false });
}).catch(error => {
console.log('_makeRankRequestCall-promisecatch', error);
this.setState({ error: RRError.getRRError(error), loading: false });
});
}
//I am unsure why you need a bigger call here because the import statement reads a JSON obj in without ASync wait time
//...but just incase you need it...
async makeRankRequestCall(currentData) {
try {
return new Promise((resolve, reject) => {
resolve(dummydata_rankrequests);
}).then(rankrequests => {
return Promise.resolve(rankrequests);
}).catch(error => {
return Promise.reject(error);
});
} catch (error) {
return Promise.reject(error);
}
}
renderItem(data) {
const height = 200;
//This is usually where you would want to use your data set
return (
<View style={[styles.item, {height: height}]} />
);
/*
//Like this
return {
<View style={[styles.item, {height: height}]}>
{ data.item.somedataTitleOrSomething }
</View>
};
*/
}
render() {
let data = [];
//This modelling of data on every render will cause a huge amount of heaviness and is not scalable
//Ideally things are already modelled here and you are just using this.state.data
if (this.state.data && this.state.data.length > 0) {
data = this.state.data.map(rr => {
return Object.assign({}, rr);
});
}
console.log('render-data', data);
return (
<View style={styles.container}>
<FlatList
data={data}
style={styles.listContainer1}
renderItem={this.renderItem.bind(this)} />
{ /* Much more appropriate place to bind 'this' context than above */ }
</View>
);
}
}
The setState is indeed asynchronous. I guess makeRankRequestCall should be like this:
async makeRankRequestCall() {
console.log('call makeRankRequestCall');
try {
const rankrequests = await new Promise((resolve, reject) => {
resolve(dummydata_rankrequests);
});
console.log('START makeRankRequestCall-rankrequests', rankrequests);
this.setState({ data: rankrequests.data, loading: false });
console.log('END _makeRankRequestCall-rankrequests');
} catch(error) {
console.log('_makeRankRequestCall-catch', error);
this.setState({ error: RRError.getRRError(error), loading: false });
}
}
Secondly, promise catching an error of renderItem is perfectly fine. In JavaScript, any catch block will catch any error that is being thrown anywhere in the code. According to specs:
The throw statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.
So in order to fix it, if you expect renderItem to fail, you could do the following:
renderItem(data) {
const height = 200;
let item = 'some_default_item';
try {
// Force a Unknown named module error here
item = styles.item
} catch(err) {
console.log(err);
}
return (
<View style={[item, {height: height}]}>
</View>
);
}