GraphQL Automatic refetch on empty responses - reactjs

I want to randomize movies from theMovieDB API. First I send a request to access the ID of the latest entry:
const { loading: loadingLatest, error: errorLatest, data: latestData, refetch: refetchLatest } = useQuery(
LATEST_MOVIE_QUERY
);
Then I want to fetch data from a randomly selected ID between 1 and the number of the latest id. Using a variable dependant on the first query seems to break the app, so for now I'm just using the same movie every time upon mounting the component:
const [
movieState,
setMovieState
] = useState(120);
const { loading, error, data, refetch } = useQuery(ONE_MOVIE_BY_ID_QUERY, {
variables : { movieId: movieState },
skip : !latestData
});
I want to press a button to fetch a new random movie, but the problem is that many of the IDs in the API lead to deleted entries and then I get an error back. I want to keep refetching until I get a good response back but I have no idea to implement it. Right now my randomize function just looks like this:
const randomizeClick = () => {
let mostRecentID = latestData.latestMovie.id;
setMovieState(Math.floor(Math.random() * mostRecentID));
};
I'd be grateful if someone can help me how to implement this.

I think what you needs is the "useLazyQuery" functionality of Apollo. You can find more information about it here: https://www.apollographql.com/docs/react/data/queries/#executing-queries-manually
With useLazyQuery you can change your variables easily and this is meant to be fired after a certain event (click or something similar). The useQuery functionality will be loaded when the component is mounted.

Related

How do I fix "Text content does not match server-rendered HTML" in nextjs/typescript

I am working on a nextjs/typescript project that helps users to create a list of items they want to purchase.
When a user has purchased an items, they come to the application and tick that it has been purchased. Whenever this happens, I have to update the cart on my database (firestore)
The problem is that the code I use to update the database gives me the following error on the browser:
picture of error on browser
Below is my code when the user clicks on the item.
async function toggleItemIsCompletedState() {
dispatch(
cartActions.toggleIsCompletedState({
itemId,
categoryName: props.category,
})
);
// update cart in firebase
// ================================================
// For some reason I am not able to get the updated data to push to firestore so I am going to duplicate
// the code for updating the redux-store here so I can get updated data before pushing it.
const updatedItems = toggleItemIsCompletedStateUtil(
items,
props.category,
itemId
);
if (updatedItems === false) return;
console.log(updatedItems);
const cartData: CurrentCartUploadType = {
cartTitle,
items: updatedItems,
totalQuantity,
cartState,
isEditingCart,
};
// console.log("CartData: ", cartData);
const response = await updateCart(cartData);
console.log(response);
// ================================================
}
I viewed the docs and the solution was to use Effect but there are so many dependencies that the program would always keep re-rendering besides I tried using useEffect() but I kept getting the same error.
You can find my code on GitHub: https://github.com/almamarie/shoppingify-frontend in the backend-integration branch.
The target file is components/cart/completing-state/CompletingCarItem.tsx

Use useEffect or not

