why polling of Apollo useQuery don't call onCompleted? - javascript

I am playing around the code: https://codesandbox.io/s/restless-framework-uldf4q?file=/src/App.js
import React, { Fragment } from "react";
import { gql } from "apollo-boost";
import { useQuery } from "#apollo/react-hooks";
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
const breed = "dingo";
const App = () => {
const [count, setCount] = React.useState(0);
const { loading, error, data, startPolling, stopPolling } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
onCompleted: () => {
setCount((count) => count + 1);
}
}
);
if (loading) {
return <h2>loading</h2>;
}
if (error) {
return <h2>Whoops</h2>;
}
return (
<div>
<h1> {count}</h1>
<Fragment>
<img
alt="Cute Doggo"
src={data.dog.displayImage}
style={{ height: 500, width: 500 }}
/>
<button onClick={() =>startPolling(500)}>Start</button>
<button onClick={stopPolling}>Stop</button>
</Fragment>
</div>
);
};
export default App;
In my code, I setCount to count+1 using React.useState in the onCompleted callback? why it just stop counting when polling?
so what is the mechanism here?

I can also observe that the onCompleted callback is not invoked as expected in the reproducer you provide.
In your CodeSandbox, you are using a 3-year-old deprecated version of the useQuery hook. You could migrate to the latest #apollo/client package, which will solve the issue.
See this migrated CodeSandbox: https://codesandbox.io/s/apolllo-on-completed-n9wzge

Related

TypeError: Cannot read properties of null (reading 'useState') in Next.js

I'm getting this error message TypeError: Cannot read properties of null (reading 'useState') when I use my custom hooks inside the getStaticProps to fetch the data from the firebase firestore. Anyone, please help me with this?
Challenges page code:
import Card from "../components/reusable/Card"
import { useCollection } from "../hooks/useCollection"
const Challenges = ({ challenges }) => {
return (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8">
{challenges.map((challenge) => {
return (
<Card key={challenge.id} card={challenge} />
)
})}
</div>
)
}
export default Challenges
export async function getStaticProps() {
const { documents, isLoading } = useCollection("challenges", null, null, [
"createdAt",
"desc",
])
return {
props: {
challenges: documents,
},
}
}
useCollection hook code:
import { useEffect, useState } from "react"
import { collection, limit, onSnapshot, orderBy, query, where } from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c, q, l, o) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
if (q) {
ref = query(ref, where(...q))
}
if (o) {
ref = query(ref, orderBy(...o))
}
if (l) {
ref = query(ref, limit(l))
}
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
return () => unsubscribe()
}, [])
return { documents, error, isLoading }
}
You can use useState or any hook only inside a React Component, or a custom hook as Rules of Hooks says, and getStaticProps is none of the two. Plus, it only runs at build time, so not sent to the browser:
If you export a function called getStaticProps (Static Site Generation) from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps.
You could either move the data fetching to the client and get rid of getStaticPros, like so:
import Card from "../components/reusable/Card";
import { useCollection } from "../hooks/useCollection";
const Challenges = () => {
const {
documents: challenges,
isLoading,
error,
} = useCollection("challenges", null, null, ["createdAt", "desc"]);
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error happend</p>;
}
return (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8">
{challenges.map((challenge) => {
return <Card key={challenge.id} card={challenge} />;
})}
</div>
);
};
export default Challenges;
Or transform useCollection to a normal async function without any hook in it, and use it inside getStaticProps. But it doesn't seem like it's what you want, since you are creating a subscription on the client and all.

maybeStripe.apply is not a function

Ahoy,
Getting an error with Stripe in Gatsby occurs on page load
Uncaught (in promise) TypeError: maybeStripe.apply is not a function
import React, { useEffect, useState } from 'react';
import { loadStripe } from '#stripe/stripe-js';
import { Elements } from '#stripe/react-stripe-js';
import StripeCheckout from './stripeCheckout'
const stripePromise = loadStripe('pk_test_xyz');
function App() {
const [clientSecret, setClientSecret] = useState('');
useEffect(() => {
fetch("/.netlify/functions/createIntent")
.then((res) => res.json())
.then(({ clientSecret }) => setClientSecret(clientSecret));
}, [])
const options = {
clientSecret,
}
return (
<main>
<h1>Payment</h1>
{clientSecret && (
<Elements stripe={stripePromise} options={options}>
<StripeCheckout />
</Elements>
)}
</main>
);
}
export default App;
import {
PaymentElement
} from '#stripe/react-stripe-js'
import React, {useState} from 'react'
import {useStripe, useElements} from '#stripe/react-stripe-js';
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:8888",
},
});
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occured.");
}
setIsLoading(false);
}
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{message && <div id="payment-message">{message}</div>}
</form>
)
}
Can't seem to find any ref's to this issue on here or stripes documentation, not sure if this is a Gatsby issue or I am just doing something wrong.
Any help is greatly appreciated
cheers
Removed node_modules and reinstalled and added import { loadStripe } from '#stripe/stripe-js/pure';
You may have installed other modules like #stripe/stripe-js but I don't think you installed the main stripe module. So run npm i stripe or yarn add stripe and the error will be fixed

