Saving list with AsyncStorage - react-native

So I made a "notepad" app and I want to do so the text that the user wrote etc it should be saved, so the text doesn't get reset when user quits the app.
I'm new to react-native, after a few google searches I need AsyncStorage? to make this happen.
but really dunno on how to do it.
import React, { useState } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
TouchableWithoutFeedback,
TouchableOpacity,
Keyboard,
AsyncStorage
} from 'react-native';
import Header from './components/header';
import ListItem from './components/listitem';
import AddList from './components/addlist';
export default function App() {
const [todos, setTodos] = useState([
]);
const pressHandler = (key) => {
setTodos((prevTodos) => {
return prevTodos.filter(todo => todo.key != key);
});
}
const submitHandler = (text) => {
if(text.length > 0) {
setTodos((prevTodos) => {
return [
{ text: text, key: Math.random().toString() },
...prevTodos
];
})
}
}
return (
<TouchableWithoutFeedback onPress={() => {
Keyboard.dismiss();
}}>
<View style={styles.container}>
<Header />
<View style={styles.content}>
<AddList submitHandler={submitHandler} />
<View style={styles.todoList}>
<FlatList
data={todos}
renderItem={({ item }) => (
<ListItem item={item} pressHandler={pressHandler} />
)}
/>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}
new problem out of nowhere worked great before now broken without touching the code
const pressHandler = key =>
setTodos(prevTodos => {
const newTodos = prevTodos.filter(todo => todo.key !== key);
storeTodosInAsync(newTodos);
console.log(prevTodos);
return newTodos;
});
const submitHandler = text => {
if (text.length > 0) {
const key = Math.random().toString();
setTodos(prevTodos => {
const newTodos = [{ text, key }, ...prevTodos];
storeTodosInAsync(newTodos);
console.log(newTodos);
return newTodos;
});
}
};

You can use AsyncStorage to store and load data to/from local storage. One thing to note is data MUST be a string, so anything like an object that is not a string needs to be stringified. You can use JSON.stringify(...) to do this. And then when you get the string back you can use JSON.parse(...) to convert it back into an object.
So to convert your current code into something that automatically loads saved todos and always saves the latest, you could write this:
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
TouchableWithoutFeedback,
TouchableOpacity,
Keyboard,
AsyncStorage,
Button
} from 'react-native';
import Header from './components/header';
import ListItem from './components/listitem';
import AddList from './components/addlist';
export default function App() {
const [todos, setTodos] = useState([]);
useEffect(() => {
restoreTodosFromAsync();
}, []);
const pressHandler = key => {
console.log('Todos BEFORE delete');
console.log(todos);
const newTodos = todos.filter(todo => todo.key !== key);
console.log('Todos AFTER delete');
console.log(todos);
setTodos(newTodos);
storeTodosInAsync(newTodos);
};
const submitHandler = text => {
if (text.length === 0) return;
const key = Math.random().toString();
console.log('Todos BEFORE submit');
console.log(todos);
const newTodos = [{ text, key }, ...todos];
console.log('Todos AFTER submit');
console.log(todos);
setTodos(newTodos);
storeTodosInAsync(newTodos);
};
const asyncStorageKey = '#todos';
const storeTodosInAsync = newTodos => {
const stringifiedTodos = JSON.stringify(newTodos);
AsyncStorage.setItem(asyncStorageKey, stringifiedTodos).catch(err => {
console.warn('Error storing todos in Async');
console.warn(err);
});
};
const restoreTodosFromAsync = () => {
AsyncStorage.getItem(asyncStorageKey)
.then(stringifiedTodos => {
console.log('Restored Todos:');
console.log(stringifiedTodos);
const parsedTodos = JSON.parse(stringifiedTodos);
if (!parsedTodos || typeof parsedTodos !== 'object') return;
setTodos(parsedTodos);
})
.catch(err => {
console.warn('Error restoring todos from async');
console.warn(err);
});
};
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<Header />
<View style={styles.content}>
<AddList submitHandler={submitHandler} />
<View style={styles.todoList}>
<FlatList
data={todos}
renderItem={({ item }) => <ListItem item={item} pressHandler={pressHandler} />}
/>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}

Related

