Control html dynamically without component re rendering in React with Apollo - javascript

In my current situation, I use Apollo useQuery to fetch a user and their posts.
The posts render in a summary view and in a detailed view. the detailed view is sitting on top of the summary view and its visibility is controlled by a piece of state.
const { data: userData, error: userError } = useQuery(GET_USER_BY_ID, { // the data fetch
variables: {
userId: getUserId(),
}
})
const [showPosts, setShowPosts] = useState(false) // controls toggle between views
the problem im having is every time the showPosts state changes the component re renders and the useQuery is run again. which I do not want. I have already got all my data, I just want to render html without hitting the server again until I need to. What is a potential solution to my problem?

Check skip param, You can do something like this:
const { data: userData, error: userError } = useQuery(GET_USER_BY_ID, { // the data fetch
variables: {
userId: getUserId(),
skip:!showPosts
}
})
or use useLazyQuery

Related

redux toolkit using older data in slice to minimize calls to server

I'm fairly new to developing web apps, I started learning react + redux toolkit while using Django as a backend framework
to my point,
I was trying to minimize calls to the server by using a useEffect to check if the value of a specified selector is filled with data, so then I can use that data instead of calling the server again
now when I make the check
useEffect(() => {
flights.value.length <= 0 && dispatch(fetchFlights())
// eslint-disable-next-line
}, [])
it works when you first call the component
but after that, every time I open that component (whether I click on its link, or using a navigate(-1) to go back to that component) it won't display anything. I'll need to manually refresh the page for it to work correctly
this is for the component to render the data via a map function (works as it displays it when first calling it)
{!logged ? <Login /> : flights.loading ? <div>loading..</div> : flights.value.length > 0 && flights.value.map(...)}
now if i change the useEffect to this:
useEffect(() => {
dispatch(fetchFlights())
// eslint-disable-next-line
}, [])
basically without the data check, it works just fine
I was wondering if there is a way to check for the data and have it displayed without a call to the server again
or hear your thoughts about calling the server again and again and maybe its just better that way?
If you are using redux-toolkit, createApi feature is the best option. You can use the fetched data across your app without retrieving it multiple times or refresh the obtained data based on your needs (polling, caching, manual refetching, invalidating it after a certain time... )
// Need to use the React-specific entry point to allow generating React hooks
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
// Define a service using a base URL and expected endpoints
export const fligthsApi = createApi({
reducerPath: 'flights',
baseQuery: fetchBaseQuery({ baseUrl: 'https://yourapi.com' }),
endpoints: (builder) => ({
getFlights: builder.query({
query: () => `/yourFlightsPath`,
}),
}),
})
// Export hooks for usage in function components, which are
// auto-generated based on the defined endpoints
export const { useGetFligthsQuery } = fligthsApi
The you can use it in your app like:
export default function App() {
// Even if this component is unmount, flights data will be cached
const { data, error, isLoading } = useGetFligthsQuery()
// render UI based on data and loading state
}
(This is a minimal example, complete working code needs importing the api in your store)

I want to access my state variable from one component to other

