Graphql subscriptions or pollInterval ? Differences in different cases? - reactjs

I'm currently watching a course using MERNG stack, and it's basically an app where you post stuff, and something I was trying to achieve by myself was to get posts in real time to all users, so, my first thought was, ok, let's use graphql subscriptions, and in react using apollo/client i made this code to get new posts in real time
import React, { useContext } from "react";
import { useQuery, useSubscription, gql } from "#apollo/client";
import { Grid } from "semantic-ui-react";
import { AuthContext } from "../context/auth";
import PostCard from "../components/PostCard";
import PostForm from "../components/PostForm";
import { FETCH_POSTS_QUERY } from "../util/graphql";
const Home = () => {
const { user } = useContext(AuthContext);
const { loading, data: { getPosts: posts } = {} } = useQuery(
FETCH_POSTS_QUERY,
// {
// pollInterval: 500
// }
);
const { data: { newPost: post } = {} } = useSubscription(POSTS_REAL_TIME);
return (
<Grid columns={3}>
<Grid.Row className="page-title">
<Grid.Column>
<h1>Recent Posts</h1>
</Grid.Column>
</Grid.Row>
<Grid.Row>
{user && (
<Grid.Column>
<PostForm />
</Grid.Column>
)}
{loading ? (
<h1>Loading Posts...</h1>
) : (
posts &&
posts.map(post => {
return (
<Grid.Column key={post.id} style={{ marginBottom: "20px" }}>
<PostCard post={post} />
</Grid.Column>
);
})
)}
</Grid.Row>
</Grid>
);
};
const POSTS_REAL_TIME = gql`
subscription {
newPost {
id
body
createdAt
username
likes {
username
}
likeCount
comments {
id
username
createdAt
}
commentCount
}
}
`;
export default Home;
And i was like, ok, but... i couldn't show them in the page because i didn't know how to push that object into my array of posts that comes from my useQuery at the beginning
so i look how to stay the client updated with subscriptions, and i found a post where it shows the documentation of graphql, saying that, you shouldn't update your client using subscriptions, instead, use
pollInterval
const { loading, data: { getPosts: posts } = {} } = useQuery(
FETCH_POSTS_QUERY,
{
pollInterval: 500
}
);
So, that literally means that, i spent 2 hours trying a weird code, while, with only one line of code i can do that, so, does pollInterval replace subscriptions? how should i use subscriptions ? if i use pollInterval to keep everyone updated, is that bad for performance or something ?
Thank you for your time comunnity !

I assume the issue is not that your subscription wasn't working, but you wanted to keep the old data in the view and the new post coming in. What you need to do is keep the old data in React state or using apollo state/cache. So, rather than update the view with the new post, you update the state with the latest post, forcing a rerender of your component.

I am not an expert but I think polling is not the right way to get that done, beacuse you will continuously try to refresh the data and you will be wasting resources from your server. I would use subscriptions(Apollo Server, Apollo Client(react)), or something like socket.io, both use websockets under the hood so it doesn't make much difference.

Related

How can I put dynamic category in react redux toolkit RTK?

Error is Cannot read properties of undefined (reading 'category')
I want to match my category on the product details page. But I can't able to implement it. I can't get the category I try a lot of ways. Please help me with how can I do it.
Redux says data: the actual response contents from the server. This field will be undefined until the response is received.
So how we can grab data? 🤔
Note: I use the Redux toolkit and RTK Query
I want to set my category chunk in my matchCategory state by default it is empty when the user click on the home page product button like ' View Detail' I want to match this id category and fetch all product to match the details category.
More Details
Remember I have a website My website has 20 product every product have two buttons one is add to cart and another one is View Details foe exmple You are a user you want to view product details before a product buy. Here is the main point I want to show a section that matches your product id category like electronics. You can call it a Related Product section :)
How can I do it?
My Product Details page
import React, { useEffect, useState } from "react";
import {
useGetSingleProductQuery,
useGetRelatedProductQuery,
useGetAllProductsQuery,
} from "../../features/productsApi";
import { useParams } from "react-router-dom";
import axios from "axios";
function ProductDetails() {
const { id } = useParams();
const {
data: getSingleProduct,
isFetching,
error,
} = useGetSingleProductQuery(id);
// Here I can't access category
const [matchCategory, setMatchCategory] = useState(getSingleProduct?.category);
const url = `http://localhost:5000/products?category=${matchCategory}`;
useEffect(() => {
axios
.get(url)
.then((res) => {
console.log(res.data)
setMatchCategory(res.data)
})
.catch((err) => console.log(err));
}, []);
return (
<div className="row mt-5">
{isFetching ? (
<p>Loading ...</p>
) : error ? (
<p>Error occured</p>
) : (
<>
<div className="col-md-6 mb-5 text-center">
<img
className="w-100"
style={{ height: "300px", objectFit: "contain" }}
src={getSingleProduct.image}
alt=""
/>
<div>{getSingleProduct.title.substring(0, 20)}</div>
<p>{getSingleProduct.description}</p>
// But here we access in jsx :( how ?
<p>{getSingleProduct.category}</p>
</div>
</>
)}
</div>
);
}
export default ProductDetails;
I think the data object is undefined in the first time so you need to add a fallback or add the ? like this:
const [matchCategory, setMatchCategory] = useState(getSingleProduct?.category);
// or const [matchCategory, setMatchCategory] = useState(getSingleProduct.category ?? {});