Is it bad practice to use useEffect?
And should useEffect be avoided if possible due to re-renders?
This question arised yesterday when a colleague asked for a code review and we had different opinions on how to solve this.
We are creating an app that shows some kind of documentation which could be sorted in chronological or reversed chronological order. This is decided by a button in the apps top bar with a default value of chronological order, this value is stored in a global redux state and will be used in every call to fetch documentation.
In this example we update sortOrder on button click and as an effect of that we fetch data.
If I understand this correctly, we render once when sortOrder state change, and once after data is fetched.
Pseudo code ish
interface AppState = {
sortOrder: SortOrder:
documentation: Documentation[];
}
reducer(){
case toggleSortOrder:
const order = state.sortOrder === 'asc' ? 'desc' : 'asc';
return {
....state,
sortOrder: order;
}
}
const AppBar = () => {
const dispatch = useDispatch();
return <div><button onClick={dispatch(toggleSortOrder)}>Change sort order</button>
</div>;
}
const DocumentationList = (type: DocumentationType) => {
const dispatch = useDispatch();
const sortOrder = useSelector((state) => state.appState.sortOrder);
const documentation = useSelector((state) => state.appState.documentation);
useEffect(() => {
// action is caught by redux-saga and a call to docApi is made through axios
dispatch(getDocumentation.request(type, sortOrder)
},[sortOrder]);
return documentation.map((doc) => <Documentation data={doc} />);
}
Is this bad practice?
Should we avoid useEffect and fetch data on click and update sortOrder in saga instead?
Reading docs and blogs I mostly see examples of how en when to use them.
In my opinion, I would go with solution more-less like yours, with splitting responsibilities between element which externally changes query params, and element which is displaying data based on current query params. If you decide to put all logic in button click handler then you are kinda coupling too much list and button, because in order to delegate all work to button click you must dig into DocumentationList fetch-data implementation and to copy it to another place(button related saga or in button click handler) in order to fetch data from another place in the app, and not just from the DocumentationList itslef.
From my perspective, only DocumentationList should be responsible to fetch data, and noone else. But you should provide way to subscribe, from documentation list, to some external query params(sort, filters etc) and when they change(if they exist) data should be loaded.
Right now you only have sort, but in case when you can potentially have more params that can be externally modified, then I would dedicate more complex redux part to query params, something like documentationQueryParams: { sort: "asc", filters: { name: "doc a", type: "type b" } }, and then inside DocumentationList I would use custom hook, for example const queryParms = useDocumentationQueryParams(); which will return standardized query params, and in useEffect I would subscribe to those queryParms change - whenever they change I will easily fetch new data since you know what is the structure of the queryParms(they must be standardized is some way). Like this you coupled them but in very flexible way, whenever you need new param you will update only filter/query-related component, because in DocumentationList you relay on standardized hook output and you can easily create generic mechanism to output query string, or body data, in order to make new request and to fetch new data.
In terms of performance, there is really no difference between hooks-based approach and moving all to click handlers, because in DocumentationList your render part should rerender only when list change, no matter how list is being changed.

Apollo Client - fetchMore component update problem

In my NextJS application interfaced with a MongoDB API back-end - managed via GraphQL, I'm trying to implement the Apollo fetchMore feature, in order to update a component responsible to load more items from a data collection.
On page rendering, the component itself shows a "gallery" of 10 elements as its native functionality, populated via a GraphQL starting query. Then, I included a "load more" button to trigger the fetchMore function. The UX expects that if the user clicks the proper button, more 10 elements will going to be loaded in addition of the previous 10 - basically a classical async-infinite loading example.
By inspecting the app, I notice that both the queries are being returned successfully - the initialization one and the "load more 10 items" too managed by fetchMore - but the latter, after its execution, triggers the component's update that it's being re-initialized with the starter query instead of the fetchMore one.
To clarify it: on "load more" click, instead to see the next 10 gallery elements loaded - so to finally display a total of 20 - the component refreshes and displays the starter 10 elements, like its starting initialization - totally ignoring the fetchMore action, even if this one is being called, executed and received back with a populated 200 response.
Because this is my very first time in using it, I don't know if I'm missing something in my implementation, or I need to fix something. Anyway, here it goes:
Due to various reasons, I'm running the query in a parent component, then I pass the data as props to a child one:
Parent
// Initialization, etc.
[...]
const {loading: loadingIndex, error: errorIndex, data: dataIndex, fetchMore: fetchMoreIndex} = useQuery(ARTICLE_QUERY.articles.indexArticles, {
// Last 30 days
variables: {
live: live,
limit: 10
}
});
// Exception check
if (errorIndex) {
return <ErrorDb error={errorIndex} />
}
// DB fetching check
if (loadingIndex) {
return (
<section className="index-articles">
<h6>Index - Articles</h6>
<aside className="articles__loading">
<h6>Loading</h6>
</aside>
</section>
);
}
const articles = dataIndex.queryArticleContents;
return (
<IndexArticles labels={props.labels} articles={articles} fetchMore={fetchMoreIndex} />
);
Child
// Initialization, etc.
[...]
let limit = 10; // My query hypothetically limiter
const IndexArticles = (props) => {
useEffect(() => {
// This is a getter method responsible to manage the ```fetchMore``` response
getArticles(props.articles, props.fetchMore);
});
return (
<>
// Component sections
[...]
// Load button
{props.fetchMore &&
<button className="articles__load" title={props.labels.index.title} tabIndex={40}>{props.labels.index.cta}</button>
}
</>
);
function getArticles(articles, fetchMore) {
// Yes, I'm using jQuery with React. Just ignore it
$('.articles__load').on('click tap', function(e) {
e.preventDefault();
$(this).addClass('hidden');
$('.articles__loading').removeClass('hidden');
fetchMore({
variables: {
// Cursor is being pointed to the last available element of the current collection
lastLoaded: articles.length,
limit: limit += 10
},
updateQuery: (prev, {fetchMoreResult, ...rest}) => {
$('.articles__loading').addClass('hidden');
$(this).removeClass('hidden');
if (!fetchMoreResult) {
return prev;
}
return {
...fetchMoreResult,
queryArticleContents: [
...prev.queryArticleContents,
...fetchMoreResult.queryArticleContents
]
}
}
});
});
}
Anyone have experience with it or had experienced this case before?
Thanks in advance for the help
As suggested on the official community, my configuration was missing about the notifyOnNetworkStatusChange: true in the query options, which is responsible to update the component and append the new data.
By changing the code in this way:
Parent
const {
loading: loadingIndex,
error: errorIndex,
data: dataIndex,
// Add networkStatus property too in order to use notifyOnNetworkStatusChange properly
networkStatus: networkStatusIndex
fetchMore: fetchMoreIndex} = useQuery(ARTICLE_QUERY.articles.indexArticles, {
// Last 30 days
variables: {
live: live,
limit: 10
},
// Important for component refreshing with new data
notifyOnNetworkStatusChange: true
});
The problem has been solved.

How to execute query on every click using useLazyQuery()

Using useLazyQuery() hooks from #apollo/react-hooks I was able to execute a query on click of a button. But I cannot use it execute same query on consecutive clicks.
export default ({ queryVariables }) => {
const [sendQuery, { data, loading }] = useLazyQuery(GET_DIRECTION, {
variables: queryVariables
});
return <div onClick={sendQuery}>Click here</div>;
}
In the above the sendQuery executes only once.
useLazyQuery uses the default network policy that of cache-first So I supposed your onClick function actually executes but because the returned value is what was in the cache, React notices no change in data as such the state is not updated since the returned data is what it already has that way no re-render and thing seem not to have changed. I suggest you should pass in a different network policy something like
const [sendQuery, { data, loading }] = useLazyQuery(GET_DIRECTION, {
variables: queryVariables,
fetchPolicy: "network-only"
});
This will mean you want the most recent information from your api hence basically no caching.
You might also want to experiment on other option and see which one best suits you
like cache-and-network: you can find out a little more here understanding-apollo-fetch-policies
As per the docs, useLazyQuery accepts the parameter fetchPolicy.
const [lazyQuery, { data, loading }] = useLazyQuery(QUERY, {
fetchPolicy: 'no-cache'
});
With fetchPolicy set to no-cache, the query's result is not stored in the cache.

React with Redux Update only state

I'm working on integrating Redux in an already finished SPA with ReactJS.
On my HomePage I have a list of the 4 newest collections added which on render, I fetch with axios from my database. These are then saved in Redux Store and displayed on the React UI.
My mapStateToProps look something like this:
const mapStateToProps = (state) => ({
credentials: credentials(state),
collections: collections(state)
});
Where credentials is irrelevant and collections is:
const collections = (state) => {
if (state.collectionsHomeViewReducer.fetching === true) {
return {
fetchingCollections: true
}
}
else if (state.collectionsHomeViewReducer.data) {
const response = state.collectionsHomeViewReducer.data;
return {
collections: response.collections,
fetchedCollections: true,
fetchingCollections: false
}
}
else if (state.collectionsHomeViewReducer.fetched === false) {
return {
fetchedCollections: false,
fetchingCollections: false
}
}
};
What is it I want to do:
Update the store state every time another client, or the current client, adds a new collection. Moreover, I do not wish for the UI to update immediately after I dispatch(action), I want it to update when a user refreshes the page or when he navigates to another view and returns ( I believe what I'm trying to say is when componentDidMount is called ).
What have I achieved so far:
By using socket.io, I
socket.emit("updateCollectionsStore")
socket.on("updateCollectionsStore")
and
socket.broadcast.emit("updateCollectionsStore")
in their respective places in the application. The final call of
socket.on("updateCollectionsStore")
after the broadcast, is in the main file of the page, app.jsx where the store is also located. The function there looks like this:
socket.on("updateCollectionsStore", () => {
store.dispatch(getCollectionsHomeView());
});
The store is updated and everything works fine, as viewed from the Redux Dev Tools.
What I can't seem to figure out is to tell the props not to change due to the fact that mapStateToProps is called every time an action is dispatched.
Why do I need this: The HomePage can deal with a continuous UI update and data fetching but I also have a page ReadAllPage where you can real all collections. The problem is if there will always be the newest post on the top, whenever a new one is added, the current one is pushed downwards. In case somebody had the intent to click the one that was pushed down, now he might have accidentally clicked the one that took its place, which is not wanted.
What else should I do different or further to achieve the result I want?
Thank you.
According to your needs, I would have two properties in the state. First is that is currently visible on the HomeView and the second is that is updated via sockets. Once a user navigates to the HomeView you can just replace the first collection with the second one.

Resources