React Native useEffect confusion - react-native

I'm building a project overview app and I'm using React-Native-calendar. I also created two buttons to filter the calendar. I'm fetching the data(API), I'm mapping the data to an object for "markedDates". Everything works appropriately as it should. Now the onPress of each button assigns the object to a state to filter. That works as well. What doesn't work is that those markedDates, that for sure come in correctly, are not shown when the app loads. They are shown however when I click on a button, but not on load. The rough code order:
const [meineTermine, setMeineTermine] = useState([]);
const [dates, setdates] = useState([]);
const [markedFinal, setMarkedFinal] = useState({});
useEffect(() => {
const unsubscribe = db.collection("Dates").onSnapshot(snapshot => (
setdates(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
})))
))
const filter = db.collection("Dates").where("involv", "==", auth.currentUser.displayName).onSnapshot(snapshot => (
setMeineTermine(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
})))
))
return unsubscribe && filter;
}, [])
let markedDayAll = {};
let markedDayMe = {};
{dates.map(({data: {anfang}}) => (
markedDayAll[anfang] = {
selected: true
}
))}
{meineTermine.map(({data: {anfang}}) => (
markedDayMe[anfang] = {
selected: true
}
))}
<View>
<Button onPress={setMarkedFinal(Object.assign({}, markedDayAll))}/>
<Button onPress={setMarkedFinal(Object.assign({}, markedDayMe))}/>
<CalendarList
markedDates={ markedFinal }
onDayPress={() => navigation.navigate("ViewDate")}
/>
</View>
I tried to map the data to objects in useEffect which didn't work. I also tried to have a default value in state, which also didn't work. What am I missing? In which order do I need to set the code up, since it works, just not on load. Where do I need to implement the "setMarkedFinal" so it shows on load?

Related

Expo-notifications trigger all useEffects in the application