I have a react query which writes the state variable- follower, and I want to access this variable in other component to find its .length can someone tell me how do I do it
const ModalFollower = ({profile}) => {
const [follower,setFollower] = useState([])
const {
data: followerName,
isLoading: followerLoading,
isFetching: followerFetching
} = useQuery(["invitations", profile?.id], () => {
getFollowers(profile?.id).then((response) => {
if (response) {
setFollower(response);
}
});
});
return(
{
!followerLoading && (
follower.map((e) => {
return(<>
<p>{e.requested_profile.Userlink}</p>
</>}
)
}
)
I want to access the length of follower in some other component
There is no need to copy data from react-query to local state, because react-query is a full-blown state manager for server state. As long as you use the same query key, you will get data from its cache. This is best abstracted away in custom hooks.
Please be aware that with the default values, you will get a "background refetch" if a new component mount, so you will see two network requests if you use it twice. That might look confusing at first, but it is intended, as it is not react-query's primary goal to reduce network requests, but to keep your data on the screen as up-to-date as possible. So when a new component mounts that uses a query, you'll get the stale data from the cache immediately, and then a background refetch will be done. This procedure is called stale-while-revalidate.
The best way to customize this behaviour is to set the staleTime property to tell react-query how long your resource is "valid". For that time, you will only get data from the cache if available. I've written about this topic in my blog here: React Query as a State Manager.
React Query also provides selectors, so if your second component is only interested in the length, this is what my code would look like:
const useInvitations = (profile, select) =>
useQuery(
["invitations", profile?.id],
() => getFollowers(profile?.id),
{
enabled: !!profile?.id
select
}
)
Note that I also added the enabled property because apparently, profile can be undefined and you likely wouldn't want to start fetching without that id.
Now we can call this in our main component:
const ModalFollower = ({profile}) => {
const { data } = useInvitations(profile)
}
and data will contain the result once the promise resolves.
In another component where we only want the length, we can do:
const { data } = useInvitations(profile, invitations => invitations.length)
and data will be of type number and you will only be subscribed to length changes. This works similar to redux selectors.

Next/React-Apollo: React props not hooked up to apollo cache when query comes from getInitialProps

I'm using nextjs and react-apollo (with hooks). I am trying to update the user object in the apollo cache after a mutation (I don't want to refetch). What is happening is that the user seems to be getting updated in the cache just fine but the user object that the component uses is not getting updated. Here is the relevant code:
The page:
// pages/index.js
...
const Page = ({ user }) => {
return <MyPage user={user} />;
};
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const user = await apolloClient.query({ query: GetUser }).then(({ data: { user } }) => user);
return { user };
};
export default Page;
And the component:
// components/MyPage.jsx
...
export default ({ user }) => {
const [toggleActive] = useMutation(ToggleActive, {
variables: { id: user.id },
update: proxy => {
const currentData = proxy.readQuery({ query: GetUser });
if (!currentData || !currentData.user) {
return;
}
console.log('user active in update:', currentData.user.isActive);
proxy.writeQuery({
query: GetUser,
data: {
...currentData,
user: {
...currentData.user,
isActive: !currentData.user.isActive
}
}
});
}
});
console.log('user active status:', user.isActive);
return <button onClick={toggleActive}>Toggle active</button>;
};
When I continuously press the button, the console log in the update function shows the user active status as flipping back and forth, so it seems that the apollo cache is getting updated properly. However, the console log in the component always shows the same status value.
I don't see this problem happening with any other apollo cache updates that I'm doing where the data object that the component uses is acquired in the component using the useQuery hook (i.e. not from a query in getInitialProps).
Note that my ssr setup for apollo is very similar to the official nextjs example: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
The issue is that you're calling the client's query method. This method simply makes a request to the server and returns a Promise that resolves to the response. So getInitialProps is called before the page is rendered, query is called, the Promise resolves and you pass the resulting user object down to your page component as a prop. An update to your cache will not trigger getInitialProps to be ran again (although I believe navigating away and navigating back should), so the user prop will never change after the initial render.
If you want to subscribe to changes in your cache, instead of using the query method and getInitialProps, you should use the useQuery hook. You could also use the Query component or the graphql HOC to the same effect, although both of these are now deprecated in favor of the new hooks API.
export default () => {
const { data: { user } = {} } = useQuery(GetUser)
const [toggleActive] = useMutation(ToggleActive, { ... })
...
})
The getDataFromTree method (combined with setting the initial cache state) used in the boilerplate code ensures that any queries fetched for your page with the useQuery hook are ran before the page render, added to your cache and used for the actual server-side rendering.
useQuery utilizes the client's watchQuery method to create an observable which updates on changes to the cache. As a result, after the component is initially rendered server-side, any changes to the cache on the client-side will trigger a rerender of the component.

