What does notifyOnNetworkStatusChange do exactly? - apollo-client

I had an issue where the loading property was not true when performing a refetch. I read somewhere notifyOnNetworkStatusChange to true would fix this.
However we've notice after using this property we have some use case where we end up in infinite refetch loop with no good explanation.
Can someone explain exactly what the notifyOnNetworkStatusChange do exactly on the useQuery hook? When should you use it, is there a particular fetch-policy you need to use in conjunction to it?

It seems like notifyOnNetworkStatusChange: true is to allow us check the current networkStatus of your useQuery, which is a number. Then based on the networkStatus variable we can choose our own strategy to render the current component.
Important Note:
loading will be undefined without notifyOnNetworkStatusChange: true
The following code is from
https://www.apollographql.com/docs/react/data/queries/#inspecting-loading-states
import { NetworkStatus } from '#apollo/client';
function DogPhoto({ breed }) {
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
notifyOnNetworkStatusChange: true,
},
);
if (networkStatus === NetworkStatus.refetch) return 'Refetching!';
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<div>
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
<button onClick={() => refetch()}>Refetch!</button>
</div>
);
}
If you noticed the code
if (networkStatus === NetworkStatus.refetch) return 'Refetching!', which means when refetch() is called the DogPhoto component will change to Retching! until it's done.
The below link explained meaning of each number in networkstatus.
https://github.com/apollographql/apollo-client/blob/master/src/core/networkStatus.ts#L4
It's used to reflect the current status of your useQuery. Then based on the status, you can do further action upon it in case if things failed, but I believe you can always create your own solution without it.

Related

Cypress conditional statement for a disabled button element

