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

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

Related

Async Storage Error Unhandled Promise Rejection in Switch

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 })
});
}

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.

Undefined props only in componentDidMount

In my code below you can see my component. How it is written will cause the app to crash with the error:
undefined is not an object (evaluation this.props.data.ID)
So in my componentDidMount that id variable is not receiving the props data.
However if i comment out that code in the componentDidMount the app will load fine and the props.data.ID will print out in View. Is there a reason why i can't access the props.data.ID in my componentDidMount?
Heres my code
// timeline.js
class TimelineScreen extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
const { id } = this.props.data.ID;
axios.post('/api/hometimeline', { id })
.then(res => {
this.setState({
posts: res.data
});
});
}
render() {
const { data } = this.props;
return (
<View style={s.container}>
{
data
?
<Text>{data.ID}</Text>
:
null
}
</View>
);
}
}
function mapStateToProps(state) {
const { data } = state.user;
return {
data
}
}
const connectedTimelineScreen = connect(mapStateToProps)(TimelineScreen);
export default connectedTimelineScreen;
The input of mapStateToProps is not react state, it is redux store. You shouldn't use this.setState in componentDidMount. Use redux actions and reducers to change redux store. Whenever redux store changes, it will invoke mapStateToProps and update your props
componentDidMount() {
console.log(this.props.data); // for test
const id = this.props.data.ID;
//OR
const {id} = this.props.data;
...
}

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>
);
}

When to make a Fetch call for React Native Component

I'm new to React Native and confused on how to properly utilize the provided Fetch API.
The call itself (as outlined here: http://facebook.github.io/react-native/docs/network.html) is straightforward, and I can log out a successful response, but when it comes time to render the data, it's undefined.
I would expect that I could define an empty 'movies' array, and then replace it by calling 'setState' from componentDidMount(), which would trigger a re-render. Is this assumption incorrect?
The code sample below results in the following error:
'undefined is not an object (evaluating 'allRowIDs.length')
Thanks in advance for any help!
import React, { Component } from 'react';
import { AppRegistry, ListView, Text, View } from 'react-native';
class ReactNativePlayground extends Component {
constructor() {
super();
this.state = {
movies: []
}
}
componentDidMount() {
fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
movies: responseJson.movies
})
})
.catch((error) => {
console.error(error);
});
}
render() {
return (
<View>
<Text>test</Text>
<ListView
dataSource={this.state.movies}
renderRow={(row) => <Text>{row.title}</Text>}
/>
</View>
)
}
}
AppRegistry.registerComponent('ReactNativePlayground', () => ReactNativePlayground);
That's because you need to place the data into a ListView.DataSource:
constructor (props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (a, b) => a !== b
})
this.state = {
movies: ds.cloneWithRows([])
}
}
// Inside the response JSON:
this.setState({
movies: this.state.movies.cloneWithRows(responseJson.movies)
});
The React Native ListView docs demonstrate this kind of setup. Using the datasource allows for optimisations to be made when rendering lists of data (notice the rowHasChanged function for instance - which prevents needless re-rendering of a row when the data hasn't altered.