Why is react skeleton not rendering?

I have a component Recommended that makes a service call to firebase and renders the returned data. During the loading delay at the database call, I want to render a react skeleton, as follows:
Recommended.js
import { useState, useEffect } from "react";
import Skeleton from "react-loading-skeleton";
import { getVenues } from "../services/firebase";
import VenueCard from "./VenueCard";
const Reccomended = () => {
const [venues, setVenues] = useState([]);
useEffect(() => {
async function getAllVenues() {
const response = await getVenues();
await setVenues(response);
}
getAllVenues();
}, []);
venues[0] ? console.log(true) : console.log(false)
return (
<div>
{!venues[0] ? (
<>
<Skeleton />
</>
) : (
<>
<p className="recommended">Recommended for Josh</p>
<VenueCard venues={venues} />
</>
)}
</div>
);
};
export default Reccomended;
However, the skeleton is not rending during loading. The returning data is saved to the state variable venues, and I'm using the 'truthiness' as a conditional for the render. I tested this by logging the following:
venues[0] ? console.log(true) : console.log(false)
In the browser it initially logged false, followed quickly by true
So given this, I don't understand why the skeleton isn't loading - any suggestions?
I've also passed parameters into <Skeleton/> which didn't change anything.
You must include the CSS styles, or you won't see anything. Just add
import "react-loading-skeleton/dist/skeleton.css";
with the rest of the imports.
This is documented in the package readme in the react-loading-skeleton basic Usage section

Hook requires data from another hook, but getting Error: Rendered more hooks than during the previous render

