Cannot update a component (`ForwardRef(BaseNavigationContainer)`) while rendering a different component (`Signinscreen`) - react-native

I am getting the following warning:
Warning: Cannot update a component (ForwardRef(BaseNavigationContainer)) while rendering a different component (Signinscreen). To locate the bad setState() call inside Signinscreen, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
Signinscreen.js
import React, { Component, useEffect } from 'react'
import { View, Text, TextInput, Image } from 'react-native'
// STYLE
import styles from "./style"
// FORMIK
import { Formik } from 'formik'
// COMPONENT
import Navigationcomponent from "../../assets/components/navigationcomponent"
// STORAGE
import AsyncStorage from '#react-native-async-storage/async-storage'
// REDUX
import { connect } from 'react-redux'
// REDUX ACTION
import { initialaccount,user_signin } from '../../actions/index'
// YUP
import * as yup from 'yup'
// IMAGE
const mainimage = require("../../assets/images/mainlogo.png")
// SCREEN
import Homescreen from "../homescreen";
const SigninSchema = yup.object().shape({
username: yup
.string()
.min(6)
.max(12)
.required('Please, provide your username!')
.nullable(),
password: yup
.string()
.min(6)
.required('Please, provide your password!')
.nullable(),
})
const Signinscreen = ({navigation, initialaccount_d, user_signin_d, user_s}) => {
const storeData = async (values) => {
try {
// await AsyncStorage.setItem('me', JSON.stringify(values))
// await initialaccount_d(true)
// console.log(values)
await user_signin_d(values)
//
// navigation.navigate("Initialhomescreen")
} catch (e) {
// saving error
}
}
const validatecheck = () => {
AsyncStorage.setItem('me', JSON.stringify(user_s.usersignin_success))
navigation.navigate("Initialhomescreen")
// console.log(user_s.usersignin_success)
// if (user_s.usersignin_success != null) {
// // console.log(user_s.usersignin_success)
// // navigation.navigate("Initialhomescreen")
// if (user_s.usersignin_success.status == 200) {
// // console.log("user_s.usersignin_success")
// await AsyncStorage.setItem('me', JSON.stringify(user_s.usersignin_success))
// navigation.navigate("Initialhomescreen")
// }
// }
}
// ONBACK PRESS
useEffect(() => {
// validatecheck()
navigation.addListener('beforeRemove', e => {
// REMOVE INPUT VALUE BEFORE LEAVE SCREEN
user_signin_d(null)
});
},[])
if (user_s.usersignin_success != null && user_s.usersignin_success.status == 200) {
// validatecheck()
return(
<View>
{validatecheck()}
<Text>Load...</Text>
</View>
)
} else {
return(
<Formik
// validationSchema={SigninSchema}
initialValues={{ username: null, password: null}}
onSubmit={values => storeData(values)}>
{({ handleChange, handleSubmit, values, errors, touched }) =>
(
<View style={styles.main}>
<View style={styles.mainconf}>
<Image style={styles.imageconf} source={mainimage}></Image>
<View style={styles.inputconfmain}>
<Text style={styles.titleconf}>
Create now, for ever...
</Text>
<TextInput
style={styles.inputconf}
placeholder="username"
placeholderTextColor="gray"
onChangeText={handleChange('username')}
value={values.username}>
</TextInput>
{touched.username && errors.username &&
<Text style={{ fontSize: 12, color: 'red', alignSelf: "center" }}>{errors.username}</Text>
}
<TextInput
style={styles.inputconf}
placeholder="password"
placeholderTextColor="gray"
secureTextEntry={true}
onChangeText={handleChange('password')}
value={values.password}>
</TextInput>
{touched.password && errors.password &&
<Text style={{ fontSize: 12, color: 'red', alignSelf: "center" }}>{errors.password}</Text>
}
<Text onPress={handleSubmit} style={styles.btnsignupconf}>
Sign in
</Text>
{user_s.usersignin_success != null ? (user_s.usersignin_success.status == 400 ? <Text style={styles.warningmsg}>{user_s.usersignin_success.message}</Text> : (null)) : null}
</View>
</View>
</View>
)}
</Formik>
)
}
}
const mapStateToProps = (state) => ({
user_s: state.user
})
const mapDispatchToProps = (dispatch) => ({
initialaccount_d: (value) => dispatch(initialaccount(value)),
user_signin_d: (value) => dispatch(user_signin(value))
})
export default connect(mapStateToProps, mapDispatchToProps)(Signinscreen)
Homescreen.js
import React, { Component, useEffect, useState } from 'react'
import { View, Text, Image, SafeAreaView, BackHandler } from 'react-native'
// COMPONENT
import Navigationcomponent from "../../assets/components/navigationcomponent";
// STORAGE
import AsyncStorage from '#react-native-async-storage/async-storage';
// STYLE
import styles from "./style"
// REDUX
import { connect } from 'react-redux'
// REDUX ACTION
import { initialaccount } from '../../actions/index'
const Homescreen = ({navigation, initialaccount_s, initialaccount_d, user_s}) => {
// useEffect(() => {
// myaccount()
// }, []);
// myaccount = async () => {
// try {
// const value = await AsyncStorage.getItem('me')
// if(value !== null) {
// // value previously stored
// console.log('Yay!! you have account.')
// // await navigation.navigate("Homescreen")
// await initialaccount_d(true)
// } else {
// console.log('Upss you have no account.')
// // await navigation.navigate("Welcomescreen")
// await initialaccount_d(false)
// }
// } catch(e) {
// // error reading value
// }
// }
// Call back function when back button is pressed
const backActionHandler = () => {
BackHandler.exitApp()
return true;
}
useEffect(() => {
console.log(user_s.usersignin_success)
// Add event listener for hardware back button press on Android
BackHandler.addEventListener("hardwareBackPress", backActionHandler);
return () =>
// clear/remove event listener
BackHandler.removeEventListener("hardwareBackPress", backActionHandler);
},[])
// const validatecheck = () => {
// console.log(user_s.usersignin_success)
// // if (user_s.usersignin_success == null || user_s.usersignin_success.status == 400) {
// // navigation.navigate("Signinscreen")
// // }
// }
clearAll = async () => {
try {
await AsyncStorage.clear()
await initialaccount_d(false)
navigation.navigate("Initialscreen")
console.log('Done. cleared')
} catch(e) {
console.log('Upss you have no account.')
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('me')
if(value !== null) {
// value previously stored
console.log(value)
} else {
console.log('Upss you have no account.')
}
} catch(e) {
// error reading value
}
}
return(
<View style={styles.main}>
<View style={styles.logoutconf}>
<Text onPress={() => clearAll()}>LOGOUT</Text>
<Text onPress={() => getData()}>cek data</Text>
</View>
<Navigationcomponent style={styles.mainconf} />
</View>
)
}
const mapStateToProps = (state) => ({
initialaccount_s: state.user,
user_s: state.user
})
const mapDispatchToProps = (dispatch) => ({
initialaccount_d: (value) => dispatch(initialaccount(value))
})
export default connect(mapStateToProps, mapDispatchToProps)(Homescreen)