How Can I Change The Language & Navigate To Another Page Simultaneously?

Having developed a redux responsible for selecting the application's language, I am facing some difficulties in changing the language and navigating to another page within one click. Two clicks are needed: First Click: Language Update - Second Click: Navigation Process. Getting a quick hand would be highly valued. Here is the source code:
languageActionTypes.tsx
export const SET_ENGLISH = 'SET_ENGLISH';
export const SET_ARABIC = 'SET_ARABIC';
export const SET_ESPANOL = 'SET_ESPANOL';
languageActionCreator.tsx
import * as languageActionTypes from '../ActionTypes/languageActionTypes';
export const setEnglish = () => {
const action = {
type: languageActionTypes.SET_ENGLISH,
};
return action;
};
export const setArabic = () => {
const action = {
type: languageActionTypes.SET_ARABIC,
};
return action;
};
export const setEspanol = () => {
const action = {
type: languageActionTypes.SET_ESPANOL,
};
return action;
};
languageReducer.tsx
import {SET_ENGLISH, SET_ARABIC, SET_ESPANOL} from '../ActionTypes/languageActionTypes';
const initialState = {language: 'en'};
const LanguageReducer = (state = initialState, action: any) => {
switch (action.type) {
case SET_ENGLISH:
return {language: 'en'};
case SET_ARABIC:
return {language: 'ar'};
case SET_ESPANOL:
return {language: 'sp'};
default:
return state;
}
};
export default LanguageReducer;
LanguageSelector.tsx
import React from 'react';
import {SafeAreaView, ImageBackground, Image, Text, View, TouchableOpacity} from 'react-native';
import * as Animatable from 'react-native-animatable';
import {images} from '../../constants';
import styles from './LanguageSelectorStyle';
import {connect} from 'react-redux';
import {setEnglish, setArabic, setEspanol} from '../../store/Actions/languageActionCreator.tsx';
import '../../assets/i18n/i18n';
import {useTranslation} from 'react-i18next';
const LanguageSelector = ({navigation, language, makeEnglish, makeArabic, makeEspanol}: any) => {
const {i18n} = useTranslation();
const transEnglish = () => {
i18n
.changeLanguage(language)
.then(() => {
makeEnglish();
language === 'en' && navigation.navigate('RegistrationLogin');
})
.catch(err => console.log(err));
};
const transArabic = () => {
i18n
.changeLanguage(language)
.then(() => {
makeArabic();
language === 'ar' && navigation.navigate('RegistrationLogin');
})
.catch(err => console.log(err));
};
const transEspanol = () => {
i18n
.changeLanguage(language)
.then(() => {
makeEspanol();
language === 'sp' && navigation.navigate('RegistrationLogin');
})
.catch(err => console.log(err));
};
return (
<SafeAreaView>
<ImageBackground style={styles.backgroundImage} source={images.backgroundImage} resizeMode="cover">
<View style={styles.content}>
<Image style={styles.appLogo} source={images.fullDTTLogo} resizeMode="contain" />
<View style={styles.actionView}>
<Animatable.View
animation="bounceInUp"
iterationCount={1}
iterationDelay={500}
direction="alternate">
<TouchableOpacity
style={styles.actionBtn}
onPress={() => {transEnglish()}}>
<Text style={styles.actionTxtBtn}>English</Text>
</TouchableOpacity>
</Animatable.View>
<Animatable.View
animation="bounceInUp"
iterationCount={1}
iterationDelay={1000}
direction="alternate">
<TouchableOpacity
style={styles.actionBtn}
onPress={() => {transArabic()}}>
<Text style={styles.actionTxtBtn}>Arabic</Text>
</TouchableOpacity>
</Animatable.View>
<Animatable.View
animation="bounceInUp"
iterationCount={1}
iterationDelay={1500}
direction="alternate">
<TouchableOpacity
style={styles.actionBtn}
onPress={() => {transEspanol()}}>
<Text style={styles.actionTxtBtn}>Espanol</Text>
</TouchableOpacity>
</Animatable.View>
</View>
</View>
</ImageBackground>
{console.log('Language:', language)}
</SafeAreaView>
);
};
const mapStateToProps = (state: any) => {
return {
language: state.LanguageReducer.language,
};
};
const mapDispatchToProps = (dispatch: any) => ({
makeEnglish: () => dispatch(setEnglish()),
makeArabic: () => dispatch(setArabic()),
makeEspanol: () => dispatch(setEspanol()),
});
export default connect(mapStateToProps, mapDispatchToProps)(LanguageSelector);
If I understand your requirement correctly, can you expand the translation functions to include a navigate call?
Instead of
onPress={() => {transArabic()}}>
You could try
onPress={() => {
transArabic();
navigation.navigate('someScreen');
}}>

