dispatchAction error of rerender limits in react native application - reactjs

Am I doing something wrong here? I'm trying to get some data, but its returning errors in my console log, saying something about rerendering
mycompontent:
const Link = (props) => {
const { state, scrape } = useContext(ScrapeContext);
const [clipboard, setClipboard] = useState('');
const [googleClip, setGoogleClip] = useState(false);
const [googleLink, setGoogleLink] = useState('');
const urlFromClipboard = () => {
Clipboard.getString().then((content) => {
if (content.includes('https://www.google.com')){
console.log('inside includes');
setGoogleClip(true);
setClipboard(content);
setGoogleLink(`${content.split('?')[0]}?somedata`);
} else {
setGoogleClip(false);
}
});
if (googleClip) {
scrape({ googleLink });
}
}
useEffect(() => {
urlFromClipboard();
}, [clipboard]);
return (
<View style={styles.container}>
<View style={styles.inputFieldContainer}>
<TextInput
style={styles.inputField}
placeholder='Enter Google url'
autoCapitalize='none'
autoCorrect={false}
value={googleClip ? clipboard : ''}
/>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => {
urlFromClipboard();
}}
style={styles.touchSubmit}
>
<Text style={styles.touchText}>Submit</Text>
</TouchableOpacity>
</View>
{state.errorMessage ? (
<Text style={styles.errorMessage}>{state.errorMessage}</Text>
) : null}
</View>
);
}
scrape context file:
const scrape = (dispatch) => {
console.log('dispatch scrape', dispatch)
return async ({googleLink}) => {
console.log('scrape googleLink',googleLink);
try {
const response = await googleApi.post('/googleLink', {googleLink});
dispatch({ type: 'googleLink', payload: response });
navigate('NewPage');
} catch (error) {
dispatch({
type: 'googleLink_error',
payload: 'Please submit correct Google link.'
})
}
}
}
I got the data in my backend to respond correctly, but it's failing to finish what its intended, on the frontend side. The console.log('dispatch scrape', dispatch) is giving me an error in my console:
dispatch scrape function dispatchAction(fiber, queue, action) {
(function () {
if (!(numberOfReRenders < RE_RENDER_LIMIT)) {
throw ReactError(Error("Too many re-renders. React limits the number …
It doesn't produce the entire error until I hover over it... saying more inline about "to prevent infinite loop..." Here's the screenshot:

it looks like every render is setting clipboard to a new value, triggering useEffect in each new render, thus causing an infinite loop. I'm new to react native, and have been suffering a lot from infinite loop myself. Not sure it helps to change the useEffect's dependency to the function urlFromClipboard, and wrap urlFromClipboard in a callback function, of course set clipboard as dependency of the callback function:
const urlFromClipboard = useCallback(() => {
Clipboard.getString().then((content) => {
if (content.includes('https://www.google.com')) {
console.log('inside includes');
setGoogleClip(true);
setClipboard(content);
setGoogleLink(`${content.split('?')[0]}?somedata`);
} else {
setGoogleClip(false);
}
});
if (googleClip) {
scrape({ googleLink });
}
}, [clipboard])
useEffect(() => {
urlFromClipboard();
}, [urlFromClipBoard]);

Related

How do I access the data from a referenced firestore document?

My data in firestore is structured as shown in the pictures. In my code, I am using a map of the exercises array within my workout document like this:
{workout.exercises.map((exercise) => {
return (
<Text>Want exercise name here</Text>
)
I believe I have to use doc.data() from getDoc(exercise).then((doc) => to get the actual values, but I can't seem to figure out how to actually render the exercise name. It's driving me crazy, any help would be greatly appreciated!
This is the exercise object referenced in the exercises array from the picture above:
EDIT: If the exercises array only has one element, it works properly, but if it has >1, the data seems to keep rendering infinitely which results in flashing and changing text.
EDIT2: Here's my full component code in case that helps:
export function WorkoutDisplayScreen({ navigation }) {
const [workouts, setWorkouts] = React.useState([]);
const workoutsArray = [];
const [isFirstLoad, setIsFirstLoad] = useState(true);
const [exerciseData, setExerciseData] = useState({});
async function getData(exercise) {
await getDoc(exercise).then((doc) => {
setExerciseData(doc.data());
});
}
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsDocs = await getDocs(workoutsCollection);
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
};
useEffect(() => {
console.log('Rendering...');
if (isFirstLoad) {
getWorkouts();
}
setIsFirstLoad(false);
}, []);
return (
<ScreenContainer>
<View style={styles.headerrow}>
<TouchableOpacity
onPress={() => {
navigation.goBack();
}}
>
<AntDesign name="leftcircle" size={24} color="black" style={styles.headericon} />
</TouchableOpacity>
<Text style={styles.header}>My Workouts</Text>
</View>
{workouts &&
workouts.map((workout) => {
return (
<View key={workout.id}>
<View style={styles.workoutnamecontainer}>
<Text style={styles.workoutnametext}>{workout.name}</Text>
</View>
{workout.exercises.map((exercise, index) => {
getData(exercise);
return (
<View style={styles.exercisecontainer} key={index}>
<Text style={styles.exercisetext}>{exerciseData.name}</Text>
<Text style={styles.exercisetext}>{exerciseData.type}</Text>
<Text style={styles.exercisetext}>{exerciseData.weight}</Text>
<Text style={styles.exercisetext}>{exerciseData.reps}</Text>
</View>
);
})}
</View>
);
})}
</ScreenContainer>
);
}
EDIT3:
I can now log the data in the correct format with, but trying to render it on screen is still not working correctly.
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsSnapshot = await getDocs(workoutsCollection);
let workoutsDocs = workoutsSnapshot.docs;
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
workouts.map((workout) => {
workout.exercises.forEach((exercise) => {
getDoc(exercise).then((doc) => {
console.log(workout.name + ':');
console.log(doc.data());
});
});
});
};
I believe you should do all the data-fetching i.e. for all the workouts and every exercise in each workout in the useEffect itself. I have never seen code where data fetching is done inside the JSX part of the function. I am highly skeptical about it.
Also, one thing I noticed while looking through your code is that you are iterating over the snapshot instead of docs in the the snapshot inside your getWorkouts() function. It should be as follows:
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsSnapshot = await getDocs(workoutsCollection);
workoutsDocs = workoutsSnapshot.docs; // This line must be added to your code
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
};

Real-Time graph not showing in React

I want to plot my data in React-Native. I am receiving data from BLE. But I can't see the real time graph on the screen. What could be the reason? My brain stopped, so I couldn't figure out what the problem was.
Note : My data is,
hr
This is BLE characteristic value.
This is my component.js,
const width = Dimensions.get('window').width;
const height = Math.floor((Dimensions.get('window').height - 150) / 3);
let counter = 0;
const slotsPerWidth = 100;
const initialState = {
flow: hr,
isLoading: false,
};
class ChartScreen extends Component {
state = {
chartData: { ...initialState },
};
static getDerivedStateFromProps(state) {
counter++;
return {
chartData: {
flow: [hr],
},
};
}
render() {
const { flow} = this.state.chartData;
return this.state.isLoading
? <ScreenContainer>
<View style={styles.main}>
<View>
<ActivityIndicator></ActivityIndicator>
</View>
<Chart
key="flow"
data={flow}
maxValue={1900}
minValue={1750}
slotsPerWidth={slotsPerWidth}
width={width}
height={height}
marginBottom={20}
lineColor="rgba(95, 92, 1, 1)"
lineThickness={2}
chartBackground="#17204d"
horizontalGridLinesCount={5}
gridColor="rgba(65, 95, 93, .4)"
gridThickness={1}
unit="ml"
axisTooClose={10}
labelsColor="rgba(255, 255, 255, 0.8)"
labelsFontSize={12}
marginLeft={60}
labelsMarginLeft={15}
/>
</View>
</ScreenContainer>:
null
}
}
const styles = StyleSheet.create({
main: {
flex: 1,
},
});
export default ChartScreen;
This is my device screen.tsx,
const DeviceScreen = ({
route,
navigation,
}: StackScreenProps<RootStackParamList, 'Device'>) => {
// get the device object which was given through navigation params
const { device } = route.params;
const [isConnected, setIsConnected] = useState(false);
const [services, setServices] = useState<Service[]>([]);
// handle the device disconnection
const disconnectDevice = useCallback(async () => {
navigation.goBack();
const isDeviceConnected = await device.isConnected();
if (isDeviceConnected) {
await device.cancelConnection();
navigation.navigate('Home');
}
}, [device, navigation]);
useEffect(() => {
const getDeviceInformations = async () => {
// connect to the device
const connectedDevice = await device.connect();
setIsConnected(true);
// discover all device services and characteristics
const allServicesAndCharacteristics = await connectedDevice.discoverAllServicesAndCharacteristics();
// get the services only
const discoveredServices = await allServicesAndCharacteristics.services();
setServices(discoveredServices);
PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Permission Localisation Bluetooth',
message: 'Requirement for Bluetooth',
buttonNeutral: 'Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
);
};
getDeviceInformations();
device.onDisconnected(() => {
navigation.navigate('Home');
});
// give a callback to the useEffect to disconnect the device when we will leave the device screen
return () => {
disconnectDevice();
};
}, [device, disconnectDevice, navigation]);
return (
<ScrollView contentContainerStyle={styles.container}>
<TouchableOpacity style={styles.button} onPress={disconnectDevice}>
<Text style={{fontFamily:"SairaExtraCondensedThin",textAlign:"center",fontSize:15,color:"white"}}>Antrenmanı Sonlandır</Text>
</TouchableOpacity>
<View>
<View style={styles.header} >
<Text>{`Name : ${device.name}`}</Text>
<Text>{`Is connected : ${isConnected}`}</Text>
</View>
{services &&
services.map((service) => <ServiceCard service={service} />)}
</View>
<View>
<ChartScreen chartdata />
</View>
</ScrollView>
);
};
Well, at least, in component.js the isLoading state is never set to true and in screen.tsx you are non passing any data since <ChartScreen chartdata /> means <ChartScreen chartdata={true} />, so it is a boolean property. Then, in getDerivedStateFromProps you are increasing counter that is not part of the state, so, by itself, it will not trigger a new render. Check also that the value of hr is set in the proper place, looks strange to me to see it not as a part of the state too, or as a property. It needs some investigation. Also, the useEffect dependencies are objects so check this out, but consider to refactor the useEffect part, react useEffect comparing objects
Note
Seems to me that there are some misconceptions about React here cousing most of the issues.
This may not be the actual and full answer but I decided to provide my input as a starting point for further investigation and impovements.

Why does AsyncStorage save an empty array with the first click and then save the entered value only with the second button click in react native

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.

Error while trying to use ref inside useMemo()

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.

React Native Functional Component Updates State On Second Submit Only

When I console log my values, it updates only if I submit the form the second time. Why is this happening?
const Link = (props) => {
const { state, scrape } = useContext(ScrapeContext);
const [clipboard, setClipboard] = useState();
const [googleClip, setGoogleClip] = useState(false);
const [googleLink, setGoogleLink] = useState('');
const urlFromClipboard = () => {
Clipboard.getString().then((content) => {
if (content.includes('https://www.google.com')){
setGoogleClip(true);
console.log('googleLink', googleLink);
setClipboard(content);
setGoogleLink(`${content.split('?')[0]}?__a=1`);
} else {
setGoogleClip(false);
}
});
if (googleClip) {
console.log(googleLink);
scrape({ googleLink });
}
}
useEffect(() => {
urlFromClipboard();
console.log('useEffect googleLink', googleLink);
console.log('useEffect state', state);
}, [clipboard]);
return (
<View style={styles.container}>
<View style={styles.inputFieldContainer}>
<TextInput
style={styles.inputField}
placeholder='Enter Google url'
autoCapitalize='none'
autoCorrect={false}
value={googleClip ? clipboard : ''}
/>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => {
urlFromClipboard();
}}
style={styles.touchSubmit}
>
<Text style={styles.touchText}>Submit</Text>
</TouchableOpacity>
</View>
{state.errorMessage ? (
<Text style={styles.errorMessage}>{state.errorMessage}</Text>
) : null}
</View>
);
}
What I have here is a component that will grab data from a url, and on submit it will scrape certain information the app needs, however, it will only update those console log values if I press the submit button twice.
The problem is here that you do not have access the state right after you set the state. So you actually set the state, and it is there, but you only see the updated state second time you run the console log. That does not mean that you have to submit twice but when you submit for second time you run the console.log for the second time and see the value. Because setting state functions are async.In this case, you need to use callback function to actually see the updated state on the console. Hope that helps.
Like Bora Sumer mentioned, you're trying to use the state values before they are actually updated. You can do something like this:
Clipboard.getString().then((content) => {
if (content.includes('https://www.google.com')){
let link = `${content.split('?')[0]}?__a=1`
setGoogleClip(true);
console.log('googleLink', googleLink);
setClipboard(content);
setGoogleLink(link);
console.log(link);
scrape({ googleLink: link });
} else {
setGoogleClip(false);
}
});
You can use async/await each time you setState like following:
const urlFromClipboard = () => {
Clipboard.getString().then( async (content) => {
if (content.includes('https://www.google.com')){
await setGoogleClip(true);
console.log('googleLink', googleLink);
await setClipboard(content);
await setGoogleLink(`${content.split('?')[0]}?__a=1`);
} else {
await setGoogleClip(false);
}
});
if (googleClip) {
console.log(googleLink);
scrape({ googleLink });
}
}

Resources