I created the entire flow for expo-notifications, although I encounter one problem. Once I receive the notification, the UI of the specific type is re-rendered and - which is the core of the problem - all the useEffects with fetch get triggered in the application; it seems that it re-renders the entire application. Even disabling the update of the specific part of the UI (that I want to update) still causes that a notification makes the app to re-render.
I tried to find the cause of that, but no progress so far. Did anyone of you ever encountered this kind of problem? Why the app gets re-rendered entirely?
The function registerForPushNotificationsAsync is copy-pasted from their docs.
Here is my notification provider - I get notification correctly, but idk what causes the re-render and trigger all the useEffects:
const NotificationsProvider = () => {
const authenticationStatus = useSelector(authStatus);
const dispatch = useDispatch();
const [expoPushToken, setExpoPushToken] = useState("");
const [notification, setNotification] = useState<Notifications.Notification | null>(null);
useEffect(() => {
if (authenticationStatus === AUTHENTICATION_MESSAGES.AUTHENTICATION_SUCCESS) {
registerForPushNotificationsAsync()
.then((token) => setExpoPushToken(token))
.catch((error) => console.error(error));
const subscription = Notifications.addNotificationReceivedListener((receivedNotification) => {
setNotification(receivedNotification);
const { id, title } = receivedNotification.request.content.data;
console.log(receivedNotification.request.content.data);
dispatch(
addAsync(
[
{
id: id,
title: title,
},
],
1 * 1000
)
);
});
APP.tsx
const App = () => {
const [fontsLoaded] = useFonts({
Roboto_400Regular,
Roboto_500Medium,
});
return fontsLoaded ? (
<Provider store={store}>
<PaperProvider theme={theme}>
<NotificationsProvider />
</PaperProvider>
</Provider>
) : (
<AppLoading />
);
};

Flatlist inside tab navigator is scrolling to top on state change in react native

Here you can see the gif
Here is my whole Navigator functional component. I'm trying to implement two tabs using Tab Navigator. One to display the cryptos and the other to display the forex data.
The problem is, when I try to load more data on reaching the flatlist's end, the flatlist is scrolling to the top since I'm making a state change [page+1].
const Navigator = () => {
const Tab = createMaterialTopTabNavigator();
const renderItems = ({ item }) => (
<Text>{item.name}<Text>
);
const fetchMarketData = async () => {
console.log("Fetching");
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
const ids = data.map((item) => item.id);
let newData = marketData.filter((item) => !ids.includes(item.id));
setData([...data, ...newData]);
setFetching(false);
} else {
setFetching(false);
Alert.alert(marketData, "Sorry for the inconvenience");
}
};
useEffect(() => {
setFetching(true);
const data = async () => {
await fetchMarketData();
};
}, [page]);
const handleLoadMore = async () => {
setFetching(true);
setPage((page) => page + 1);
};
const ScreenA = () => (
<FlatList
data={data}
style={{ backgroundColor: "white" }}
keyExtractor={(item) => item.id}
renderItem={renderItems}
scrollEventThrottle={16}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
/>
);
return (
<Tab.Navigator
screenOptions={({ route }) => screenOptions(route)}
keyboardDismissMode="auto"
>
<Tab.Screen name="Crypto" component={ScreenA} />
<Tab.Screen name="Forex" component={ScreenC} />
</Tab.Navigator>
);
};
export default Navigator;
OnEndReached is firing the handleLoadMore function and after the state change on data, the Flatlist is scrolling to the top.
1st reason
you have typo in "fetchMarketData", how exactly u get "newData" because i cant see it anywhere, maybe it should be "marketData" if not then u adding SAME old data PLUS undefined[...data, ...undefined]
2nd reason
reason why is that u call setPage(page + 1) and then "fetchMarketData" this is bad why ? because setState is async and it can be changed instant or after 5 secound, so u dont know when its changed and this is why we have hooks, you can use "useEffect" to handle this
change your "handleLoadMore" for example like this
const handleLoadMore = () => {
setPage(page + 1);
};
add useEffect hook that runs when "page" state changes
React.useEffect(() => {
(async() => {
setFetching(true)
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
setData([...data, ...marketData]);
} else {
Alert.alert(marketData, "Sorry for the inconvenience");
}
setFetching(false)
})()
}, [page])

Pass useAnimatedGestureHandler via forwardRef

I'm about to swap the old React Native Animated library with the new React Native Reanimated one to gain performance issues but I have encountered one problem I could not solve.
In all examples I found online, I saw that the GestureHandler, created with useAnimatedGestureHandler, is in the same component as the Animated.View. In reality that is sometimes not possible.
In my previous app, I just pass the GestureHandler object to the component via forwardRef but it seems React Native Reanimated is not able to do that. I don't know whether I have a syntax error or it is just a bug.
const App = () => {
const handlerRef = useAnimatedRef();
const y = useSharedValue(0);
handlerRef.current = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startY = y.value;
},
onActive: ({translationX, translationY}, ctx) => {
y.value = translationY;
},
onEnd: () => {},
});
const animatedStyles = useAnimatedStyle(() => ({transform: [{translateY: withSpring(y.value)}]}));
const UsingHandlerDirect = () => (
<PanGestureHandler onGestureEvent={handlerRef.current} >
<Animated.View style={[styles.blueBox, animatedStyles]} />
</PanGestureHandler>
)
const UsingHandlerForwardRef = forwardRef(({animatedStyles}, ref) => (
<PanGestureHandler onGestureEvent={ref?.handlerRef?.current}>
<Animated.View style={[styles.redBox, animatedStyles]} />
</PanGestureHandler>
));
return (
<SafeAreaView>
<View style={styles.container}>
<UsingHandlerForwardRef ref={handlerRef} animatedStyles={animatedStyles}/>
<UsingHandlerDirect />
</View>
</SafeAreaView>
);
}
I have saved the GestureHandler in a useAnimatedRef handlerRef.current = useAnimatedGestureHandler({}) to make things more representable. Then I pass the the ref directly into the PanGestureHandler of the UsingHandlerDirect component. The result is that when I drag the blue box the box will follow the handler. So this version works.
But as soon as I pass the handlerRef to the UsingHandlerForwardRef component non of the gesture events get fired. I would expect that when I drag the red box will also follow the handler but it doesn't
Has someone an idea whether it's me or it's a bug in the library?
Cheers
I have given up on the idea to pass a ref around instead, I created a hook that connects both components with each other via context.
I created a simple hook
import { useSharedValue } from 'react-native-reanimated';
const useAppState = () => {
const sharedXValue = useSharedValue(0);
return {
sharedXValue,
};
};
export default useAppState;
that holds the shared value using useSharedValue from reanimated 2
The child component uses this value in the gestureHandler like that
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startX = sharedXValue.value;
},
onActive: (event, ctx) => {
sharedXValue.value = ctx.startX + event.translationX;
},
onEnd: (_) => {
sharedXValue.value = withSpring(0);
},
});
and the Parent just consumes the hook value
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: -sharedXValue.value,
},
],
};
});
I have created a workable Snack which contains the 2 components - a Child with a blue box and a Parent with a red box

react native - react context like icon action