Getting undefined props in functional react components

How to pass the {requests} prop to the RequestRow component after executing the setRequests? My understanding is that the requests get initialized as undefined in the beginning and before being set with the asynchronously called object, it gets passed to the RequestRow component as undefined, and the error occurs.
import React, { useState, useEffect } from 'react';
import 'semantic-ui-css/semantic.min.css';
import Layout from '../../../components/Layout';
import { Button } from 'semantic-ui-react';
import { Link } from '../../../routes';
import Campaign from '../../../blockchain/campaign';
import { Table } from 'semantic-ui-react';
import RequestRow from '../../../components/RequestRow';
const RequestsIndex = ({ address }) => {
const { Header, Row, HeaderCell, Body } = Table;
const campaign = Campaign(address);
const [requestCount, setRequestCount] = useState();
const [requests, setRequests] = useState([]);
const getRequests = async () => {
const count = await campaign.methods.getRequestsCount().call();
setRequestCount(count);
};
let r;
const req = async () => {
r = await Promise.all(
Array(parseInt(requestCount))
.fill()
.map((_element, index) => {
return campaign.methods.requests(index).call();
})
);
setRequests(r);
};
useEffect(() => {
getRequests();
if (requestCount) {
req();
}
}, [requestCount]);
return (
<Layout>
<h3>Requests List.</h3>
<Link route={`/campaigns/${address}/requests/new`}>
<a>
<Button primary>Add Request</Button>
</a>
</Link>
<Table>
<Header>
<Row>
<HeaderCell>ID</HeaderCell>
<HeaderCell>Description</HeaderCell>
<HeaderCell>Amount</HeaderCell>
<HeaderCell>Recipient</HeaderCell>
<HeaderCell>Approval Count</HeaderCell>
<HeaderCell>Approve</HeaderCell>
<HeaderCell>Finalize</HeaderCell>
</Row>
</Header>
<Body>
<Row>
<RequestRow requests={requests}></RequestRow>
</Row>
</Body>
</Table>
</Layout>
);
};
export async function getServerSideProps(context) {
const address = context.query.address;
return {
props: { address },
};
}
export default RequestsIndex;
The RequestRow component is shown below. It takes in the {requests} props, which unfortunately is undefined.
const RequestRow = ({ requests }) => {
return requests.map((request, index) => {
return (
<>
<div>Request!!!</div>
</>
);
});
};
export default RequestRow;
The snapshot of the error is shown below:
I think React is trying to render your component before your promises resolve. If that's the case, all you need to do is set a default value (an empty array in your case) for your requests.
const [requests, setRequests] = useState([]);
May the force be with you.

useEffect called multiple times when using <Redirect> or history.push()