I encountered the same problem and I think that passing navigation.navigate("<screen_name>");} to another screen's onPress() prop (like
onPress={() => {navigation.navigate("<screen_name>");}
or
...
const navFunction = () => {
navigation.navigate("<screen_name>");
};
return(
<TouchableOpactiy onPress={() => {navFunction();} />
...
</TouchableOpacity>
);
...
).
I solved this by not passing the navigation from the onPress prop in the homeScreen and instead using the navigation prop inherent to the otherScreen.
// The homescreen.
const App = props => {
return(
<TouchableOpactiy
onPress={() => {
props.navigation.navigate('OtherScreen');
}}
>...</TouchableOpacity>
);
};
// The other screen.
const App = ({navigation}) => {
return(
<TouchableOpacity
onPress={() => {
navigation.navigate('HomeScreen');
}}
>...</TouchableOpacity>
);
};

Related

Expo React Native: BarcodeScanner.requestPermissionsAsync() doesnt apply a permission request

I'm trying to build a Barcode scanner component and I'm using expo-barcode-scanner.
the console.log works fine and I get the ASKING text.
the dialog window doesnt show up
here is my code:
import React, { useState, useEffect } from 'react';
import { Text,SafeAreaView, View, StyleSheet, Button } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
const ScannerComponent =()=> {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const askForCameraPermission = () =>{
console.log('ASKING')
async () => {
const {status} = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
}
}
useEffect(() => {
askForCameraPermission();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
alert(`Bar code with type ${type} and data ${data} has been scanned!`);
};
if (hasPermission === null) {
return (<View style={styles.container}><Text style={styles.text}>Requesting for camera permission</Text></View>);
}
if (hasPermission === false) {
return (<View style={styles.container}><Text style={styles.text}>No access to camera</Text></View>);
}
return (
<SafeAreaView style={styles.container}>
<Text value='hello' />
<View style={styles.scanner}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
</View>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</SafeAreaView>
);
}
export default ScannerComponent

Render after fetching async data

I am fetching data in useEffect() and then modifying the object (modifying clients.unreadMessages based on what should render icon), which is later sent to the component to render. But this component does not render correctly always, the icon is sometimes missing. I think it's because data are modified after rendering.
ClientList
import Colors from '#helper/Colors';
import { useSelector } from '#redux/index';
import { HealthierLifeOption } from '#redux/types';
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import ClientItem from './ClientItem';
import {usePubNub} from "pubnub-react";
export type Client = {
id: number;
username: string;
individualPlanPaid: boolean;
healthierLifeOption?: HealthierLifeOption;
company?: {
id: number;
companyName: string;
};
mentor?: {
id: number;
username: string;
};
}
type Props = {
navigation: any;
clients: Client[];
isAdmin?: boolean;
isLoading: boolean;
onEndReached: Function;
fromAdminTab?: boolean
}
const ClientList = ({ navigation, clients, onEndReached, isLoading, isAdmin = false, fromAdminTab= false }: Props) => {
const resultCount = useSelector(state => state.user.menteesResultCount);
const [hasMoreResults, setHasMoreResults] = useState(true);
const userId = useSelector(state => state.user.id);
const pubnub = usePubNub();
useEffect(() => {
setHasMoreResults(resultCount !== clients?.length);
}, [resultCount, clients]);
useEffect(() => {
getUnreadMessagesProccess().then(r => console.log("aaaaaaaaa"));
}, []);
async function setGrant() {
return new Promise( async resolve => {
await pubnub.grant({
channels: [userId.toString() + '.*'],
ttl: 55,
read: true,
write: true,
update: true,
get: true,
}, response => resolve(response));
});
}
async function getMetadata() {
const options = {include: {customFields: true}};
return await pubnub.objects.getAllChannelMetadata(options);
}
function setChannelsAndTokens(channelMetadata, channels, tokens) {
channelMetadata.data.forEach((value, index) => {
tokens.push(value.custom.lastToken);
channels.push(value.id)
});
}
async function getUnreadedMessages(channels, tokens) {
return await pubnub.messageCounts({
channels: channels,
channelTimetokens: tokens,
});
}
async function getUnreadMessagesProccess() {
const tokens = ['1000'];
const channels = ['1234567'];
const auth = await setGrant();
const channelMetadata = await getMetadata();
const l = await setChannelsAndTokens(channelMetadata, channels, tokens);
const unread = await getUnreadedMessages(channels, tokens).then((res) => {
clients.forEach((value, index) => {
if (res.channels[value.id + '-' + userId + '-chat']) {
value.unreadMessages = res["channels"][value.id + '-' + userId + '-chat'];
} else {
value.unreadMessages = 0;
}
})
console.log(res);
});
return unread;
}
return (
<View>
<FlatList
keyExtractor={item => item.id.toString()}
data={clients}
onEndReached={() => hasMoreResults ? onEndReached() : null}
onEndReachedThreshold={0.4}
renderItem={item => (
<ClientItem
client={item.item}
isAdmin={isAdmin}
navigation={navigation}
fromAdminTab={fromAdminTab}
/>
)}
ListFooterComponent={isLoading
? () => (
<View style={{ padding: 20 }}>
<ActivityIndicator animating size="large" color={Colors.border_gray} />
</View>
)
: null
}
/>
</View>
);
}
export default ClientList;
ClientItem
import React, {useEffect, useState} from 'react';
import { Text, View, Image, TouchableOpacity } from 'react-native';
import Images from '#helper/Images';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import Colors from '#helper/Colors';
import styles from '../styles';
import ClientModal from './ClientModal/ClientModal';
import { Client } from './ClientList';
import { HealthierLifeOption } from '#redux/types';
type Props = {
client: Client;
navigation: any;
isAdmin?: boolean;
fromAdminTab?: boolean
}
const ClientItem = ({ client, navigation, isAdmin = false, fromAdminTab= false }: Props) => {
const [showModal, setShowModal] = useState(false);
const [showIcon, setShowIcon] = useState(false)
console.log(client.unreadMessages)
useEffect(() =>{
if(client.unreadMessages>0)
setShowIcon(true);
},[client.unreadMessages]);
let clientIcon = Images.icon.logoIcon;
const handleContinueButton = () => {
if(!fromAdminTab) {
navigation.navigate('ChatFromClientsList', { selectedId: client.id, showBackButton: true });
}else {
setShowModal(!showModal)
}
};
return (
<View style={styles.client_item_container}>
<TouchableOpacity style={styles.client_content_container} onPress={handleContinueButton}
>
<View style={styles.image_container}>
<Image
style={styles.client_profile_img}
source={clientIcon}
resizeMode="contain"
resizeMethod="resize"
/>
</View>
<View style={styles.title_container}>
<Text style={styles.title} >{ client.username } </Text>
</View>
<View style={styles.dot_container}>
{showIcon && <FontAwesome5
name="comment-dots"
size={20}
color={Colors.red}
/> }
</View>
<View style={styles.hamburger_container} >
<FontAwesome5
name="bars"
size={30}
color={Colors.black}
onPress={() => setShowModal(!showModal)}
/>
</View>
<View>
<ClientModal
isVisible={showModal}
onCollapse={(value: boolean) => setShowModal(value)}
closeModal={(value: boolean) => setShowModal(value)}
client={client}
navigation={navigation}
isAdmin={isAdmin}
/>
</View>
</TouchableOpacity>
</View>
);
};
export default ClientItem;
This code does not render correctly always:
{showIcon && <FontAwesome5
name="comment-dots"
size={20}
color={Colors.red}
/> }
You should not calculate the value in renderItem.
Why you don’t calc the value outside of renderItem in pass it in
useEffect(() =>{
if(client.unreadMessages>0)
setShowIcon(true);
},[client.unreadMessages]);
Do something like:
renderItem={item => (
<ClientItem
client={item.item}
isAdmin={isAdmin}
navigation={navigation}
fromAdminTab={fromAdminTab}
showIcon={item.item.unreadMessages>0}
/>
)}
By the way renderItem should not be a anonymous function.

How do I store and get the theme state using AsyncStorage

I'm tying to store the theme state in the app, so that the chosen theme will be persistent after the app is closed. I have two files, one is the App.js file which has the navigation, and the other is the DrawerContent.js file that has the content of the drawer as well as the switch for the theme. toggleTheme is passed to the DrawerContent through useContext.
These are the snippets of the App file.
import {
NavigationContainer,
DefaultTheme as NavigationDefaultTheme,
DarkTheme as NavigationDarkTheme
} from '#react-navigation/native';
import {
Provider as PaperProvider,
DefaultTheme as PaperDefaultTheme,
DarkTheme as PaperDarkTheme
} from 'react-native-paper';
import AsyncStorage from '#react-native-community/async-storage';
const THEME_KEY = 'theme_color';
export default function App() {
const [isDarkTheme, setIsDarkTheme] = useState(false);
{/* Themes */ }
const CustomDefaultTheme = {
...NavigationDefaultTheme,
...PaperDefaultTheme,
colors: {
...NavigationDefaultTheme.colors,
...PaperDefaultTheme.colors,
background: '#ffffff',
text: '#333333',
tab: '#0789DC'
}
}
const CustomDarkTheme = {
...NavigationDarkTheme,
...PaperDarkTheme,
colors: {
...NavigationDarkTheme.colors,
...PaperDarkTheme.colors,
background: '#333333',
text: '#ffffff',
tab: '#2c3e50'
}
}
const theme = isDarkTheme ? CustomDarkTheme : CustomDefaultTheme;
// AsyncSotrage
const storeTheme = async (key, isDarkTheme) => {
try {
await AsyncStorage.setItem(THEME_KEY, JSON.stringify(isDarkTheme))
setIsDarkTheme(isDarkTheme);
console.log(isDarkTheme)
} catch (error) {
alert(error);
}
}
const getTheme = async () => {
try {
const savedTheme = await AsyncStorage.getItem(THEME_KEY)
if (savedTheme !== null) {
setIsDarkTheme(JSON.parse(savedTheme));
}
} catch (error) {
alert(error);
}
}
const authContext = useMemo(() => ({
toggleTheme: () => {
setIsDarkTheme(isDarkTheme => !isDarkTheme);
// storeTheme(isDarkTheme => !isDarkTheme);
console.log(isDarkTheme);
}
}),
[]
);
useEffect(() => {
getTheme();
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem(USER_TOKEN)
if (userToken !== null) {
setUserToken(JSON.parse(userToken))
}
} catch (e) {
alert(e)
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
return (
<PaperProvider theme={theme}>
<AuthContext.Provider value={authContext}>
<NavigationContainer theme={theme}>
...
</NavigationContainer>
</AuthContext.Provider>
</PaperProvider>
)
}
This is the DrawerContent code.
import {
useTheme,
Avatar,
Title,
Caption,
Paragraph,
Drawer,
Text,
TouchableRipple,
Switch
} from 'react-native-paper';
export function DrawerContent(props) {
const paperTheme = useTheme();
const { signOut, toggleTheme } = React.useContext(AuthContext);
return (
<View style={{ flex: 1 }}>
<DrawerContentScrollView>
<View style={styles.drawerContent}>
...
<Drawer.Section title="Preferences">
<TouchableRipple onPress={() => { toggleTheme() }}>
<View style={styles.preference}>
<Text>Dark Theme</Text>
<View pointerEvents="none">
<Switch
value={paperTheme.dark}
/>
</View>
</View>
</TouchableRipple>
</Drawer.Section>
</View>
</DrawerContentScrollView>
</View>
)
}
Perhaps the effective synchronous access of AsyncStorage can solve your problem. It is recommended that you use the react-native-easy-app open source library react-native-easy-app, through which you can access any data in AsyncStorage like memory objects.
For specific usage, maybe you can refer to the StorageController file in Project Sample_Hook

Change screen without a click event using navigation stack react native

Well what I'm trying to do is when he finishes reading the qr code is to move to the next screen as soon as this event ends. I tried to do this by declaring:
const handleBarCodeScanned = ({ type, data }) => {
{this.props.navigation.navigate ('testScreen', {data1, data2})}
}
Usually, the documentation always shows accompanied by an onClick () function associated with a button.
import React, { useState, useEffect } from 'react';
import { Text, View, StyleSheet, Button, PermissionsAndroid } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import wifi from 'react-native-android-wifi';
export default function QrCodeScreen() {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
{this.props.navigation.navigate('nextScreen', { data1, data2 })}//Change screen
})}
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end',
}}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</View>
);
}
Seems like you're using functional components so there is no this context.
You forget to import and init the navigation hook
import { useNavigation } from '#react-navigation/native';
And
export default function QrCodeScreen() {
const navigation = useNavigation();
...
Then
const handleBarCodeScanned = ({ type, data }) => {
navigation.navigate('nextScreen', { data1, data2 })
})}
I managed to solve the error by passing as the navigation parameter in the function declaration.
Before
export default function QrCodeScreen() {
}
After
export default function QrCodeScreen({navigation}) {
}
Change screen
navigation.navigate('SetupConnectionScreen');

