I have a React app working in expo snack but need to add navigation. When I do, I am getting _this.setState is not a function. Any help is greatly appreciated as this is fairly new to me. Without the navigation in place and utilizing export default class App extends Component, the app builds and runs as it should but I ma having trouble figuring out where/how to declare setState.
Here's the code:
import React, { useEffect, useState } from 'react';
import { View, ActivityIndicator, Text,SafeAreaView,FlatList,ImageBackground,Image,StyleSheet,TouchableOpacity } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MapView, { Marker } from "react-native-maps";
import axios from 'axios';
const GOOGLE_MAPS_APIKEY = "xxxxxxxx";
function HomeScreen() {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
const state = { reports: [] }
const mapMarkers = () => {
return state.reports.map((report) => <Marker
key={report.anum}
coordinate={{ latitude: report.lat, longitude: report.lon }}
title={report.name}
description={report.image}
>
</Marker >)
}
const getMovies = async () => {
fetch('https://xxxxx.cfm?d3d2fcs1d')
.then(res => res.json())
.then(data => {
this.setState({reports: data.reports}, () => {
console.log(data)
}
)})
.catch(console.error)
}
getMovies()
return (
<View style={styles.container}>
<MapView
style={{ ...StyleSheet.absoluteFillObject }}
initialRegion={{
latitude: 34.8527370,
longitude: -82.3933179,
latitudeDelta: .01,
longitudeDelta: .01
}} apikey={GOOGLE_MAPS_APIKEY} >
{mapMarkers()}
</MapView>
</View>
);
}
function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} options={{headerShown: false}}/>
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
MainContainer: {
justifyContent: 'center',
flex: 1,
paddingTop: 50,
backgroundColor: '#000',
},
logo: {
width: '90%',
height: 159,
marginTop: 4,
justifyContent: 'center',
},
bannerAd: {
width: '100%',
height: 80,
marginBottom: 12,
justifyContent: 'center',
backgroundColor: '#000',
},
imageThumbnail: {
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
});
export default App;
CodePudding user response:
I can see that maybe you confused React Functional Components and React Class Components. If you decide to declare your React component a function then you can use React Hooks like useState
. But if you rather choose Class components, then you can use this.state
or this.setState
because there are no React Hooks
To fix these mismatches, your code should look like this instead:
import React, { useEffect, useState } from 'react';
import {
View,
ActivityIndicator,
Text,
SafeAreaView,
FlatList,
ImageBackground,
Image,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MapView, { Marker } from 'react-native-maps';
import axios from 'axios';
const GOOGLE_MAPS_APIKEY = 'xxxxxxxx';
function HomeScreen() {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
// EDIT 1: Add react state hook to manage reports
const [reports, setReports] = useState([]);
// EDIT 3: only fetch movies once the screen loaded and not every time it renders
useEffect(() => {
getMovies();
}, []);
const mapMarkers = () => {
return reports.map((report) => (
<Marker
key={report.anum}
coordinate={{ latitude: report.lat, longitude: report.lon }}
title={report.name}
description={report.image}
></Marker>
));
};
const getMovies = async () => {
fetch('https://xxxxx.cfm?d3d2fcs1d')
.then((res) => res.json())
.then((data) => {
// EDIT 2: Update reports state
setReports(data.reports, () => {
console.log(data);
});
})
.catch(console.error);
};
return (
<View style={styles.container}>
<MapView
style={{ ...StyleSheet.absoluteFillObject }}
initialRegion={{
latitude: 34.852737,
longitude: -82.3933179,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
apikey={GOOGLE_MAPS_APIKEY}
>
{mapMarkers()}
</MapView>
</View>
);
}
function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} options={{ headerShown: false }} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
MainContainer: {
justifyContent: 'center',
flex: 1,
paddingTop: 50,
backgroundColor: '#000',
},
logo: {
width: '90%',
height: 159,
marginTop: 4,
justifyContent: 'center',
},
bannerAd: {
width: '100%',
height: 80,
marginBottom: 12,
justifyContent: 'center',
backgroundColor: '#000',
},
imageThumbnail: {
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
});
export default App;
EDIT 1: and EDIT 2: should do the trick.
Now to avoid a loop when your screen loads, it is always recommend not to update the state in the main render function. So I added EDIT 3: to fix that as well.
Furthermore have a look at React Hooks: https://reactjs.org/docs/hooks-state.html
CodePudding user response:
You have a functional component so should not use setState.
You have already a hook to store your data, you should update that setData and where you are trying to use state now, use the data from that hook instead. (const [data, setData] = useState([]);) Another thing, the way you have getMovies() called it will happen on each render. Generally for async data fetching you want to use a useEffect hook. You would want this to happen on mount, and you should also cleanup in case the component becomes unmounted before the call finished.
useEffect(() => {
let controller = new AbortController();
(async () => {
try {
const response = await fetch('https://xxxxx.cfm?d3d2fcs1d', {
signal: controller.signal
});
const data = response.json();
setData(data.reports);
controller = null;
} catch (e) {
// Handle fetch error
}
})();
return () => controller?.abort();
}, []);