invalid hook call in mobx+react native - react-native

I'm new to mobx,
I was told that I can't use directly rootStore from rootStore.tsx directly, and I have to replace it with hook, so I've tried to call hook useStore from rootStore.tsx
but in this case I've got an error "invalid hook call. Hooks can be called inside of the body"
my files are:
rootStore.tsx
import { createContext, useContext } from 'react'
import { makeAutoObservable } from 'mobx'
import { AsyncTrunk } from 'mobx-sync'
import AsyncStorage from '#react-native-async-storage/async-storage'
import { DayStyle, firstDayStyle } from '../styles/markedDayStyle'
const period: Record<string, DayStyle> = {
'2022-02-16': firstDayStyle,
}
export const rootStore = makeAutoObservable({
periods: period,
})
export const trunk = new AsyncTrunk(rootStore, {
storage: AsyncStorage,
})
export const StoreContext = createContext(rootStore)
export const StoreProvider = StoreContext.Provider
export const useStore = () => useContext(StoreContext)
App.tsx
const App = observer(({}) => {
const store = useStore()
const [isStoreLoaded, setIsStoreLoaded] = useState(false)
useEffect(() => {
const rehydrate = async () => {
await trunk.init()
setIsStoreLoaded(true)
}
rehydrate().catch(() => console.log('problems with localStorage'))
}, [store])
if (!isStoreLoaded) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator size="large" />
</View>
)
} else {
return (
<StoreProvider value={store}>
<PaperProvider theme={store.isDarkMode ? darkTheme : lightTheme}>
<View style={styles.container}>
<CalendarScreen/>
</View>
</PaperProvider>
</StoreProvider>
)
}
})
CalendarScreen.tsx
const CalendarScreen = observer(({}) => {
const store = useStore()
const handleDayPress = (day: DateData) => {
setModalVisible(true)
setPressedDay(day.dateString)
}
return (
<SafeAreaView style={styles.screenContainer}>
<Calendar
onDayPress={day => {handleDayPress(day)}}
/>
<View>
<ModalConfirmDay modalVisible={modalVisible} setModalVisible={setModalVisible} pressedDay={pressedDay} />
</View>
</SafeAreaView>
)
)}
ModalConfirmDay.tsx
import { fillMarkedDays } from '../functions/fillMarkedDays'
const ModalConfirmDay = observer(({ modalVisible, setModalVisible, pressedDay }: ModalConfirmDayProps) => {
const handlePeriodStarts = () => {
fillMarkedDays(pressedDay)
setModalVisible(false)
}
return (
<View style={styles.centeredView}>
<Modal
visible={modalVisible}
>
<View style={styles.modalView}>
<TouchableOpacity onPress={() => handlePeriodStarts()}>
<Text>Period starts</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
)
})
fillMarkedDays.tsx
import { rootStore, useStore} from '../store/rootStore'
import { firstDayStyle} from '../styles/markedDayStyle'
const fillMarkedDays = (selectedDay: string) => {
const store = useStore()
if (selectedDay) {
store.periods[selectedDay] = firstDayStyle
}
}
when I try to add a new key-value (in fillMarkedDays.tsx) to store.periods I'm getting this
how can I fix this or select a better approach to call the store? Thanks everyone