I am currently storing my user using react context, each user can like as many posts as they want.
i have a parameter called isLiked in my backend that can either be true or false for each post for each user.
Here is my code:
I attempted a solution, my problem is that when i press the outlined heart to like a post it changes to a heart and a record of this like is sorted in my database but when i close the post and open it again it does not change, i need to refresh app in order for it to change.
attempted solution
Postdetailsscreen.js
const post=route.params;
const [addedToLikes, setAddedToLikes] = useState(post.isLiked);
const addToLikes = (PostId,userId) => {
setAddedToLikes(!addedToLikes);
likePost({PostId,userId});
};
<TouchableOpacity
onPress={() => {
addToLikes(post.id,user.id);
}}
>
{addedToLikes ?
<MaterialCommunityIcons
name="heart"
/>
:
<MaterialCommunityIcons
name="heart-outline"
/>}
</TouchableOpacity>
in my backend i have an isLiked parameter that if the current userId and postId are found in my likes table then isLiked is true otherwise false.
here is my backend code:-
router.get("/",
auth,
async (req, res) => {
const posts = await Post.findAll({
order: [["createdAt", "DESC"]],
include: [
{ model: User, attributes: ["id", "name", "email"] },
{ model: Post_Image, attributes: ["id", "images"] },
]})
if (!posts) return res.status(404).send();
const baseUrl = config.get("assetsBaseUrl");
const plainPosts = posts.map((x) => x.get({ plain: true }));
const resultPosts = [];
for (const post of plainPosts) {
const isLiked = post.Likes.some(x => x.userId === req.user.id);
const { Post_Images, ...postAttributes } = listing;
const IMAGES = Post_Images.map((postImage) => ({
url: `${baseUrl}${postImage.images}_full.jpg`,
thumbnailUrl: `${baseUrl}${postImage.images}_thumb.jpg`,
}));
resultPosts.push({ ...postAttributes, images: IMAGES
,isLiked
});
}
res.send(resultPosts);
});
Can someone help me with that if a user liked a post the icon stays filled even without refreshing the app?
Assuming you are getting the props from parent component.
const Heart = ({ isLiked }) => {
const [ liked, setLiked ] = useState(false);
useEffect(() => {
setLiked(isLiked)
},[isLiked])
......
}
use useEffect to make sure you update your state whenever the isLiked prop changes.
Then in your parents component.
const ListofPost = () => {
const data = fetchTheData(url);
....
return ( data.map( item => <Heart isLiked={item.isLiked} />) )
}

how to pass a response to a react-native RNPickerSelect funtional components

ok this is a very simple action that i want to do i just want to fill a RNPickerSelect with the data that i recibe from a http request , the thing is i am learning how to use react-native and i am little confused. This is the code i have :
import React,{useState} from 'react';
import RNPickerSelect from 'react-native-picker-select';
import { View, Text, Button } from 'react-native';
const getData = () => {
const xhr = new XMLHttpRequest();
xhr.open('GET','http://3.86.214.41/api/v1/web/activities');
xhr.responseType = 'json'
xhr.onload = () => {
const data = xhr.response
for (var i = 0; i < data.length; i++) {
alert(data[i].id + data[i].name);
}
}
xhr.send();
}
const Actividad = () => {
const [activityId,setActivityId] = useState('');
return (
<View>
<Text>Selecciona Actividad</Text>
<RNPickerSelect
onValueChange={activityId => setActivityId(activityId)}
items={
[ {label: 'actividad', value: '1'}]
}
/>
<Button onPress={getData} title="Get Activities" />
</View>
);
};
export default Actividad;
you can see the response i iterate just for make sure the data is there , so i want to make the list of items with that data.
from the getData funtion to the items prop inside the RNPickerSelect component hope i am clear enough so please if someone can help me i will vote your answer for thank you so much. sorry if this is to dump question but i can't find a clear and easy undertandable answer yet thanks.
You will need to add an extra state to your component,
const [items,setItems] = useState([ {label: 'actividad', value: '1'}]);
Then pass this state to your RNPickerSelect:
<RNPickerSelect
onValueChange={activityId => setActivityId(activityId)}
items={items}
/>
the rest is just change this items state.
we can create another function lets call it onEndGetData end this will receive your endpoint data and update items state
const onEndGetData = (payload) => {
setItems(payload.map((item) => ({ label: item.name, value: item.id })));
};
And now just pass this function to getData function so you can set state according
<Button onPress={() => getData(onEndGetData)} title="Get Activities" />
so in your getData you can use it once you have the response:
const getData = (onEndGetData) => {
...
xhr.onload = () => {
onEndGetData(xhr.response);
}