Trying to click a button based on another element with enabled/disabled status. For some reason, my disabled check code is not working and it always ends in another statement ('No existing routes found') even though the UI has a select button enabled.
cy.get('voyage-suggested-routes')
.find('button.selectButton')
.then(($routes) => {
if ($routes.is(":disabled")) {
cy.log("No existing routes found...")
} else {
cy.log("Deleting......")
cy.get('.delete-button').click({ force: true, multiple: true })
}
});
This is the DOM: (There are 3 elements by default and a delete option will be there for each Select button if it is not disabled.)
<button class="selectButton" disabled route="1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="..."></path></svg>
SELECT
</button>
Tried the jquery method as well but the same result.
var btnStatus = Cypress.$('.selectButton')
.is(":disabled");
if (btnStatus == true) {
cy.log("Deleting......")
cy.get('.delete-button').click({ force: true, multiple: true })
} else {
cy.log("No existing routes found...")
}
What am I missing?
Update 1:
After Electron's input, my new code is:
cy.get('voyage-suggested-routes')
.find('button.selectButton')
.then(($routes) => {
if ($routes.is(":disabled").length === 0) {
cy.log("No existing routes found...")
} else {
cy.log("Deleting......")
cy.get('.delete-button').click({ force: true, multiple: true })
}
});
From the docs jQuery .is()
Description: Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
So if only one route is disabled, the delete will not go ahead.
Try using a filter to see if any are disabled.
cy.get('voyage-suggested-routes')
.find('button.selectButton')
.then(($routes) => {
const disabled = $routes.filter(":disabled")
if ($routes.length === disabled.length) {
cy.log("No existing routes found...")
} else {
cy.log("Deleting......")
cy.get('.delete-button').click({ force: true, multiple: true })
}
})
It's because you need each instead of then, like this:
.each(($routes) => {
in order to perform as many actions as there are button elements.
Edit: as Electron stated in the comments, the suggestion below will fail a test if all buttons are disabled, so take care if you use it.
And to better optimize your code, your can set the selector as .find('button.selectButton:not(:disabled)') then you don't need if block at all, just the delete statement.
Here's a custom command which conditionally runs a callback, depending on the result of filtering by given selector.
Not a lot of difference to .then(($routes) => { const disabled = $routes.filter(":disabled") pattern. Unfortunately ending part of a chain is quite difficult, as the whole test is considered one chain.
Cypress.Commands.add('maybe', {prevSubject:true}, (subject, selector, callback) => {
const result = subject.filter(selector)
if (result.length > 0) {
cy.log(`Maybe: Found ${result.length} "${selector}"`)
cy.wrap(result).then(callback)
return
}
cy.log(`Maybe: Not found: "${selector}"`)
})
cy.get('button.selectButton')
.maybe(':not(:disabled)', ($result) => {
// can use result of filter here
console.log('result is', $result))
// or conditionally run some other commands
cy.log(`Deleting...`)
cy.get('.delete-button').click({ force: true, multiple: true })
})
// runs either way
cy.wrap('something')
.then(x => console.log('next', x))

Apollo GraphQL Deconstruction of data not working

I am digging graphql so I followed a tutorial, And I stucked in this part.
Home.js
function Home() {
const {
loading,
data: { getPosts: posts } // <===## Here ##
} = useQuery(FETCH_POSTS_QUERY);
return (
<div>
{loading ? (
<h1>Loading posts..</h1>
) : (
posts &&
posts.map((post) => (
<p>
{post.content}
</p>
))
)}
</div>
);
}
const FETCH_POSTS_QUERY = gql`
{
getPosts {
id
content
}
}
`;
export default Home;
resolver
Query: {
async getPosts() {
try {
const posts = await Post.find().sort({ createdAt: -1 });
return posts;
} catch (err) {
throw new Error(err);
}
}
},
Whole code: https://github.com/hidjou/classsed-graphql-mern-apollo/tree/react10
In above example is working well, and it use it use data: { getPosts: posts } for deconstruction of returned data. but In my code, I followed it but I got an error
TypeError: Cannot read property 'getPosts' of undefined
Instead, If I code like below,
function Home() {
const {
loading,
data // <===## Here ##
} = useQuery(FETCH_POSTS_QUERY);
if(loading) return <h1>Loading...</h1>
const { getPosts: posts } = data // <===## Here ##
return (
<div>
{loading ? (
<h1>Loading posts..</h1>
) : (
posts &&
posts.map((post) => (
<p>
{post.content}
</p>
))
)}
</div>
);
}
It working well. Seems like my code try to reference data before it loaded. But I don't know why this happen. Code is almost same. Different things are 1. my code is on nextjs, 2. my code is on apollo-server-express. Other things are almost same, my resolver use async/await, and will return posts. Am I miss something?
my resolver is like below.
Query: {
async getPosts(_, { pageNum, searchQuery }) {
try {
const perPage = 5
const posts =
await Post
.find(searchQuery ? { $or: search } : {})
.sort('-_id')
.limit(perPage)
.skip((pageNum - 1) * perPage)
return posts
} catch (err) {
throw new Error(err)
}
},
Your tutorial may be out of date. In older versions of Apollo Client, data was initially set to an empty object. This way, if your code accessed some property on it, it wouldn't blow up. While this was convenient, it also wasn't particularly accurate (there is no data, so why are we providing an object?). Now, data is simply undefined until your operation completes. This is why the latter code is working -- you don't access any properties on data until after loading is false, which means the query is done and data is no longer undefined.
If you want to destructure data when your hook is declared, you can utilize a default value like this:
const {
loading,
data: { getPosts: posts } = {}
} = useQuery(FETCH_POSTS_QUERY)
You could even assign a default value to posts as well if you like.
Just keep in mind two other things: One, data will remain undefined if a network error occurs, even after loading is changed to true, so make sure your code accounts for this scenario. Two, depending on your schema, if there's errors in your response, it's possible for your entire data object to end up null. In this case, you'll still hit an issue with destructuring because default values only work with undefined, not null.

How to use most recent state from useReducer before re-render

I have two reducer actions that I want to dispatch one after the other. The first one modifies the state, then the second one uses a portion of the modified state to make another modification. The difficulty is that when the second dispatch is called, it still has the old outdated state and thus doesn't update the state properly.
An example is the following (also found here - https://codesandbox.io/s/react-usereducer-hqtc2) where there is a list of conversations along with a note of which one is considered the "active" conversation:
import React, { useReducer } from "react";
const reducer = (state, action) => {
switch (action.type) {
case "removeConversation":
return {
...state,
conversations: state.conversations.filter(
c => c.title !== action.payload
)
};
case "setActive":
return {
...state,
activeConversation: action.payload
};
default:
return state;
}
};
export default function Conversations() {
const [{ conversations, activeConversation }, dispatch] = useReducer(
reducer,
{
conversations: [
{ title: "James" },
{ title: "John" },
{ title: "Mindy" }
],
activeConversation: { title: "James" }
}
);
function removeConversation() {
dispatch({ type: "removeConversation", payload: activeConversation.title });
dispatch({ type: "setActive", payload: conversations[0] });
}
return (
<div>
Active conversation: {activeConversation.title}
<button onClick={removeConversation}>Remove</button>
<ul>
{conversations.map(conversation => (
<li key={conversation.title}>{conversation.title}</li>
))}
</ul>
</div>
);
}
In here, when I click the "remove conversation" button, I want to remove the active conversation, then set the active conversation to be the one at the top of the list. However, here when the first dispatch removes the conversation from the list, the second dispatch sets active to conversations[0], which still contains the removed value (since the state hasn't updated yet). As a result, it keeps the active conversation as the one it was before, even though it's been removed from the list.
I could probably combine the logic into just one action and do it all there (remove the conversation and set active all in one), but I would ideally like to keep my reducer actions to have one responsibility each if possible.
Is there any way to make the second dispatch call have the most recent version of the state so that this kind of problem doesn't occur?
It may help if you think of useEffect() like setState's second parameter (from class based components).
If you want to do an operation with the most recent state, use useEffect() which will be hit when the state changes:
const {
useState,
useEffect
} = React;
function App() {
const [count, setCount] = useState(0);
const decrement = () => setCount(count-1);
const increment = () => setCount(count+1);
useEffect(() => {
console.log("useEffect", count);
}, [count]);
console.log("render", count);
return (
<div className="App">
<p>{count}</p>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( < App / > , rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Some further info on useEffect()
Answering this for anyone who may come across similar issues in the future. The key to finding the solution to this is understanding that state in React is a snapshot.
You can see that in the dispatched setActive action, the value of conversations[0] of state is being passed:
dispatch({ type: "setActive", payload: conversations[0] });
Thus when the action is called before the next render, it uses the snapshotted state at the time of re-render:
// snapshot of state when action is called
{
conversations: [
{ title: "James" },
{ title: "John" },
{ title: "Mindy" }
],
activeConversation: { title: "James" }
}
Thus conversations[0] evaluates to {title: "James"}. This is why in the reducer, activeConversation: action.payload returns {title: "James"} and the active conversation doesn't change. In technical terms, "you're calculating the new state from the value in your closure, instead of calculating it from the most recent value."
So how do we fix this? Well useReducer actually in fact always has access to the most recent state value. It is a sister pattern to the state updater function, which also gives you access to the latest state variable even before the next render.
This means that after the first dispatch action:
dispatch({ type: "removeConversation", payload: activeConversation.title }); // first dispatch action
dispatch({ type: "setActive", payload: conversations[0] }); // second dispatch action
the next dispatch action actually has access to the latest state already. You just need to access it:
case "setActive":
return {
...state,
activeConversation: state.conversations[0]
};
You can verify this by logging it to the console:
const reducer = (state, action) => {
console.log(state);
switch (action.type) {
case "removeConversation":
return {
...state,
conversations: state.conversations.filter(
c => c.title !== action.payload
)
};
case "setActive":
return {
...state,
activeConversation: state.conversations[0]
};
default:
return state;
}
};
Also important to note that the 2 dispatch calls are batched as explained in the state updater function link mentioned above. More info on batching here too.

reselect across multiple comoponents work only with deepEqual check

I've tested in various ways... Still, It isn't working.
I don't seem to doing anything wrong
exactly same code as reselect doc
redux store is all normalized
reducers are all immutable
From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)
### This is what Parent components render looks like
render() {
return (
<div>
<h4>Parent component</h4>
{this.props.sessionWindow.tabs.map(tabId =>
<ChildComponentHere key={tabId} tabId={tabId} />
)}
</div>
);
}
### This is what Child component looks like
render() {
const { sessionTab } = this.props (this props is from connect() )
<div>
<Tab key={sessionTab.id} tab={sessionTab} />
</div>
))
}
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
createSelector(
[getTheTab],
(tab: object) => tab
)
export const makeMapState = () => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props)
}
}
return mapStateToProps
}
Weirdly Working solution: just change to deep equality check.(from anywhere)
use selectors with deep equality works as expected.
at shouldComponentUpdate. use _.isEqual also worked.
.
1. const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}
From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.
To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.
This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?
For more clarification.(hopefully..)
First,My redux data structure is like this
sessionWindow: {
byId: { // window datas byId
"windowId_111": {
id: "windowId_111",
incognito: false,
tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
},
"windowId_222": {
id: "windowId_222",
incognito: true,
tabs: [2, 8, 333, 111]
},{
... keep same data structure as above
}
},
allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: { // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
"1": {
id: 1
title: "google",
url: "www.google.com",
active: false,
...more properties
},
"7": {
id: 7
title: "github",
url: "www.github.com",
active: true
},{
...keep same data structure as above
}
}
Problems.
1. when a small portion of data changed, It re-renders all other components.
Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic
const updateSessionTab = (state, action) => {
return {
...state,
[action.tabId]: {
...state[action.tabId],
title: action.payload.title,
url: action.payload.url
}
}
}
Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed
After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...
Three aspects of your selector definition and usage look a little odd:
getTheTab is digging down through multiple levels at once
makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
In mapState, you're passing the entire props object to theTabSelector(state, props).
I'd suggest trying this, and see what happens:
const selectSessionWindows = state => state.sessionWindows;
const selectSessionTabs = createSelector(
[selectSessionWindows],
sessionWindows => sessionWindows.sessionTab
);
const makeTheTabSelector = () => {
const selectTabById = createSelector(
[selectSessionTabs, (state, tabId) => tabId],
(sessionTabs, tabId) => sessionTabs[tabId]
);
return selectTabById;
}
export const makeMapState() => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props.tabId)
}
}
return mapStateToProps
}
No guarantees that will fix things, but it's worth a shot.
You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.
Hopefully that will let you figure things out. Either way, leave a comment and let me know.