By the rules of hooks you can't use hooks outside of the body of the function (component), so basically you can only use them before return statement, and also you can't use any conditions and so on. fillMarkedDays is just a function, not a component, it has no access to React context, hooks or whatever.
What you can do is first get the store with hook, then pass it as an argument into the fillMarkedDays function:
const ModalConfirmDay = observer(({ modalVisible, setModalVisible, pressedDay }: ModalConfirmDayProps) => {
const store = useStore()
const handlePeriodStarts = () => {
fillMarkedDays(store, pressedDay)
setModalVisible(false)
}
// ...
}

Related

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

Unable to store data in redux store

i am trying to build a react-native app in that user can add a routine or daily task in EditRoutine.js file and it can be seen in RoutineOverviewScreen.js and i am using redux for storing these data and using hooks for storing and fetching data.
Below is the EditRoutine.js code snippet
import React, { useState, useEffect, useCallback } from "react";
import { View, StyleSheet, Text, TextInput, ScrollView } from "react-native";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import Card from "../components/Card";
import { useSelector, useDispatch } from "react-redux";
import * as routinesActions from "../store/actions/routine";
import Routine from "../models/routine";
import HeaderButton from "../components/HeaderButton";
const EditRoutine = (props) => {
const dispatch = useDispatch();
const [title, setTitle] = useState("");
const [detail, setDetail] = useState("");
const [time, setTime] = useState("");
const submitHandler = useCallback(() => {
dispatch(routinesActions.createRoutine(title, detail, time));
props.navigation.goBack();
}, [dispatch,title, detail, time]);
useEffect(() => {
props.navigation.setParams({ submit: submitHandler });
}, [submitHandler]);
return (
<Card style={styles.container}>
<Text>Title</Text>
<TextInput
style={styles.input}
value={title}
onChangeText={(text) => setTitle(text)}
/>
<Text>Details</Text>
<TextInput
style={styles.input}
multiline
numberOfLines={4}
value={detail}
onChangeText={(text) => setDetail(text)}
/>
<Text>Time</Text>
<TextInput
style={styles.input}
value={time}
onChangeText={(text) => setTime(text)}
/>
</Card>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
padding: 10,
width: "100%",
},
input: {
paddingHorizontal: 2,
borderBottomColor: "#ccc",
borderBottomWidth: 1,
width: "100%",
marginVertical: 15,
},
});
EditRoutine.navigationOptions = (navData) => {
const submitFn = navData.navigation.getParam("submit");
return {
headerTitle: "Edit Routine",
headerTitle: "Your Routines",
headerLeft: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Menu"
iconName={
Platform.OS === "android" ? "md-arrow-back" : "ios-arrow-back"
}
onPress={() => {
navData.navigation.goBack();
}}
/>
</HeaderButtons>
),
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Save"
iconName={
Platform.OS === "android" ? "md-checkmark" : "ios-checkmark"
}
onPress={submitFn}
/>
</HeaderButtons>
),
};
};
export default EditRoutine;
and this is my RoutineOverviewScreen.js file where i am trying to show the created routine
import React from "react";
import { View, StyleSheet, Text, FlatList } from "react-native";
import Card from "../components/Card";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import HeaderButton from "../components/HeaderButton";
import { useSelector } from "react-redux";
const RoutineOverViewScreen = (props) => {
const routines = useSelector((state) => state.routines.myRoutine);
return (
<FlatList
data={routines}
keyExtractor={(item) => item.id}
renderItem={(itemData) => (
<Card>
<View>
<Text>{itemData.item.id} </Text>
</View>
<View>
<Text>{itemData.item.title} </Text>
</View>
<View>
<Text>{itemData.item.detail} </Text>
<View>
<Text>{itemData.item.time} </Text>
</View>
</View>
</Card>
)}
/>
);
};
const styles = StyleSheet.create({});
RoutineOverViewScreen.navigationOptions = (navData) => {
return {
headerTitle: "Your Routines",
headerLeft: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Menu"
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
onPress={() => {
navData.navigation.toggleDrawer();
}}
/>
</HeaderButtons>
),
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Add"
iconName={
Platform.OS === "android" ? "md-add-circle" : "ios-add-circle"
}
onPress={() => {
navData.navigation.navigate("Edit");
}}
/>
</HeaderButtons>
),
};
};
export default RoutineOverViewScreen;
Below is my action file routine.js snippet
export const CREATE_ROUTINE= 'CREATE_ROUTINE';
export const deleteRoutine = routineId => {
return { type: DELETE_ROUTINE, pid: routineId };
};
export const createRoutine = (title, detail, time) => {
return {
type: CREATE_ROUTINE,
routineData: {
title,
detail,
time
}
};
};
Below is my reducer file reducer.js snippet
import {
DELETE_ROUTINE,
CREATE_ROUTINE,
UPDATE_ROUTINE,
} from "../actions/routine";
import Routine from "../../models/routine";
const initialState = {
myRoutine: {},
id: 1,
};
export default (state = initialState, action) => {
switch (action.type) {
case CREATE_ROUTINE:
const newRoutine = new Routine(
state.id,
action.routineData.title,
action.routineData.detail,
action.routineData.time
);
return {
...state,
items: { ...state.items, [state.id]: newRoutine },
id: state.id + 1,
};
default: {
return state;
}
}
return state;
};
and this is my app.js file snippet
import React, { useState } from 'react';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { AppLoading } from 'expo';
import * as Font from 'expo-font';
import routinesReducer from './store/reducers/routine';
import AppNavigator from './navigator/RoutineNavigator';
const rootReducer = combineReducers({
routines: routinesReducer,
});
const store = createStore(rootReducer);
const fetchFonts = () => {
return Font.loadAsync({
'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'),
'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf')
});
};
export default function App() {
const [fontLoaded, setFontLoaded] = useState(false);
if (!fontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => {
setFontLoaded(true);
}}
/>
);
}
return (
<Provider store={store}>
<AppNavigator />
</Provider>
);
}
with these above code i am able to create a new routine but i am not knowing whether my input are getting stored in to the App central state because when i am trying to render those saved data i am unable to see in my RoutineOverviewScreen screen.please help me and
About Me: Govind Kumar Thakur ( iamgovindthakur )
email: iamgovindthakur#gmail.com
Thank You :)
You are trying to use the data of "myRoutine" but never save data to this property in the reducer.
I think that the issue you are having is due to the following line in your reducer:
items: { ...state.items, [state.id]: newRoutine },

React Native SectionList not reliably rendering one of its sections