I'm getting Error:
Rendered more hooks than during the previous render.
I found some answers saying I should put all hook calls on the top.
However, in the following code, session prints undefined 3 times while it's loading until it prints the session object the fourth time.
Hence, I added a check for the loading state.
import { signIn, signOut, useSession } from "next-auth/client";
import { request } from "graphql-request";
import useSWR from "swr";
export default function Profile() {
const [session, loading] = useSession();
if (loading) {
return <p className="">loading...</p>;
}
if (!session) {
signIn();
}
const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
request("/api/graphql", query, { email })
);
return (
<div className="">
<p className="">{session.user.nickname}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
However, this is throwing an error.
A workaround for this particular issue is to define a NextAuth callback for session, but I don't think that's the correct way to fix it, since I definitely will need to make other hook calls based on the value in the session object in the future.
How can I fix this?
Because hooks are tracked by array index internally by React, you can't invoke them conditionally. You need the same hook calls in the same order each time a given component renders.
You could move the useSWR and subsequent markup to a separate component to avoid the conditional hook calls.
In the example below I've moved the signIn call to a separate component too, but that's not strictly necessary. If you wanted to leave that inline you could just return null instead.
export default function Profile() {
const [session, loading] = useSession();
if (loading) {
return <p className="">loading...</p>;
}
return !session ? <SignIn /> : <UserStuff />
}
function SignIn () {
signIn();
return null;
}
function UserStuff () {
const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
request("/api/graphql", query, { email })
);
return (
<div className="">
<p className="">{session.user.nickname}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}

TypeError: Cannot read property 'getPosts' of undefined - useQuery hook, react Functional Components

I did try searching for the same question but all of those were of either angular or unrelated,
I am trying to make a Social app using MongoDB, Express, React, Node, Graphql with Apollo, I am following a video from freecodecamp : Link to the video
In that video everything worked fine but in his deployed version he is having the same error as mine
react_devtools_backend.js:2450 TypeError:
Cannot read property 'getPosts' of undefined
at ae (Home.js:14)
at Jo (react-dom.production.min.js:3274)
link to the deployed app
My Code: I am dropping a link to my github repo containing the whole project : Link to github
repo
Stack Overflow was throwing too many indentation issues so i have linked my github above as there
is too much of code
I'm using semantic-ui for styling
I'm using graphql the fetch posts from MongoDB
Apollo Client for rendering data
This is the error I am getting in the Home.js:
Screen Shot of the error:
Make it simpler to debug, instead:
const {
loading,
data: { getPosts: posts }
} = useQuery(FETCH_POSTS_QUERY);
do:
const { data, loading, error } = useQuery(FETCH_POSTS_QUERY);
if(data) {
console.log(data);
const { getPosts: posts } = data;
}
if(error) {
console.log(error);
return "error"; // blocks rendering
}
this works but not when data is there and not always
"not when data", "not always"??? weird ... 'posts' can be defined only if data exists ... accessing it when undefined will fail, always ... you must check 'data'
You can/should render items (posts) ONLY when:
!loading
AND
data != undefined - if(data) or (data && in JSX
{loading && <h1>Loading posts..</h1>}
{data && (
<Transition.Group>
{posts &&
posts.map((post) => (
<Grid.Column key={post.id} style={{ marginBottom: 20 }}>
<PostCard post={post} />
</Grid.Column>
))}
</Transition.Group>
)}
use this code like this
const { loading, data: { posts } = {} } = useQuery(FETCH_POSTS_QUERY);
You need to define the query operation like:
export const FETCH_POSTS_QUERY = gql`
query GetPosts {
getPosts {
// fields
}
}
`
Alternatively, you can make use of alias to easily reference them.
export const FETCH_POSTS_QUERY = gql`
query GetPosts {
posts: getPosts {
// fields
}
}
`
const {
loading,
data: { posts } // uses alias directly. no need to rename
} = useQuery(FETCH_POSTS_QUERY);
const { loading, data: { getPosts: posts } = {} } = useQuery(FETCH_POSTS_QUERY)
This should solve the problem
THIS WILL WORK
write data.getPosts inside the grid
const { loading ,data , error } = useQuery(FETCH_POSTS_QUERY);
if (error) return Error! ${error.message};
{loading ? (<h1>Loading posts...</h1>)
: (data.getPosts &&
data.getPosts.map((post) => (
<Grid.Column key={post.id} style= {{ marginBottom: 20}}>
<PostCard post={post} />
</Grid.Column>

Using new Date() in useFirestoreConnect hook where query causing continuous document reads

I am successfully querying Firestore documents with a timestamp earlier than the current date like so:
import React from 'react'
import CircularProgress from '#material-ui/core/CircularProgress'
import { useSelector } from 'react-redux'
import { useFirestoreConnect } from 'react-redux-firebase'
import Grid from '#material-ui/core/Grid'
function MeetingHistory({ userId }) {
const todaysDate = new Date()
useFirestoreConnect([
{ collection: 'users',
doc: userId,
subcollections: [{ collection: 'scheduled',
where: [
['date', '<', todaysDate],
]
}],
storeAs: `${userId}-history-scheduled`
}
])
const pastMeetings = useSelector(state => state.firestore.ordered[`${userId}-history-scheduled`])
if (!pastMeetings) return (
<Grid container direction='row' alignItems='center' justify='center'>
<CircularProgress />
</Grid>
)
console.log('meetings', pastMeetings)
return (
<div>
<p>I am going to render past meeting data here</p>
</div>
)
}
export default MeetingHistory
However, console.log('meetings', pastMeetings) is printing constantly leading me to believe that since the current date is constantly updating as time passes, my database is getting queried every second. This makes sense seeing that new Date() is constantly changing, but I don't want this behavior. I am looking for a way to take a snapshot of the time that won't keep changing with time and base my query off that. Any solution to this would be greatly appreciated. My goal is simply to avoid querying as often as time passes.
I doubt anyone is curious but if they are here is what works. Using a regular Firestore query:
const todaysDate = new Date()
const [pastMeetings, setPastMeetings] = useState(null)
useEffect(() => {
const db = firebase.firestore()
db.collection('users').doc(userId).collection('scheduled').where('date', '<=', todaysDate).limit(15)
.get()
.then(snap => {
var temp = []
snap.forEach(doc => {
temp.push(doc.data())
})
setPastMeetings(temp)
})
.catch(err => {
console.log('error', err)
})
}, [userId])
if (!pastMeetings) return (
<Grid container direction='row' alignItems='center' justify='center'>
<CircularProgress />
</Grid>
)
console.log('meetings', pastMeetings)
This runs once as expected. I am thinking that there must have been an issue with my use of useFirestoreConnect or an issue with redux-firestore. Also, I had to run this inside of useEffect() of course, since updating state in the query would cause infinite re-renders. I was not able to call useFirestoreConnect inside useEffect() since you cant use a hook inside of a callback.

Resources