Getting "Unreachable code no-unreachable" in ReactJS

I am new to create GET requests in React. I am trying to fetch media_id from Instagram Content when someone enters the url in the input field. The interesting thing is I do get response in Inspector for the following GET request but I am not sure how to properly execute this.
Following is my Component Code.
import React, { Component } from 'react';
export default class UrlInput extends Component {
constructor(props){
super(props);
this.state = {
term: '',
mediaid: ''
};
this.onInputChange = this.onInputChange.bind(this);
}
componentDidMount(){
const url = 'https://api.instagram.com/oembed/?url=http://instagram.com/p/Y7GF-5vftL/';
fetch(url).then(response => {
if(response.ok){
return response.json();
}
throw new Error('Request failed!');
},
networkError => console.log(networkError.message)).then(jsonResponse => {
return jsonResponse;
console.log(jsonResponse);
});
}
render(){
return (
<div className='search-bar'>
<input
value= {this.state.term}
onChange={ event => this.onInputChange(event.target.value)} />
<div>{this.state.term}</div>
</div>
);
}
onInputChange(term){
this.setState({term});
}
}
no-unreachable is just a warning that tells you that you have unreachable code in your code. In this case, it is the console.log(jsonResponse) after return jsonResponse
It is unreachable because when the code find a return statement, it will just break out of the function and not continue any further, thus the console.log(jsonResponse) will never be called.
I faced the same problem. Seems it is because the parenthesis of return should be inline like return (
and not like
return
(
This solved problem for me
Remove console.log() after the return value
Had this same issue.
Removing the semicolon on the line the warning was on solved the problem.

Resources