react native - react context like icon action - react-native

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} />) )
}

Related

Why is the data in a Context not updating/completing/getting passed down

I use the following code to fetch the data for my app.
export const DataContext = () => {
const [posts, setPosts] = useState<Array<Post>>([]);
const [profiles, setProfiles] = useState<Array<User>>([]);
const [page, setPage] = useState<number>(1);
const [perPage, setPerPage] = useState<number>(10);
useEffect(() => {
async function fetchData() {
const { data: postsData } = await supabase
.from("posts")
.select("*")
.range((page - 1) * perPage, page * perPage - 1);
setPosts(postsData!.map((post) => new Post(post)));
const postUids = postsData!.map((post) => post.uid);
const { data: profilesData } = await supabase
.from("profiles")
.select("*")
.in("uid", postUids);
const profiles = profilesData!.map((userData: any) => {
const userPosts = posts.filter((post) => post.uid === userData.uid);
const user = new User({ ...userData, posts: userPosts });
return user;
});
setProfiles((prevUsers) => [...prevUsers, ...profiles]);
}
fetchData();
}, [page, perPage]);
const nextPage = () => {
setPage(page + 1);
};
const prevPage = () => {
setPage(page - 1);
};
return React.createContext({ posts, profiles, nextPage, prevPage });
};
Then, in a TabBarView I use that context to share the data between two screens.
const { posts, profiles } = useContext(DataContext());
const Home = (props: any) => (
<View style={Theme().screen}>
<HomeScreen posts={posts} users={users} />
</View>
);
const Discover = (props: any) => (
<View style={Theme().screen}>
<ExploreScreen posts={posts} users={profiles} />
</View>
);
My first issue was that the posts array in each user was always empty in the context. I fixed this temporarily by repeating the logic to add those posts in the TabBarView.
const { posts, profiles } = useContext(DataContext());
const users: Array<User> = profiles.map((user) => { ///Added
const userPosts = posts.filter((post) => post.uid === user.uid); ///Added
user.posts.push(...userPosts); ///Added
return user; ///Added
}); ///Added
const Home = (props: any) => (
<View style={Theme().screen}>
<HomeScreen posts={posts} users={users} />
</View>
);
const Discover = (props: any) => (
<View style={Theme().screen}>
<ExploreScreen posts={posts} users={profiles} />
</View>
);
Even though that data is now logging what I would expect, the data going to HomeScreen and DiscoverScreen through props is different. The posts is fine but users is just logging as [] which means the users are not added there. Why is this happening and how can I fix this?
Example data from DataContext:
Posts: [{
"caption":"Caption",
"date":"1669244422569",
"imageUrls":[
"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg"
],
"location":{
"latitude":150,
"locationInfo":"City, State",
"longitude":-150
},
"postId":"1669244407166",
"uid":"daf6b8be-7cd0-4341-89d7-07879b207087"
}]
Users: [{
"blockedUsers":[],
"displayName":"name",
"photoURL":"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg",
"uid":"daf6b8be-7cd0-4341-89d7-07879b207087",
"verified":false
}]
Example log from TabBarView:
Posts: [Post, Post, Post]
Users: [User, User, User]
What would log in HomeScreen:
Posts: [Post, Post, Post]
Users: []

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

React Native useEffect confusion

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?

remove duplicate error from JSON value React Native

I am trying to pass filtered value from JSON to the parent component, however I've tried using Set but seems the output is still the same. The component that I'm using to render the JSON is picker from native-base. I want to filter out the repeated value in my picker. Greatly appreciated if anyone can help me.
enter image description here
Here's my code.
Picker.js
const DefaultPicker = ({labelItem, pickerWidth, onHandleValue, ...rest}) => {
const context = useContext(WindowContext);
const [selectedValue, setSelectedValue] = useState('-Select-');
const [data, setData] = useState([]);
const {user, setUser} = useContext(AuthContext);
function onNewData() {
if (user) {
user.getIdToken().then((idToken) => {
Axios.get('URL_ENDPOINT', {
headers: {
Authorization: 'Bearer' + idToken,
},
})
.then(({data}) => {
setData(data.features);
// console.log(data.features);
})
.catch((error) => {
console.error(error);
});
});
}
}
useEffect(() => {
const form = onNewData(onNewData);
return form;
}, []);
return (
<PickerWrapper>
<PickerItem
width={pickerWidth}
height="60"
mode="dropdown"
selectedValue={selectedValue}
onValueChange={(itemValue, itemIndex) => {
setSelectedValue({itemValue});
}}>
{Array.from(
new Set(
data.map((value, index) => (
<PickerItem.Item
key={index}
label={value.properties[labelItem]}
value={value.properties[labelItem]}
{...rest}
/>
)),
),
)}
</PickerItem>
</PickerWrapper>
);
};
And here is my parent component
SiteData.js
const SiteData = () => {
const [values, setValues] = useState([]);
const onHandleValue = (params) => {
setValues(params);
console.log(params);
};
return (
<ScrollableView>
<DetailContainer>
<DetailWrapper>
<DetailTitle>Site Data</DetailTitle>
<DetailSubtitle marginTop="10">
Insert new data found during your audit or observation session
</DetailSubtitle>
<DetailSubcontainer>
<DefaultPicker
labelItem={'category'} <-- receive value from child
pickerWidth="100%"
onHandleValue={onHandleValue}
/>
</DetailSubcontainer>
</DetailWrapper>
</DetailContainer>
</ScrollableView>
);
};
UPDATE 1:
I'm using the filter() method so i can create a new array but it returns only one value in the picker list.
const indexData = data.filter(
({category}, index) => {
return (
data.findIndex(
(item) =>
item.category === category,
) === index
);
},
);
The output
enter image description here
I fixed my code by adding this on child component
var setObj = new Set();
var result = data.reduce((acc,item)=>{
if(!setObj.has(item.category)){
setObj.add(item.category,item)
acc.push(item)
}
return acc;
},[]);

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