How display data with FlatList from useQuery? - react-native

Home Component :
export const Home: React.FC<Props> = (): any => {
const [recipesList, setRecipesList] = useState([] as Array<any>);
const { loading, error, data } = useQuery(GET_RECIPES);
useEffect(() => {
const getRecipes = () => {
if (error) {
return console.log(error);
}
if (loading) {
return console.log("LOADING =>", loading)
}
setRecipesList(data);
};
getRecipes();
}, [data]);
return (
<View style={styles.container}>
<Recipe recipesList={recipesList} />
</View>
);
};
Recipe Component :
export const Recipe: React.FC<Props> = (props: Props): any => {
const { recipesList } = props;
const displayRecipe = ({ item }: any) => {
console.log("RENDER ITEM")
return null;
};
return (
<View style={styles.container}>
<FlatList
data={recipesList}
extraData={recipesList}
numColumns={2}
renderItem={displayRecipe}
/>
</View>
);
};
Impossible to display data in the flatlist component, it never enters in the renderItem function no matter what I do. The recipesList is never empty when i log in.

You need to pass an array to the Flatlists data prop.
Try to change this line:
setRecipesList(data);
To this:
// I'm guessing that your query is named getRecipes
setRecipesList(data.getRecipes);

Related

How to use asyncStorage inside useEffect

I'm building a mobile game using react native and I'm trying to retrieve the best value storage on it to display on the screen. The problem is that it seems that react native is rendering the screen before it retrieves the value and then it doesn't re-render when the value is updated using setBest(), so no value is displayed.
Here is the code:
const navigation = useNavigation()
const [result, setResult] = useState('')
const [best, setBest] = useState('')
useEffect(() => {
const Storage = async (key,value) => {
await AsyncStorage.setItem(key,value)
}
const Retrieve = async (key) => {
const value = await AsyncStorage.getItem(key)
setBest(()=>value)
}
Retrieve('1').catch(console.error)
setResult(route.params.paramKey)
if(route.params.paramKey>best){
var aux = result.toString()
Storage('1',aux)
console.log(best)
}
}, [])
return (
<View style={styles.container}>
<View style={styles.textView}>
<Text style={styles.tituloText}>Melhor pontuação</Text>
<Text style={styles.tituloText}>{best}</Text>
<Text style={styles.tituloText}>Sua pontuação</Text>
<Text style={styles.resultText}>{result}</Text>
<View style={styles.viewBtn}>
<TouchableOpacity style={styles.viewBack} onPress={() => navigation.navigate('Modo1')}>
<Icon style={styles.iconBack} name="backward" />
</TouchableOpacity>
<TouchableOpacity style={styles.viewHome} onPress={() => navigation.dispatch(StackActions.popToTop)}>
<Icon style={styles.iconBack} name="home" />
</TouchableOpacity>
</View>
</View>
</View>
);
}
Thanks for the help guys! I've been struggling with this for days and any help will be appreciated!
This is how you retrieve the value..
useEffect(() => {
AsyncStorage.getItem('key').then(value => {
if (value != null) {
console.log(value);
setBest(value);
}
});
}, []);
also don't forget to add the import statement..
To set the value you must use
AsyncStorage.setItem('key', value);
You can use Async Functions inside of ~useEffect()` like this:
useEffect(() => {
(async () => {
async function getData() {
try {
const value = await AsyncStorage.getItem('myKey');
if (value !== null) {
setData(value);
}
} catch (error) {
console.log(error);
}
}
getData();
})();
}, []);
}

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.

React-Native FlatList item clickable with data to another screen

I'm trying to access a screen when you click on an item in my flatlist by passing the date I retrieved from the firebase before, I've tried several things without success so I come to you.
Basically when I click on one of the elements -> A screen with details should appear.
export default function Notifications() {
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => { * HERE I NEED TO PASS DATA AND SHOW AN ANOTHER SCREEN FOR DETAILS * }}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
EDIT: Small precision this screen is located in a Tab.Navigator
you can pass params in navigation,
export default function Notifications(props) {
const { navigation } = props
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('screenName', {
//pass params here
})
}}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
you can access params in the navigated screen by props.route.params

Calling function in main file from component

I have recently refactored my app from using Class components to Functional components and having issues with a few last things.
My Home.js looks like the following (simplified):
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
start = () => {
console.log("START");
// stuff
};
reset = () => {
console.log("RESET");
// stuff
};
stop = () => {
console.log("STOP");
// stuff
};
return (
<View style={styles.container}>
<StartStopButtons
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
My StartStopButtons has a different look, depending of the current state, it will either display Start, Stop or Reset and call the corresponding function. I am currently putting this intelligence in another file, my Button.js file.
Button.js :
//imports....
export const StartStopButtons = ({
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};
Before the refactoring, I was using this.state.start, this.state.stop to call my start and stop functions, located in Home.js.
How can I achieve that now? Is there a better approach?
You can pass the functions as props exactly like how you pass isRunning, isReset, and elapsedMilliseconds.
But please add const before function names as well.
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
const start = () => {
console.log("START");
// stuff
};
const reset = () => {
console.log("RESET");
// stuff
};
const stop = () => {
console.log("STOP");
// stuff
};
const pause = () => {};
return (
<View style={styles.container}>
<StartStopButtons
start={start}
stop={stop}
reset={reset}
pause={pause}
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
and use them like
//imports....
export const StartStopButtons = ({
start,
stop,
reset,
pause,
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};

state params undefined using react-navigation

I can navigate to a screen but params are undefined, state is just:
{
key: "id-1574950261181-7",
routeName: "Video Player"
}
Navigate from:
render() {
const {
id,
title,
description,
video,
preview,
push,
dispatch,
testLink,
navigate
} = this.props;
return (
<TouchableHighlight
style={styles.episode}
activeOpacity={1}
underlayColor="#808080"
onPress={() => {
navigate("VideoPlayer", { tester: id });
}}
>
<View style={styles.title}>
<Text style={styles.text}>{title}</Text>
</View>
</TouchableHighlight>
);
}
My VideoPlayerScreen (for brevity) gives me :
import React from "react";
import {
...
} from "react-native";
...
...
class VideoPlayerScreen extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.onShare = this.onShare.bind(this);
this.navigateScreen = this.navigateScreen.bind(this);
this.bookmarkVideo = this.bookmarkVideo.bind(this);
this.loadRecapVideo = this.loadRecapVideo.bind(this);
}
....
render() {
const {
videos,
bookmarkVideo,
navigate,
state: {
params: { id }
}
} = this.props;
console.log(this.props.navigation.state.params);
let videoProps = videos.find(obj => obj.id == id);
return (<View />)
}
}
const mapDispatchToProps = dispatch => ({
bookmarkVideo: id => dispatch(bookmarkVideo(id))
});
const mapStateToProps = state => {
return {
videos: state.tcApp.videos
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(VideoPlayerScreen);
In case you are using react-native-navigation 4x you should change the props variable
from:
const {
...
navigate,
} = this.props;
to:
const {
...
navigation,
} = this.props;
and call it like:
onPress={() => {
navigation.navigate("VideoPlayer", { tester: id });
}}
Official Doc: React Native Navigation Docs
Friendly.