React Native useEffect API Call

I'm working on an App, which has a search function / page. Now I want some information (an array of meals, not that important) to be loaded when the page opens and then via props given to the subpages from the root search site.
import { Div, Text, Button, Input } from "react-native-magnus";
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
import searchFoodInformation from "../utils/searchFoodInformation";
import { SafeAreaView, View, FlatList, StyleSheet, StatusBar, TouchableOpacity } from 'react-native';
import BottomSheet from "react-native-gesture-bottom-sheet";
import FoodInformationModal from "./FoodInformationModal";
import { Dimensions } from 'react-native';
import mealsByDay from "../utils/mealsByDayAndUser";
const Search = ({navigation, route}) => {
const bottomSheet = useRef();
const [searchString, setSearchString] = useState("")
const [error, setError] = useState('');
const [results, setResults] = useState("")
const [clickedInfo, setClickedInfo] = useState({})
const [meals, setMeals] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
(async () => {
try {
const mealsByDayRes = await mealsByDay("10-01-2022");
if (mealsByDayRes != null) {
setMeals(mealsByDayRes);
}
} catch (error) {
console.log(error);
}
setLoading(false);
})();
}, [])
const SearchForFoodInformation = () => {
setError("")
setResults("")
searchFoodInformation(searchString).then((res) => {
try {
if (res.searchFoodInformation.error != "") {
setError(res.searchFoodInformation.error)
setResults("")
} else {
setError("")
setResults(res.searchFoodInformation.foodInformation)
}
} catch (error) {
alert(error)
}
}).catch(error => {setError(error)})
}
try {
if (route.params.params) {
setSearchString(route.params.params.data)
route.params.params = null
SearchForFoodInformation()
}
} catch (e) {
}
const Item = ({ element }) => (
<TouchableOpacity
onPress={() => {
setClickedInfo(element)
bottomSheet.current.show()
}}
>
<View style={styles.item}>
<Text>{element.name}</Text>
</View>
</TouchableOpacity>
);
const renderItem = ({ item }) => (
<Item element={item} />
);
return (
<>
{}
{!loading ? (
meals != null ? (
<Div shadow="sm" rounded="md" alignItems="center" mt={50} >
{error.length > 0 ? <Text color="black" p="md" borderStyle="solid" borderColor="red" borderWidth={3} rounded={16} m={10} bg="red"> {error} </Text> : null }
<Div alignItems="center" row>
<Input icon="ios-search" placeholder="Search" onChangeText={(text) => setSearchString(text)} value={searchString} mr="10%" ml="1%" />
<Button onPress={() => {SearchForFoodInformation()}} alignSelf="center" rounded="md" mr={"1%"} ml="auto">Suchen</Button>
</Div>
{ results.length > 0 &&
<Div pt={50}>
<FlatList
data={results}
renderItem={renderItem}
/>
</Div>
}
<BottomSheet hasDraggableIcon ref={bottomSheet} height={Dimensions.get("screen").height * 0.9}>
<FoodInformationModal id={clickedInfo.id} name={clickedInfo.name} nutrients={clickedInfo.nutrients} description={clickedInfo.description} meals={meals}/>
</BottomSheet>
</Div>
) : (
<Text>Loading...</Text>
)) : (
<Text>Loading...</Text>
)
}
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
item: {
backgroundColor: '#eee',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
}
})
export default Search
But the call for mealsByDay isn't executed and so the value is always undefined, but the site renders like the values is not null.
Thanks

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.

Error when simulate change text react native with jest

