React apollo client api calling two time - reactjs

This is my React code of ApolloClient
function EmpTable() {
const GET_EMPLOYEE = gql`
query refetch($id: String) {
employeeById(id: $id) {
id
name
role
}
}
`;
const {refetch} = useQuery(GET_EMPLOYEE)
const getEmpByID = (id) => {
refetch({
id: id
}).then((response) => {
// do something
})
}
return (
<div className="row">
{/* { I am rending list of employee with map and passing id this way
<a onClick={() => getEmpByID(id)}>get employ info</a>
} */}
</div>
);
}
export default EmpTable;
Everything is working very well in this code, the only problem is the API being called two times, first time it returns no data, and 2nd time, it returns the expected data.
How can I prevent calling this twice?
I guess this is executing first time: const {refetch} = useQuery(GET_EMPLOYEE) and making the first request without data, because, no variable is passed there. I know I can can pass a variable in useQuery first time but the problem is that I can’t pass this from there, because the query params are not in my state or props.
Can anyone tell me what is the possible solution for this?

Due to documentantion:
When React mounts and renders a component that calls the useQuery
hook, Apollo Client automatically executes the specified query. But
what if you want to execute a query in response to a different event,
such as a user clicking a button? The useLazyQuery hook is perfect for
executing queries in response to events other than component
rendering. This hook acts just like useQuery, with one key exception:
when useLazyQuery is called, it does not immediately execute its
associated query. Instead, it returns a function in its result tuple
that you can call whenever you're ready to execute the query:
Example :
const GET_COUNTRIES = gql`
{
countries {
code
name
}
}
`;
export function DelayedCountries() {
const [getCountries, { loading, data }] = useLazyQuery(GET_COUNTRIES);
if (loading) return <p>Loading ...</p>;
if (data && data.countries) {
console.log(data.countries);
}
return (
<div>
<button onClick={() => getCountries()}>
Click me to print all countries!
</button>
{data &&
data.countries &&
data.countries.map((c, i) => <div key={i}>{c.name}</div>)}
</div>
);
}
useLazyQuery will be executed at the moment, when getCountries is called.
https://codesandbox.io/s/apollo-client-uselazyquery-example-6ui35?file=/src/Countries.js

This is a normal behavior in React 18, all events are called twice, this is new in React 18, and, only happens in development.
Now, why are they doing this? because it helps to identify bugged effects and hooks, and it works.
If you wanna disable this you can do it by disabling strict mode (https://reactjs.org/docs/strict-mode.html).
So, if the component is working, you have no warning about data leaking stay calm and continue. Another option to try is compile your application and run it in production mode, if the hook it's called twice then yeah, by all means, validate your component code (and it's parent components)

Related

Components aren't re-rendered after setState update, requires updating twice before re-render

