I'm doing the notification page of my react native app. It has infinite scroll and "pull to refresh" options. Entering to the page it works, and it works also pulling to refresh.
The problem occurs when I scroll down because it seems it calls server to fetch new notifications but it doesn't concatenate to the array.
import React, { useState, useEffect, useCallback, Component } from "react";
import {
View,
Text,
FlatList,
Button,
Platform,
ActivityIndicator,
StyleSheet,
ScrollView,
RefreshControl,
SafeAreaView,
} from "react-native";
import { useSelector, useDispatch } from "react-redux";
import i18n from "i18n-js";
import Colors from "../../constants/Colors";
import { getNotificationList } from "../../utils/NotificationsUtils";
import Card from "../../components/UI/Card";
const NotificationsScreen = (props) => {
const [refreshing, setRefreshing] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(0);
const [notifications, setNotifications] = useState([]);
const [error, setError] = useState();
const dispatch = useDispatch();
const onRefresh = useCallback(async () => {
setRefreshing(true);
setNotifications([]);
setPage(0);
console.log("-- Refreshing --");
getNotifications().then(() => {
setRefreshing(false);
});
}, [dispatch, setRefreshing]);
const fetchMoreNotifications = useCallback(async () => {
const newPage = page + 7;
setPage(newPage);
console.log(
"FETCH MORE from page " + newPage + " on array of " + notifications.length
);
getNotifications().then(() => {
setIsLoading(false);
});
}, [dispatch, getNotifications]);
const getNotifications = useCallback(async () => {
setError(null);
setIsLoading(true);
try {
console.log("Get from page " + page);
// let fromRecord = (page - 1) * 7;
const retrievedNotifications = await getNotificationList(
page,
7,
true,
false
);
console.log(
"Setting " +
retrievedNotifications.response.notifications.length +
" new notifications on an already existing array of " +
notifications.length +
" elements"
);
let updatedNews = notifications.concat(
retrievedNotifications &&
retrievedNotifications.response &&
retrievedNotifications.response.notifications
);
setNotifications(updatedNews);
} catch (err) {
setError(err.message);
}
setIsLoading(false);
}, [dispatch, setIsLoading, setNotifications, setError]);
useEffect(() => {
setIsLoading(true);
getNotifications(page).then(() => {
setIsLoading(false);
});
}, [dispatch, getNotifications]);
return (
<View>
{error ? (
<View style={styles.centered}>
<Text>Error</Text>
</View>
) : refreshing ? (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colors.primary} />
</View>
) : !notifications || !notifications.length ? (
<View style={styles.centered}>
<Text>No data found</Text>
</View>
) : (
<FlatList
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
data={notifications}
keyExtractor={(notification) => notification.notificationQueueId}
onEndReached={fetchMoreNotifications}
onEndReachedThreshold={0.5}
initialNumToRender={4}
renderItem={(itemData) => (
<View
style={{
marginTop: 10,
height: 150,
width: "100%",
}}
>
<Card style={{ height: 150, backgroundColor: "white" }}>
<Text style={{ fontSize: 16, color: Colors.black }}>
{itemData.item.text}
</Text>
</Card>
</View>
)}
/>
)}
</View>
);
};
const styles = StyleSheet.create({
centered: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});
export default NotificationsScreen;
If I scroll to end it triggers 'fetchMoreNotifications' function and I get this in the console:
FETCH MORE from page 7 on an array of 0
Get from page 0
Setting 7 new notifications on an already existing array of 0 elements
FETCH MORE from page 7 on an array of 0
Get from page 0
Setting 7 new notifications on an already existing array of 0 elements
FETCH MORE from page 7 on an array of 0
Get from page 0
Setting 7 new notifications on an already existing array of 0 elements
...and so on
As you can see it says 'existing array of 0 elements' even if previously I saved notifications. Maybe it has some issue with useCallback's dependency?
Issue :
There are 2 main issues, one with page and second with notifications, due to useCallback and dependencies, useCallback function will always point to the old values which are not in dependencies until one of the dependencies for updated.
1) The solution to page issue :
Pass newPage as param to getNotifications, due to async behavior of setPage it will not get updated directly
And on the second time, to get the updated value of page you can pass page as a dependency.
2) The solution to the notification issue :
Update the notification directly from its prev state value with setState(prevState => newState).
Solution :
const fetchMoreNotifications = useCallback(async () => {
const newPage = page + 7;
setPage(newPage);
console.log(
"FETCH MORE from page " + newPage + " on array of " + notifications.length
);
getNotifications(newPage).then(() => { // <---- Pass as param
setIsLoading(false);
});
}, [page]); // <---- Add page as dependency
const getNotifications = useCallback(
async page => { // <---- Get page as a param
setError(null);
setIsLoading(true);
try {
console.log("Get from page " + page);
// let fromRecord = (page - 1) * 7;
const retrievedNotifications = await getNotificationList(
page,
7,
true,
false
);
setNotifications(prevNotification => prevNotification.concat(
retrievedNotifications &&
retrievedNotifications.response &&
retrievedNotifications.response.notifications
)); // <---- Setting up state directly from previous value, instead of getting it from clone version of use callback
} catch (err) {
console.log(err);
setError(err.message);
}
setIsLoading(false);
},
[setIsLoading, setNotifications, setError]
);
WORKING DEMO :
Check the console log for updated page value and notification will be rendered on Html it self
NOTE : Removed some of your code just to improve code readability and
debug the issue
The problem is really simple. The getNotifications function is created using useCallback and hasn't used notifications as a dependency. Now when notifications updates, the getNotications function is still referring to the old notifications values due to closure.
Also note that you call getNotifications on fetchMoreNotifications immediately after setting page state but page state too is bound by closure and will not update in the same re-render
The solution here is to use the function approach to setNotifications and use useEffect to trigge4r getNotification on page change
const fetchMoreNotifications = useCallback(async () => {
const newPage = page + 7;
setPage(newPage);
}, [dispatch, getNotifications]);
useEffect(() => {
setIsLoading(true);
getNotifications(page).then(() => {
setIsLoading(false);
});
}, [dispatch, page, getNotifications]);
const getNotifications = useCallback(async () => {
setError(null);
setIsLoading(true);
try {
console.log("Get from page " + page);
// let fromRecord = (page - 1) * 7;
const retrievedNotifications = await getNotificationList(
page,
7,
true,
false
);
setNotifications(prevNotification => prevNotification.concat(
retrievedNotifications &&
retrievedNotifications.response &&
retrievedNotifications.response.notifications
));
} catch (err) {
setError(err.message);
}
setIsLoading(false);
}, [dispatch, setIsLoading, setNotifications, setError]);
Related
I'm creating a React native app for a covid form
I'm getting the data from a Json so I've used fetch and .then to get the actually json with the question and save it on a QuestionsState, but when I try to filter the data and get the individual question to save that on a QuestionState, that is not showing on the view, but when I console.log that and press ctrl + s to save en vs code, the individual question appears
the questions code is this
import { StyleSheet, Text, View, Button } from 'react-native'
import React, {useState, useEffect} from 'react'
const url = 'https://raw.githubusercontent.com/alejoduke52/covidForm/main/covid-19.json'
const Questions = () => {
const [covidQuestions, setCovidQuestions] = useState([{}])
const [individualQuestion, setIndividualQuestion] = useState([])
const [loading, setLoading] = useState(true)
const [countId,setCountId] = useState(1)
useEffect(() => {
console.log("cargando use effect")
getData()
},[])
useEffect(() => {
console.log("cargando use effect data unica")
getQuestion(1)
},[countId])
const getData = () =>{
fetch(url)
.then(resp => resp.json())
.then(data => {
setCovidQuestions(data)
getQuestion(1)
})
}
const getQuestion = (id) => {
setIndividualQuestion(covidQuestions.filter(Question => Question.id === id))
console.log(individualQuestion)
setLoading(false)
}
const handlerNextQuestion = () => {
console.log("jasdjasjdas")
console.log(individualQuestion)
//getQuestion(countId)
setCountId(countId+1)
}
return (
<View>
{
loading ? <Text style={styles.loading}>LOADING</Text> : (
<View key={0}>
<Text>hello {individualQuestion.question}</Text>
</View>
)
//jsx
}
<Button title="Cargando s" onPress={() => handlerNextQuestion()} />
</View>
);
}
export default Questions
const styles = StyleSheet.create({
loading: {
fontSize: 40
}
})
It is very strange cause when I first render the app the state is empty but only when I press ctrl + s, it shows
individualQuestion state is an array you have to use it like
Option 1:
hello {individualQuestion[0].question}
Option 2:
setIndividualQuestion(covidQuestions.filter(Question => Question.id === id)[0])
You can use getQuestion(1, data) instead of getQuestion(1).
You can pass data to getQuestion function
const getData = () =>{
fetch(url)
.then(resp => resp.json())
.then(data => {
setCovidQuestions(data)
getQuestion(1, data)
})
}
const getQuestion = (id,data) => {
setIndividualQuestion(data.filter(Question => Question.id === id))
...
...
...
}
Please check the below code It will give you full functionality:
import { StyleSheet, Text, View, Button } from 'react-native'
import React, { useState, useEffect } from 'react'
const url = 'https://raw.githubusercontent.com/alejoduke52/covidForm/main/covid-19.json'
const Questions = () => {
const [covidQuestions, setCovidQuestions] = useState([])
const [individualQuestion, setIndividualQuestion] = useState()
const [loading, setLoading] = useState(true)
const [countId, setCountId] = useState(1)
useEffect(() => {
console.log("cargando use effect")
getData()
}, [])
const getData = () => {
fetch(url)
.then(resp => resp.json())
.then(data => {
if (data.length > 0) {
setCovidQuestions(data)
console.log("Data", data)
getQuestion(1, data)
}
})
}
const getQuestion = (id, data) => {
console.log(data)
let question = data.find(Question => Question.id === id)
console.log("Question", question)
setIndividualQuestion(question)
setLoading(false)
}
const handlerNextQuestion = () => {
setCountId(countId + 1)
getQuestion(countId + 1, covidQuestions)
}
return (
<View>
{
loading ? <Text style={styles.loading}>LOADING</Text> : (
<View key={0}>
<Text>hellO</Text>
{individualQuestion != undefined ?
<View>
<Text>{JSON.stringify(individualQuestion)}</Text>
<Text>Question: {individualQuestion.question}</Text>
</View> : null}
</View>
)
}
<Button title="next" onPress={() => handlerNextQuestion()} />
</View>
);
}
export default Questions
const styles = StyleSheet.create({
loading: {
fontSize: 40
}
})
Hope You will like it! :)
Here there are two problem which I see
First problem is you are trying to access covidQuestions immediately after setCovidQuestions inside getQuestion(1) function, since setting state in react is always an asynchronous event and takes some time to update the state value. So basically you should not call getQuestion(1) immediately after setCovidQuestions .
Second, problem I see with the filter you are using, since filter always return an array of filtered questions, even if there is one question, so you can never access individualQuestion like {individualQuestion.question}, since its an array.
My solution to the first problem would be to remove getQuestion(1) after setCovidQuestions inside fetch API call and instead you can actually add covidQuestions as dependency to your useEffect which runs on change of countId like below
JSX
useEffect(() => {
console.log("cargando use effect data unica")
getQuestion(1)
},[countId, covidQuestions])
And for your second problem related to filter mechanism, you can do something like this, while rendering
JSX
<View key={0}>
<Text>hello {individualQuestion[0].question}</Text>
</View>
I want to save the progress in MongoDB and then display the progress. The progress value is getting calculated and updated. But when I do page refresh the progress value becomes 0 and in between moves to the next page.
const Quiz = (props) => {
const {progress: lastProgress, userId} = props;
const [progress, setProgress] = useState(lastProgress || 0);
const dispatch = useDispatch();
const history = useHistory();
const classes = useStyles();
useEffect(() => {
async function fetchProgress(){
try{
const progress = await api.get(paths.FETCH_USER);
const newprogress = progress.progress;
console.log(newprogress + "alpha");
if (newprogress <= 100) {
setProgress(Number(newprogress));
}}
catch(error){
console.log(error);
}
};
fetchProgress();
}, []);
useEffect(() => {
async function updateProgress() {
try {
await api.patch(paths.UPDATE_USER, JSON.stringify({progress}))
console.log(progress + "valueof");
}
catch(error){
console.log("error", error);
}
};
updateProgress();
}, [progress]);
function handleSubmit(event) {
event.preventDefault();
const valid = questions.some((q) => !q.value);
console.log(valid + "questionsalpha");
if (!valid) {
dispatch(postAssessment({ responses: questions, id: assessment.id }, history));
}
setCurrentQuestion(0);
setChecked((previousState) => !previousState);
setTimeout(() => {
setChecked(previousState => ({
afterPreviousChange: previousState.previousChange
}))
}, 1000);
setProgress((prevProgress)=> prevProgress + 10);
}
return (
<Grid item md={4} >
<Fragment>
<Grid item xs={12} md={6} sm={6}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" orientation="vertical" value={progress} />
</Box>
</Box>
</Grid>
</Fragment>
</Grid>
)
);
I am able to calculate the value of progress and the value is stored but on page refresh the progress value changes to 0. Do not know what mistake I am making.
Any help will be appreciated.
I think all you need is to persist the progress state to longterm storage so it's accessible after a page reload. Use localStorage to persist the progress state when it updates. Lazy initialize the progress state from localStorage. This avoids any issue of accidentally "resetting" the progress back to 0 via the PATCH request.
Example:
const Quiz = (props) => {
...
const [progress, setProgress] = useState(() => {
return Number(JSON.parse(localStorage.getItem("progress")) ?? 0);
});
...
useEffect(() => {
async function fetchProgress() {
try {
const { progress } = await api.get(paths.FETCH_USER);
console.log(progress + "alpha");
if (progress <= 100) {
setProgress(Number(progress));
}
} catch(error) {
console.log(error);
}
};
fetchProgress();
}, []);
useEffect(() => {
async function updateProgress() {
try {
await api.patch(paths.UPDATE_USER, JSON.stringify({ progress }))
console.log(progress + "valueof");
} catch(error){
console.log("error", error);
}
};
updateProgress();
localStorage.setItem("progress", JSON.stringify(progress));
}, [progress]);
function handleSubmit(event) {
event.preventDefault();
...
setProgress((progress) => progress + 10);
}
...
);
I try to save the tasks in my ToDo app with AsyncStorage so that they can be retrieved after an app restart.
So far I have managed to save the tasks. However, an empty array is always saved in the first run. If I want to create a new task, it only saves it the second time I click the button. Logical if the whole thing runs asynchronously. I just can't figure out where my fault is. I would be very happy to receive help and tips.
Here you can see the empty array when creating the first task:
Reactotron Empty Array
And here you can see the first value get's saved after i created the second task:
Reactotron AsyncStorage after second click
First Part:
if (__DEV__) {
import('./ReactotronConfig').then(() => console.log('Reactotron Configured'));
}
import Reactotron, { asyncStorage } from 'reactotron-react-native';
import React, { useState, useEffect } from 'react';
import {
Keyboard,
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
ScrollView,
Image,
SafeAreaView,
} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Task from './components/Task';
export default function App() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('TASKS');
const jsonValue2 = JSON.parse(jsonValue);
if (jsonValue2 !== null) {
setTaskItems(jsonValue2);
}
} catch (e) {
alert(e);
}
};
const storeData = async () => {
await AsyncStorage.clear();
try {
const jsonValue = await AsyncStorage.setItem(
'TASKS',
JSON.stringify(taskItems)
);
return jsonValue;
Reactotron.log(jsonValue);
} catch (e) {
alert(e);
}
};
useEffect(() => {
getData();
}, []);
const handleAddTask = () => {
storeData();
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
setTask(null);
};
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
};
const bearyDustLogo = require('./assets/bearydust-logo-bear-with-text.png');
Second Part:
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.scrollView}>
{/* Aufgaben für heute */}
<View style={styles.tasksWrapper}>
<View style={styles.headerWrapper}>
<Text style={styles.sectionTitle}>StandUp Aufgaben</Text>
<Image style={styles.tinyLogo} source={bearyDustLogo}></Image>
</View>
<View style={styles.items}>
{/* Aufgabenbereich */}
{taskItems.map((item, index) => {
return (
<TouchableOpacity
key={index}
onPress={() => completeTask(index)}
>
<Task text={item} />
</TouchableOpacity>
);
})}
</View>
</View>
</ScrollView>
{/* Aufgabe erstellen */}
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.writeTaskWrapper}
>
<TextInput
style={styles.input}
placeholder={'Ey! Lass was machen'}
value={task}
onChangeText={(text) => setTask(text)}
/>
<TouchableOpacity
onPress={() => {
handleAddTask();
}}
>
<View style={styles.addWrapper}>
<Text style={styles.addText}>+</Text>
</View>
</TouchableOpacity>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
Looking at the code, it's first saving and then it's updating the array, so you will always be one step behind on your storage:
const handleAddTask = () => {
storeData(); // here you are handling the save
Keyboard.dismiss();
setTaskItems([...taskItems, task]); // but you update the values only here
setTask(null);
};
To keep your code simple I would suggest you to save each time you have an update on your taskItems but keep in mind you don't need to update the first time you load from storage, so something like this should work:
export default function App() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const [loading, setLoading] = useState(true);
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('TASKS');
const jsonValue2 = JSON.parse(jsonValue);
if (jsonValue2 !== null) {
setTaskItems(jsonValue2);
}
} catch (e) {
alert(e);
} finally {
setLoading(false)
}
};
const storeData = async () => {
// commenting this line as you don't need to clean the storage each time you write something on it, as you'll just override the existing key 'TASKS'
// await AsyncStorage.clear();
// here you validate if the data was loaded already before starting watching for changes on the list
if (!loading) {
try {
const jsonValue = await AsyncStorage.setItem(
'TASKS',
JSON.stringify(taskItems)
);
return jsonValue;
Reactotron.log(jsonValue);
} catch (e) {
alert(e);
}
}
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
storeData()
}, [taskItems])
const handleAddTask = () => {
// won't need this line anymore as now everything you update your list it will be saved.
// storeData();
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
setTask(null);
};
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
// this will trigger a save as well
setTaskItems(itemsCopy);
};
const bearyDustLogo = require('./assets/bearydust-logo-bear-with-text.png');
This way you will always save any updates on the task list and will prevent to save then as empty when first rendering your component.
Success on your project.
I am working of a Guessing Game for 'React Native' where the user enters a number and the phone tries to guess it. Each time the phone generates a guess the user can click Greater/Lower. When the user entered number and the computer made guess equal each other we are taken to the game over screen.
The game over screen is not rendering. The logic to render the game over screen is placed inside of a useEffect()
Problem
useEffect is only fired once during the mounting phase and never again?
const { userSelectedNumber, onGameOver } = props;
useEffect(() => {
console.log(currentGuess, userSelectedNumber);
if (currentGuess === userSelectedNumber) {
onGameOver(rounds);
}
}, [userSelectedNumber, onGameOver]);*emphasized text*
(./screens/GameScreen.js)
We should exit the GameScreen when currentGuess === userSelectedNumber but this code is only run once.
Full code for GameScreen below:
import React, { useState, useRef, useEffect } from "react";
import { View, StyleSheet, Button, Text, Alert } from "react-native";
import NumberContainer from "../components/NumberContainer";
import Card from "../components/Card";
const randNumberGeneratorBetween = (min, max, exclude) => {
min = Math.ceil(min);
max = Math.floor(max);
const randNum = Math.floor(Math.random() * (max - min)) + min;
if (randNum === exclude) {
return randNumberGeneratorBetween(1, 100, exclude);
} else {
return randNum;
}
};
const GameScreen = props => {
const [currentGuess, setCurrentGuess] = useState(
randNumberGeneratorBetween(1, 100, props.userSelectedNumber)
);
const [rounds, setRounds] = useState(0);
const currentLow = useRef(1);
const currentHigh = useRef(100);
const { userSelectedNumber, onGameOver } = props;
useEffect(() => {
console.log(currentGuess, userSelectedNumber);
if (currentGuess === userSelectedNumber) {
onGameOver(rounds);
}
}, [userSelectedNumber, onGameOver]);
const nextGuessHandler = direction => {
if (
(direction === "lower" && currentGuess < props.userSelectedNumber) ||
(direction === "greater" && currentGuess > props.userSelectedNumber)
) {
Alert.alert("Don't Lie", "You know this is wrong", [
{ text: "Sorry", style: "cancel" }
]);
}
if (direction === "lower") {
currentHigh.current = currentGuess;
} else {
currentLow.current = currentGuess;
}
const nextNumber = randNumberGeneratorBetween(
currentLow.current,
currentHigh.current,
currentGuess
);
console.log('nextNumber',nextNumber);
setCurrentGuess(nextNumber);
setRounds(currRounds => currRounds + 1);
console.log('currRound',rounds);
};
return (
<View style={styles.screen}>
<Text>Opponents Guess</Text>
<NumberContainer>{currentGuess}</NumberContainer>
<Card style={styles.buttonContainer}>
<Button
title="Lower"
onPress={nextGuessHandler.bind(this, "lower")}
></Button>
<Button
title="Greater"
onPress={nextGuessHandler.bind(this, "greater")}
></Button>
</Card>
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
padding: 10,
alignItems: "center"
},
buttonContainer: {
flexDirection: "row",
justifyContent: "space-between",
marginTop: 20,
width: 300,
maxWidth: "80%"
}
});
export default GameScreen;
Project can be found here:
https://codesandbox.io/s/github/SMasood1/guessingGame?file=/screens/GameScreen.js:852-1039
You need to add rounds and currentGuess to the dependencies array in the useEffect hook
useEffect(() => {
console.log(currentGuess, userSelectedNumber);
if (currentGuess === userSelectedNumber) {
onGameOver(rounds);
}
}, [userSelectedNumber, onGameOver,currentGuess,rounds]);
Also it is considered a anti-pattern to use props to initialize a state, so I would recommend to add an other useEffect hook:
useEffect(()=>{
setCurrentGuess(randNumberGeneratorBetween(1, 100, props.userSelectedNumber))
},[props.userSelectedNumber]);
The useEffect hook causes the component to update whenever any of the values of the dependency array changes. Make sure the values you use to trigger that hook are in fact changing.
You can elegantly trigger useEffect by supplying a timestamp on you navigation.navigate call
e.g.
// someComponent.tsx
navigation.navigate('Home', {
showSubscriptionModal: true
})
// HomeScreen.tsx
const showSubscriptionModal = props.route.params?.showSubscriptionModal ?? false
useEffect(() => {
if(showSubscriptionModal) setIsShowingModal(true)
},[showSubscriptionModal])
will only fire once, while
// someComponent.tsx
navigation.navigate('Home', {
showSubscriptionModal: true,
updateTs: new Date()
})
// HomeScreen.tsx
const showSubscriptionModal = props.route.params?.showSubscriptionModal ?? false
useEffect(() => {
if(props.route.params?.showSubscriptionModal) setIsShowingModal(true)
},[showSubscriptionModal, props.route.params?.updateTs])
will fire every time you re-navigate to your screen via navigation.navigate()
I want to scroll FlatList to the certain index when screen is launched and data for the list is retrieved from the server.
I have a problem of using ref inside useMemo(). I'm getting an error:
Cannot read property current of undefined.
How to fix this error? Does my approach correct?
Here is what I'm doing:
const WeeklyMeetings = props => {
const [meetings, setMeetings] = useState(null)
useEffect(() => {
AllMeeting() // getting data from the server
}, [])
const getResult = useMemo(() => {
flatListRef.current.scrollToIndex({index: 15, animated: true })
}, [meetings]);
const flatListRef = useRef();
const AllMeeting = async (id) => {
setLoading(true)
try {
const meetings = await meeting.allMeetingsAsc(id)
setMeetings(meetings)
} catch (error) {
console.log("error ", error)
}
}
return (
<View style={styles.rootContainer}>
<FlatList
ref={flatListRef}
style={styles.list}
data={meetings}
renderItem={renderItem}
onScrollToIndexFailed={()=>{}}
/>
</View>
)
}
The ref needs to be defined before using it.
Also since you want to just scroll to index when you receive meeting value, you can make use of useEffect hook.
Also note that you only want to scrollToIndex once value meetings is available and hence you can skip the initial call to useEffect by keeping track of initialRender
const WeeklyMeetings = props => {
const [meetings, setMeetings] = useState(null)
useEffect(() => {
const AllMeeting = async (id) => {
setLoading(true)
try {
const meetings = await meeting.allMeetingsAsc(id)
setMeetings(meetings)
} catch (error) {
console.log("error ", error)
}
}
AllMeeting();
}, [])
const flatListRef = useRef();
const initialRender = useRef(true)
useEffect(() => {
if(!initialRender.current) {
flatListRef.current.scrollToIndex({index: 15, animated: true })
} else {
initialRender.current = false;
}
}, [meetings])
return (
<View style={styles.rootContainer}>
<FlatList
ref={flatListRef}
style={styles.list}
data={meetings}
renderItem={renderItem}
getItemLayout={(data, index) => (
{length: 50, offset: 50 * index, index}
)}
/>
</View>
)
}
According to Documentation
You would need to implement a getItemLayout function for FlatList
since scrollToIndex Cannot scroll to locations outside the render
window without specifying the getItemLayout prop.