How to make data from localStorage reactive in Vue js

I am using localStorage as a data source in a Vue js project. I can read and write but cannot find a way to use it reactively. I need to refresh to see any changes I've made.
I'm using the data as props for multiple components, and when I write to localStorage from the components I trigger a forceUpdate on the main App.vue file using the updateData method.
Force update is not working here. Any ideas to accomplish this without a page refresh?
...............
data: function () {
return {
dataHasLoaded: false,
myData: '',
}
},
mounted() {
const localData = JSON.parse(localStorage.getItem('myData'));
const dataLength = Object.keys(localData).length > 0;
this.dataHasLoaded = dataLength;
this.myData = localData;
},
methods: {
updateData(checkData) {
this.$forceUpdate();
console.log('forceUpdate on App.vue')
},
},
...............
Here's how I solved this. Local storage just isn't reactive, but it is great for persisting state across refreshes.
What is great at being reactive are regular old data values, which can be initialized with localStorage values. Use a combination of a data values and local storage.
Let's say I was trying to see updates to a token I was keeping in localStorage as they happened, it could look like this:
const thing = new Vue({
data(){
return {
tokenValue: localStorage.getItem('id_token') || '',
userValue: JSON.parse(localStorage.getItem('user')) || {},
};
},
computed: {
token: {
get: function() {
return this.tokenValue;
},
set: function(id_token) {
this.tokenValue = id_token;
localStorage.setItem('id_token', id_token)
}
},
user: {
get: function() {
return this.userValue;
},
set: function(user) {
this.userValue = user;
localStorage.setItem('user', JSON.stringify(user))
}
}
}
});
The problem initially is I was trying to use localStorage.getItem() from my computed getters, but Vue just doesn't know about what's going on in local storage, and it's silly to focus on making it reactive when there's other options. The trick is to initially get from local storage, and continually update your local storage values as changes happen, but maintain a reactive value that Vue knows about.
For anyone facing the same dilemma, I wasn't able to solve it the way that I wanted but I found a way around it.
I originally loaded the data in localStorage to a value in the Parent's Data called myData.
Then I used myData in props to populate the data in components via props.
When I wanted to add new or edit data,
I pulled up a fresh copy of the localStorage,
added to it and saved it again,
at the same time I emit the updated copy of localStorage to myData in the parent,
which in turn updated all the data in the child components via the props.
This works well, making all the data update in real time from the one data source.
As items in localstorage may be updated by something else than the currently visible vue template, I wanted that updating function to emit a change, which vue can react to.
My localstorage.set there does this after updating the database:
window.dispatchEvent(new CustomEvent('storage-changed', {
detail: {
action: 'set',
key: key,
content: content
}
}));
and in mounted() I have a listener which updates forceRedraw, which - wait for it - force a redraw.
window.addEventListener('storage-changed', (data) => {
this.forceRedraw++;
...

Prevent componentDidMount from fetching data if already available from server-side

ComponentDidMount() is triggered when the component is mounted, including when it is hydrated following server-side rendering.
One of the solutions I found online is checking whether we have data in the state; however this requires a lot of code to include on every component. What are other solutions?
componentDidMount() {
// if rendered initially, we already have data from the server
// but when navigated to in the client, we need to fetch
if (!this.state.data) {
this.constructor.fetchData(this.props.match).then(data => {
this.setState({ data })
})
}
}
I have found an alternative solution. In my Redux store I keep the URL of the current page. Therefore on navigation, I am able to do the following:
componentDidMount() {
const { url, match } = this.props;
if (url !== match.url) {
fetchData(match.path);
}
}
Just use a boolean variable in the store, I just use one called "done", when the server fetch the data it set the variable to true, in the component in compoponentDidMount just check if the variable is true, if is, then dont fetch the data, like this:
componentDidMount() {
if(!this.props.done)
this.props.fetchData();
}

Categories

Resources