I have a custom SectionList component, called EventSectionList:
import React from 'react';
import { SectionList } from 'react-native';
import { View, Text, Fab } from '../../../common/Layout';
import EventCard from '../EventCard';
import styles from './styles';
const EventSectionList = ({
sections,
refreshing,
onRefresh,
onEndReached,
onFabPress,
inExtraData,
}) => {
const renderSectionHeader = ({ section: { title } }) => (
<Text style={styles.sectionHeader}>{title}</Text>
);
const renderSectionFooter = () => <View style={styles.sectionSeparator} />;
const renderSeparator = () => <View style={styles.itemSeparator} />;
const renderFooter = () => <View style={styles.footer} />;
const renderEvent = event => {
const { item } = event;
return <EventCard event={item} />;
};
return (
<View style={styles.container}>
<SectionList
sections={sections}
refreshing={refreshing}
onRefresh={onRefresh}
onEndReachedThreshold={0}
onEndReached={onEndReached}
renderItem={renderEvent}
renderSectionHeader={renderSectionHeader}
renderSectionFooter={renderSectionFooter}
ItemSeparatorComponent={renderSeparator}
ListFooterComponent={renderFooter}
showsVerticalScrollIndicator={false}
extraData={inExtraData}
keyExtractor={(item, index) => String(index)}
/>
<Fab icon="add" style={styles.fabAdd} onPress={onFabPress} />
</View>
);
};
export default EventSectionList;
An EventSectionList is being rendered here on the EventsScreen:
import React, { useState, useEffect, Fragment } from 'react';
import { isEmpty } from 'lodash';
import { Text } from '../../../common/Layout';
import { connect } from './../../../common/Helpers';
import EventSectionList from './../../Events/EventSectionList';
import styles from './styles';
const EventsScreen = ({ services }) => {
const { eventsService } = services;
const [hostedEvents, setHostedEvents] = useState([]);
const [registeredEvents, setRegisteredEvents] = useState([]);
const [eventSections, setEventSections] = useState(null);
const [refreshing, setRefreshing] = useState(false);
const [page, setPage] = useState(1);
useEffect(() => {
if (!isEmpty(eventSections)) {
setRefreshing(false);
}
}, [eventSections]);
useEffect(() => {
if (!isEmpty(hostedEvents) || !isEmpty(registeredEvents)) {
setEventSections([
{
title: 'Upcoming Hosted Events',
data: hostedEvents,
},
{
title: 'Upcoming Registered Events',
data: registeredEvents,
},
]);
}
}, [hostedEvents, registeredEvents]);
useEffect(() => {
const hostedEventsPromise = async () => {
const { data } = await eventsService.getEvents({
page,
hosted: true,
upcoming: true,
page_size: 100,
});
if (page === 1) {
setHostedEvents(data);
} else {
setHostedEvents([...hostedEvents, ...data]);
}
};
const registeredEventsPromise = async () => {
const { data } = await eventsService.getEvents({
page,
hosted: false,
upcoming: true,
page_size: 100,
});
if (page === 1) {
setRegisteredEvents(data);
} else {
setRegisteredEvents([...registeredEvents, ...data]);
}
};
registeredEventsPromise();
hostedEventsPromise();
}, [page]);
const eventsRefresh = async () => {
if (!refreshing) {
setPage(1);
}
};
const eventsRefreshEnd = async () => {
if (!refreshing) {
setPage(page + 1);
}
};
return (
<Fragment>
{eventSections ? (
<EventSectionList
sections={eventSections}
inExtraData={registeredEvents}
refreshing={refreshing}
onRefresh={eventsRefresh}
onEndReached={eventsRefreshEnd}
onFabPress={() => {}}
/>
) : (
<Text style={styles.message}>You have no upcoming events.</Text>
)}
</Fragment>
);
};
export default connect()(EventsScreen);
Unfortunately, sometimes - the Registered Events section doesn't render, until the pull - down refresh occurs, and even then - it doesn't always render. I can see this section sort of trying to render - the EventCards flicker where they should be rendered, but they don't "stick".
For reference, here is the EventCard component:
import React, { useState } from 'react';
import { isEmpty } from 'lodash';
import moment from 'moment';
import { connect } from '../../../common/Helpers';
import { View, Text, Touchable } from '../../../common/Layout';
import { Image } from '../../../common/Image';
import styles from './styles';
const EventCard = ({ event }) => {
const dateFormat = 'MMM D';
const timeFormat = 'h:mm A';
const [startDate] = useState(
moment(event.start_date)
.format(dateFormat)
.toUpperCase()
);
const [startTime] = useState(moment(event.start_date).format(timeFormat));
const [startDateTime] = useState(`${startDate} AT ${startTime}`);
const [name] = useState(event.name);
const [address] = useState(
event.start_location && event.start_location.address
);
const [uri] = useState(event.cover_image);
return (
<Touchable>
<View style={styles.container}>
{!isEmpty(uri) ? (
<Image source={{ uri }} style={styles.detailsImage} />
) : (
<Image name="eventPlaceholder" style={styles.detailsImage} />
)}
<View style={styles.detailsContainer}>
<Text style={styles.detailsDate}>{startDateTime}</Text>
<Text style={styles.detailsName}>{name}</Text>
<Text style={styles.detailsAddress}>{address}</Text>
</View>
</View>
</Touchable>
);
};
export default connect()(EventCard);