how to show Loader till API get data using redux?

I am new to react and redux. I have implemented API fetching using redux but not sure where should i put code for loader till API gives data and error message if no network or API fails.
everything is working fine I am getting data too..only thing i stick is how to show activity indicator and error message. Is there any way to do that? Thanks in advance :)
reducer.js
export const GET_REPOS = 'my-awesome-app/repos/LOAD';
export const GET_REPOS_SUCCESS = 'my-awesome-app/repos/LOAD_SUCCESS';
export const GET_REPOS_FAIL = 'my-awesome-app/repos/LOAD_FAIL';
const initialState = {
repos: [],
loading: false,
error: null
};
export default function reducer(state = initialState , action) {
switch (action.type) {
case GET_REPOS:
return { ...state, loading: true };
case GET_REPOS_SUCCESS:
return { ...state, loading: false, repos: action.payload.data };
case GET_REPOS_FAIL:
return {
...state,
loading: false,
error: 'Error while fetching repositories',
};
default:
return state;
}
}
export function listRepos(photos) {
return {
type: GET_REPOS,
payload: {
request: {
url: `photos/`
}
}
};
}
export function listThumb(albumId) {
return {
type: GET_REPOS,
payload: {
request: {
url: `photos?albumId=${albumId}`
}
}
};
}
home.js
import React, { Component } from 'react';
import { ActivityIndicator } from 'react-native-paper';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import styles from '../HomeComponent/style';
import { Ionicons } from '#expo/vector-icons';
import { listRepos } from '../../../reducer';
class Home extends Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
this.props.listRepos('');
}
FlatListItemSeparator = () => (
<View style={styles.flatListItemSeparator} />
)
renderItem = ({ item }) => (
<View style={styles.listRowContainer}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('ThumbnailViewScreen', {
albumID: item.id,
})} style={styles.listRow}>
<View style={styles.listTextNavVIew}>
<Text style={styles.albumTitle}> {item.title} </Text>
<Ionicons name='md-arrow-dropright' style={styles.detailArrow} />
</View>
</TouchableOpacity>
</View>
);
render() {
const { error, loading, products } = this.props;
if (error) {
return <Text>adadf </Text>;
}
if (loading) {
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<ActivityIndicator animating={true} size='large' />
</View>
);
}
const { repos } = this.props;
return (
<View style={styles.MainContainer} >
<FlatList
styles={styles.container}
data={repos}
renderItem={this.renderItem}
ItemSeparatorComponent={this.FlatListItemSeparator}
/>
</View>
);
}
}
const mapStateToProps = state => {
let storedRepositories = state.repos.map(repo => ({ key: repo.id, ...repo }));
return {
repos: storedRepositories
};
};
const mapDispatchToProps = {
listRepos
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
const mapStateToProps = state => {
let storedRepositories = state.repos.map(repo => ({ key: repo.id, ...repo }));
return {
repos: storedRepositories
};
};
loading may not have been passed as a prop to Home. To fix that,
add it to mapStateToProps:
return {
repos: storedRepositories,
loading: state.loading
};