I am creating a custom blog listing page in React for a HubSpot site (I've already searched there, couldn't find anything relevant to my issue). I'm still somewhat new to React but it is my first project from scratch in over a year. Basically what I am trying to do is combine three separate blogs into one page and add filtering options to show only posts from certain blogs and/or posts with certain tags attached to them. Currently I am successfully pulling the post data and sorting it. I am almost finished with the filtering functionality, but I am hitting a snag when trying to reset all filters.
I understand that setState is asynchronous and you can't expect the state value to updated immediately. My issue is that even after I have successfully updated the state, the useEffect hook doesn't fire consistently. I have tabs at the top of the page to switch between blogs and that works perfectly, as soon as I click one it filters out every other blog post and I can go back and forth between them easily. The problem is on the reset, I have to click the button twice for it to work. I'm completely at a loss as to why this is. In the console I see that the useEffect function for blogActive fires after the first time I click the reset button, but the blog view doesn't change. The second time I click the reset button, the blog view changes but the useEffect function doesn't fire.
I know that useEffect isn't supposed to fire if the state isn't actually updated (its being set to undefined when the button is clicked, so the second time it won't change) but I am still confused as to why this happens with the reset button and not with the blog switch function. I was originally resetting the blog from the reset function instead of just the sort function but that wasn't working either and calling it from the sort function is simpler in terms of displaying results with resetting the other filters (I have removed the tag filtering functions and components and still get the same results). I've also tried calling the handleBlogSwitch method instead of using the setState hook but I get the same results.
I've pasted the most relevant code below, but I've also included a sandbox link with a skeleton of the whole project including the code to fetch the post data.
Any help would be much appreciated!
function Blog({ blogName, blogID, handleBlog }) {
return (
<div className="blog">
<button className={"blog-" + blogName} onClick={() => handleBlog(blogID)}>
{blogName}
</button>
</div>
);
}
export default Blog;
function Archive() {
const [page, setPage] = useState(1);
const [blogActive, setBlog] = useState();
const [resetCheck, setChecked] = useState([]);
const [postState, setPosts] = useState(postArray);
let postArray = [];
function handleBlogSwitch(id) {
setBlog(id);
}
function sortPosts(sortme) {
let resetSort = false;
if (sortme) {
sortme.sort((a, b) => b["dataset"]["date"] - a["dataset"]["date"]);
postArray = [...postArray, ...sortme];
} else if (sortme === false) {
resetSort = true;
setBlog(() => {
return undefined;
});
}
let filtered = postArray;
if(!resetSort) {
if (blogActive) {
filtered = blogFilter(filtered);
setLoader("spinner-loaded");
}
}
setPosts(filtered);
setLoader("spinner-loaded");
}
function resetFilters(resetThis) {
setChecked([]); // Uncheck everything
sortPosts(resetThis);
}
// Handle logic for blog and tag filters
useEffect(() => {
setBtnLoad("btn-load-more"); // add this to the useEffect hook for tags too.
if(blogActive) {// Don't run this on page load.
sortPosts();
console.log("test blogactive effect");
}
}, [blogActive]);
return (
<div className="results__container">
<div className="tabs__container">
{Blogs.blogs.map((blog, key) => (
<Blog
key={key}
blogName={blog.name}
blogID={blog.id}
handleBlog={handleBlogSwitch}
></Blog>
))}
</div>
<div className="filter__container">
<button onClick={() => resetFilters(false)}>Reset Filters</button>
<div className="posts__container">
{postState.slice(0, page * resultsPerPage).map((html, key) => (
<Posts key={key} postHtml={html.outerHTML}></Posts>
))}
<img className={loader} src={logoSrc} />
<button className={btnClass} onClick={() => setPage(page + 1)}>
Load More
</button>
</div>
</div>
</div>
);
}
export default Archive;
https://codesandbox.io/s/wonderful-lalande-0w16yu?file=/src/App.js
Update: I didn't really fix this bug I'm facing, but I found a workaround. Instead of relying on the value of blogActive in sortPosts(), I created a new boolean in sortPosts which is set to true when it is called from the reset function. This way I know that the states should be resetting and skip that code, even if they aren't caught up yet.

ReactJS: useEffect update metadata on every request of aws IVS chaannel

I would like to perform the continuous process as this below link to update the metadata of the aws IVS stream.
I have managed with the useEffect hook but it is not updating the value and throws the undefined error.
https://codepen.io/amazon-ivs/pen/XWmjEKN?editors=0011
function AppComponent(props) {
const [metaData, setMetaData] = useState(null)
useEffect(() => {
const data = props.metadata
const initData = async () => {
setMetaData(data)
}
initData()
}, [metaData])
return (
<div>
<h2>{metaData.question}</h2>
</div>
)
}
Ok, I am a bit confused about this code snippet.
if you assume that you receive in your props metadata then probably you don't need state at all, and your code should look like below
function AppComponent(props) {
return (
<div>
<h2>{props.metadata.question}</h2>
</div>
)
}
Regarding your code overall, you have created metaData with initial value null, so metaData.question will throw error on the initial run.
It doesn't make no sense to run setMetaData asynchronously. Also you have subscribed to metaData change in your hook, but only place where you update it is your hook, it will cause also infinite loop.
It would be better if you will provide more information about your issue and make this question not so wide
If you use only some properties from the object you are passing then you probably don't need to pass whole object as a prop, but it would be better to pass props that you use, as any change on the object, you are passing will cause rerendering of the component. Also you can try to use React.memo to avoid unnecessary rerendering cycles.

Do I have to worry about useState causing a re-render?

NB: I've asked this on wordpress.stackexchange, but it's not getting any response there, so trying here.
I'm not sure if this is WordPress specific, WordPress's overloaded React specific, or just React, but I'm creating a new block plugin for WordPress, and if I use useState in its edit function, the page is re-rendered, even if I never call the setter function.
import { useState } from '#wordpress/element';
export default function MyEdit( props ) {
const {
attributes: {
anAttribute
},
setAttributes,
} = props;
const [ isValidating, setIsValidating ] = useState( false );
const post_id = wp.data.select("core/editor").getCurrentPostId();
console.log('Post ID is ', post_id);
const MyPlaceholder = () => {
return(
<div>this is a test</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
If I comment out const [ isValidating, setIsValidating ] = useState( false ); then that console.log happens once. If I leave it in, it happens twice; even if I never check the value of isValidating, never mind calling setIsValidating. I don't want to over-optimize things, but, equally, if I use this block n times on a page, the page is getting rendered 2n times. It's only on the admin side of things, because it's in the edit, so maybe not a big deal, but ... it doesn't seem right. Is this expected behavior for useState? Am I doing something wrong? Do I have to worry about it (from a speed perspective, from a potential race conditions as everything is re-rendered multiple times)?
In your example code, the console.log statement is being immediately evaluated each time and triggering the redraw/re-rendering of your block. Once console.log is removed, only the state changes will trigger re-rendering.
As the Gutenberg Editor is based on Redux, if the state changes, any components that rely on that state are re-rendered. When a block is selected in the Editor, the selected block is rendered synchronously while all other blocks in the Editor are rendered asynchronously. The WordPress Gutenberg developers are aware of re-rendering being a performance concern and have taken steps to reduce re-rendering.
When requesting data from wp.data, useEffect() should be used to safely await asynchronous data:
import { useState, useEffect } from '#wordpress/element';
export default function MyEdit(props) {
...
const [curPostId, setCurPostId] = useState(false);
useEffect(() => {
async function getMyPostId() {
const post_id = await wp.data.select("core/editor").getCurrentPostId();
setCurPostId(post_id);
}
getMyPostId();
}, []); // Run once
const MyPlaceholder = () => {
return (
<div>Current Post Id: {curPostId}</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
As mentioned in the question, useState() is used in core blocks for setting and updating state. The state hook was introducted in React 16.8, its a fairly recent change and you may come across older Gutenberg code example that set state via the class constructor and don't use hooks.
Yes, you have to worry about always put an array of dependencies, so that, it won't re-render, As per your query, let's say are planning to edit a field here is the sample code
const [edit, setEdit]= useState(props);
useEffect(() => {
// logic here
},[edit])
that [edit] will check if there is any changes , and according to that it will update the DOM, if you don't put any [](array of dependencies) it will always go an infinite loop,
I guess this is expected behavior. If I add a similar console.log to native core blocks that use useState, I get the same effect. It seems that WordPress operates with use strict, and according to this answer, React double-invokes a number of things when in strict mode.

How to properly implement "isLoading" in a parent component

I'd like to create a reusable component that may contain various children that might require loading data. The parent component doesn't (and should not) know, if any childs need to fetch additional data. It looks like this:
function MyPage(props) {
return (
<>
<WidgetA/>
<WidgetB/>
<WidgetC/>
</>
);
}
Now, instead of showing 3 spinners for each individual widget, I'd like to show just one spinner for the whole page. Also, when there is no data available (none of the childs returned any HTML), I'd like to show another component saying something like "There is no data available".
I've already tried a simple idea, namely to return null from a child to indicate that it isn't ready yet.
function MyPage(props) {
const widgetA = <WidgetA/>
if(!widgetA) {
return <div>Loading</div>
}
return (
<div>
<WidgetA/>
</div>
);
}
function WidgetA() {
// ...
if(loading) return null
}
However, this does not work because I am unable to determine whether a component is returning something or not. The component is never null, React.Children.count doesn't work and so on.
You can use ternary condition for this- Sample app
function MyPage() {
const [isLoading, setLoading] = React.useState(true);
React.useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/users')
.then(res=>res.json())
.then(res=>{
setLoading(false)
console.log(res)
})
},[])
return (
<div>
{!isLoading ?
<>
<WidgetA/>
<WidgetB/>
<WidgetC/>
</>:<h2>Loading....</h2>
}
</div>
);
}
isLoading will be set true/false as per your condition like fetching api or inside click function you can set true/false.
Live working demo to use loading.
Use some central store management like redux. And call that loading variable in header component which is common for all three child components.
Make api calls from redux and toggle isLaoding variable on api calls.
Your job done.

Calling a graphQL query from within react client

I'm trying to return a list of all users transactions, but I can't seem to call the graphQL query correctly. I've defined the query at the bottom and I'm trying to call it in refreshData(). What am I missing?
async refreshData() {
const allTransactions = await graphql(getTransactionsQuery);
console.log(allTransactions);
this.setState({
transactions: allTransactions
});
}
render() {
const transactionsFromState = this.state.transactions;
const transactions = transactionsFromState.map((transaction) =>
<li key={transaction.id}>{transaction}</li>
);
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactions }</ul>
</div>
);
}
}
const getTransactionsQuery = gql`
query getTransactions($id: ID!) {
transactions(userId: $id){Transaction}
}
`;
//change User ID use context.user.id
export default graphql(getTransactionsQuery)(App);
Using the graphql Higher Order Component
The graphql function you're calling returns a higher order component that can be then used to wrap another React component. It does not return a promise that can be awaited like you're trying to do.
You are already utilizing it (more or less) correctly here:
export default graphql(getTransactionsQuery)(App)
When you wrap your component with the graphql HOC like this, your component receives a data prop that can then be used inside your component's render method. This negates the need to utilize component state to persist your query results. Your render method should look something like this:
render() {
const { transactions } = this.props.data
const transactionItems = transactions
? transactions.map(t => <li key={t.id}>{t}</li>)
: null
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactionItems }</ul>
</div>
);
}
data.transactions will be initially undefined before the query is fetched, so it's important to be mindful of that inside your render function. You can also check data.loading to determine if the query is still in flight. Please check the Apollo docs (here) for more examples.
Fetching a query with variables
Your query utilizes variables, so those variables need to be sent along with it. You'll need to pass those into the graphql function as options like this:
export default graphql(getTransactionsQuery, { options: { variables: { id: 'youridhere' } } })(App)
Options is normally passed an object, but it can also be passed a function. This function takes props as an argument, so that you can derive your variables from props if needed. For example:
const options = props => ({ variables: { id: props.id } })
export default graphql(getTransactionsQuery, { options })(App)
Check here for more information.
Refetching data
Since Apollo does the heavy lifting for you, there's no need to have a button to fetch the data from your server. But should you need to refetch your data, you can do so through the data prop passed down to your component by calling props.data.refetch(). You can read more about refetch here.

Resources