I got a little problem because I can't redirect logged in user to app, when he's saved in localStorage.
Both react-router-dom functions return Maximum update depth exceeded. but why?
import React, { useState, useEffect } from 'react'
import { authLocalUser } from 'actions/userActions'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { useHistory } from 'react-router-dom'
// components
import SigninForm from 'components/organisms/Forms/SigninForm'
import SignupForm from 'components/organisms/Forms/SignupForm'
// styles
import { Content, Footer, Wrapper, Header } from './styles'
const Landing = ({ fetchLocalStorage, userID }) => {
const [isModalOpen, setModalOpen] = useState(false)
const history = useHistory()
useEffect(async () => {
const userData = await JSON.parse(
localStorage.getItem('userData'),
)
await fetchLocalStorage(userData)
}, [])
return (
<>
{userID && history.push('/app')}
<Content>
{isModalOpen && (
<div
style={{
zIndex: 300,
left: 0,
position: 'absolute',
width: '100%',
height: '100%',
background: 'rgb(0,0,0,0.5)',
}}
>
<SignupForm setModalOpen={setModalOpen} />
</div>
)}
<Wrapper w='60'>
<Header>
<h1>ChatterApp</h1>
<h3>
Chat with your friend in real-time using
magic of Web Sockets! Join our community
today!
</h3>
</Header>
</Wrapper>
<Wrapper signin w='40'>
<SigninForm setModalOpen={setModalOpen} />
</Wrapper>
</Content>
<Footer>
<Content>
<h3>ChatterApp</h3>
<h5>Dawid Szemborowski</h5>
</Content>
</Footer>
</>
)
}
const mapStateToProps = ({ user }) => ({
userID: user._id,
})
const mapDispatchToProps = dispatch => ({
fetchLocalStorage: localStorage =>
dispatch(authLocalUser(localStorage)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Landing)
Landing.propTypes = {
userID: PropTypes.string,
fetchLocalStorage: PropTypes.func.isRequired,
}
Landing.defaultProps = {
userID: undefined,
}
I tried calling this function without async/await, I tried providing userID and localStorage as that last parameter for componentDidUpdate. Where is my problem? Error I get displays the problem is inside Lifecycle component
index.js:1 The above error occurred in the <Lifecycle> component:
at Lifecycle (http://localhost:3000/static/js/vendors~main.chunk.js:47761:29)
at Redirect (http://localhost:3000/static/js/vendors~main.chunk.js:47862:28)
authLocalUser code
export const authLocalUser = userData => {
return {
type: 'FETCH_LOCAL_STORAGE',
payload: userData,
}
}
You probably want to do something like this.
Replacing {userID && history.push('/app')} with:
useEffect(() => {
if(userId) {
history.push('/app')
}
}, [userId])
As a suggestion, your first useEffect call can be corrected. If you make the callback of useEffect as async it will return a promise which is not the way useEffect works. It returns a cleanup function.
Use an IIFE instead:
useEffect(() => {
(async () => {
const userData = JSON.parse(localStorage.getItem('userData'))
await fetchLocalStorage(userData)
})()
}, [])

How to pass id from one component to another component onclick of an element

I'm trying to pass this is as id as props to another component which is not a child of the component. I was considering using context but i wanted to know if there was another way to it, since I'm quite new to react I'm looking for a more efficient way.
This is the component where the id of the element clicked is being generated. When i logged it the data is correct an no problems was notified. I first tried passing it as props as seen below but since i didn't want it to be seen on that page i didn't pass it to the main return statement neither did i call the method in it, but then it returned undefined in the component where i wanted to make use of it
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom';
import Workspacelist from '../Workspace/Workspacelist';
function BoardList({ boards }) {
const [currentid, setcurrentid] = useState('')
const history = useHistory()
const navigate = (id) => {
setcurrentid(id);
console.log(id)
history.push(`/workspace/${id}`)
return(
<Workspacelist id = {id}/>
)
}
return (
<>
{
boards.map((board) => (
<li key={board.id} className="boardlist" style={styles} onClick={() => navigate(board.id)}>
<h3>{board.title}</h3>
</li>
))}
</>
)
}
export default BoardList
PS: Firebase is being incoporated in this project, i was thinking that might be the reason cause it's my first time using firebase so maybe I'm missing something since all the data is coming from the server
And this is the component i want to pass it to
import React, { useState, useEffect } from 'react'
import Firebase, { db } from '../Firebase/Firebase';
import { Todo } from './List';
function Workspacelist({ id }) {
const [updatedId] = useState(id)
const [show, setshow] = useState(false);
const [Todos, setTodos] = useState([]);//Todolist
const [ToDo, setToDo] = useState('');
useEffect(() => {
const docRef = db.collection("boards").doc(updatedId).get().then(doc => {
if (doc.exists) {
setTodos(doc.data().todo);
console.log("Document data:", doc.data().todo);
} else {
console.log("No such document!");
}
}).catch(function (error) {
console.log("Error getting document:", error);
});
return docRef
})
return (
<div className="workspacelist">
<div className="todo">
<div>
<b>To Do</b>
<b>...</b>
<Todo Todos={Todos} />
<span onClick={() => { setshow(current => !current) }} style={{ display: show ? 'none' : 'block' }}>+ Add a card</span>
</div>
<div className="add" style={{ display: show ? 'block' : 'none' }}>
<textarea placeholder="Enter a title for this card..." value={ToDo} onChange={(e) => { setToDo(e.target.value) }} />
<button className="addcard" onClick={one}>Add Card</button>
<button onClick={() => { setshow(current => !current) }}>X</button>
<button className="more">...</button>
</div>
</div>
</div>
)
}
export default Workspacelist
Thanks in advance i did appreciate the help even if i have to rewrite it just tell me the way you would do it if you were in my shoes
To navigate to another page, you just need history.push(/workspace/${id}).
You don't even need any state here.
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom';
import Workspacelist from '../Workspace/Workspacelist';
function BoardList({ boards }) {
const history = useHistory()
const navigate = (id) => {
history.push(`/workspace/${id}`)
}
return (
<>
{
boards.map((board) => (
<li key={board.id} className="boardlist" style={styles} onClick={() => navigate(board.id)}>
<h3>{board.title}</h3>
</li>
))}
</>
)
}
export default BoardList
To get the id param on the Workspace page, you will need to use the useRouteMatch hook from react-router-dom:
import { useRouteMatch } from 'react-router-dom';
function Workspacelist() {
const {
params: { id },
} = useRouteMatch('/workspace/:id');
console.log(id)
}
Let me know if it solves your problem.
If you use dom version 6, change the following parts that showed in #HichamELBSI answer.
useHistory should change into useNavigate.
useRouteMatch should change into useMatch.
After applying those, the codes should be
import { useNavigate} from 'react-router-dom';
const nav = useNavigate();
const navigate = (id) => {
nav(`/workspace/${id}`)
}
Then other part should be
import { useMatch } from 'react-router-dom';
function Workspacelist() {
const {
params: { id },
} = useMatch('/workspace/:id');
console.log(id)
}

Categories

Resources