Let's say I create a login screen. Inside that screen, I import form component. And inside form component I have a text input.
Then, I want to simulate text input on change text, but always get an error
Method “simulate” is meant to be run on 1 node. 0 found instead.
This is my test file
it('calls the login submit method', () => {
const fieldPhoneNumber = wrapper
.find('Form')
.dive()
.find('TextInput[id="fieldPhoneNumber"]');
fieldPhoneNumber
.at(0)
.simulate('changeText', { target: { value: '082262366193' } });
});
This is my component login file
import React, { useState, useEffect } from 'react';
import { ScrollView, StatusBar, Platform } from 'react-native';
import Header from './components/Header';
import Form from './components/Form';
import ButtonSocialMedia from './components/ButtonSocialMedia';
function LoginScreen() {
const [phoneNumber, setPhoneNumber] = useState('');
const [focus, setFocus] = useState(false);
useEffect(() => {
}, [phoneNumber]);
const changePhoneNumber = (value) => {
setPhoneNumber(value);
};
const showAppleButton = () => {
if (Platform.OS === 'ios') {
const version = Platform.Version.split('.')[0];
if (version >= 13) {
return true;
} else {
return false;
}
} else {
return false;
}
};
const loginSubmit = () => {
console.log('Login Submit');
};
return (
<ScrollView>
<StatusBar
translucent
backgroundColor="transparent"
barStyle="light-content"
/>
<Header />
<Form
phoneNumber={phoneNumber}
changePhoneNumber={(value) => changePhoneNumber(value)}
focus={focus}
setFocus={() => setFocus(true)}
loginSubmit={() => loginSubmit()} />
<ButtonSocialMedia showAppleButton={() => showAppleButton()} />
</ScrollView>
);
}
export default LoginScreen;
This is my form component
/* eslint-disable prettier/prettier */
import React from 'react';
import { View, Text, TextInput } from 'react-native';
import styles from '../styles/StyleForm';
import color from '../../../../__global__/styles/themes/colorThemes';
import regex from '../../../../constant/regex';
import * as yup from 'yup';
import { Formik } from 'formik';
import ButtonFull from '../../../../__global__/button/buttonFull';
const regexPhoneNumber = regex.phone;
function Form(props) {
const renderFocus = () => {
if (props.focus) {
return (
<Text style={styles.textFocus}>Type your phone number</Text>
);
}
};
return (
<Formik
enableReinitialize={true}
initialValues={{
phoneNumber: props.phoneNumber,
}}
onSubmit={values => {
console.log('Login Submit');
}}
validateOnMount={true}
validationSchema={yup.object().shape({
phoneNumber: yup
.string()
.required()
.min(8)
.matches(regexPhoneNumber, 'Phone number is not valid'),
})}>
{({
// values,
handleChange,
errors,
setFieldTouched,
touched,
isValid,
handleSubmit,
}) => (
<View style={styles.form}>
<View style={styles.subContainer}>
<View style={styles.containerTitle}>
<Text style={styles.textTitle}>+62</Text>
</View>
<View style={styles.containerPhoneNumber}>
{renderFocus()}
<TextInput
id={'fieldPhoneNumber'}
onFocus={() => props.setFocus(true)}
value={props.phoneNumber}
style={styles.subContainerPhoneNumber}
placeholderStyle={styles.placeholder}
placeholder={'Type your phone number'}
onChangeText={(value) => {
handleChange('phoneNumber');
props.changePhoneNumber(value);
setFieldTouched('phoneNumber', true);
}}
keyboardType={'numeric'}
onBlur={() => setFieldTouched('phoneNumber', true)}
/>
</View>
</View>
{touched.phoneNumber && errors.phoneNumber && (
<View style={styles.containerError}>
<Text style={styles.textError}>Phone number is not valid</Text>
</View>
)}
<View style={styles.containerButton}>
<ButtonFull
isDisabled={!isValid}
id={'buttonLogin'}
color={isValid ? color.thema : color.grey}
handleSubmit={() => props.loginSubmit()}
title={'Next'}
/>
</View>
</View>
)}
</Formik>
);
}
export default Form;
The error you're facing implies that the statement const fieldPhoneNumber wrapper.find('Form').dive().find('TextInput[id="fieldPhoneNumber"]'); couldn't find the TextInput component and hence the simulate function cannot be called. Try searching for the string "TextInput" inside the wrapper, and see if that works.

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