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)
Related
I would like to fetch data from local storge (in runtime) every time the app starts, then I store it in a store.
Gatsby docs explain the way to do this:
https://www.gatsbyjs.com/docs/conceptual/data-fetching/
Basically just use the useEffect hook in a page and get the data from local storage. However, I would like to get this data independently of the page being visited. For example, if I fetch the data on the index page, and the user refreshes another page, the data won't be fetched. I would like to do it in the equivalent of App.tsx file in a regular React app.
My current solution is to do it in wrap-pages file:
const MyLocalStorage = ({ children }) => {
const { getLocalStorage} = fromStore()
useEffect(() => {
getLocalStorage() // fetches data from local storage
}, [])
return null
}
export function wrapPagesDeep({ element }) {
return (
<>
<MyLocalStorage/>
{element}
</>
)
}
This however doesn't make much sense. This file is intended for wrapping components, not for data fetching. What would be the correct way to do that please?
There are multiple ways depending on your architecture, design system, and use cases (for example, from a provider to a wrapper, from an isolated service to a controller, etc.).
There is no such thing as "best practice" without knowing everything involved in the decision: making some super complicated and isolated logic (like adding an MVVM: controller, stores, etc.) may look good but can be an extremely bad practice for a simple scenario, and vice-versa: an easy and straightforward approach can be a bad solution for a complex app.
Following your approach, I think it could be easily isolated (and reused) by moving this logic into a Layout (or a wrapper that wraps your application) and adding a location prop to it. Something like:
const Layout = ({ children, location = {} }) => {
const { getLocalStorage} = fromStore()
useEffect(() => {
getLocalStorage() // fetches data from local storage
}, [location])
return (
<>
<main>{children}</main>
</>
)
}
export default Layout
Then, in every use of Layout:
const SomePage = ({ location }) => {
return (
<Layout location={location}>
<h1>Some content</h1>
</Layout>
);
};
Note: location prop is inherited by default in all top-level components (pages and templates) as you can see in the docs
So every time the location changes, you will fetch the local storage data. This can be easily moved to a provider that updates the value automatically. You will only need to wrap your application accordingly.
I have a react component called Sidebar.jsx. Within it, I am making an API call to get a array of fleets to populate an eventual JSX dropdown element within my Sidebar. This results in a simple JSON array.
I have imported a function called getFleets() from my services folder to make the API call. The service uses the fetch API to make a query call to my backend and looks like this:
export async function getFleets() {
const resp = await fetch("http://localhost:5000/fleets", {
method: 'GET',
headers: {},
mode: 'cors'
});
return resp.json();
};
However, when I use the website, it appears to infinitely make the API call. This is my first time trying to make an API call within a react component so I am a bit confused here. Other guides I've read online seem to be similar but I am obviously missing something.
What can I do to make this API call only once and retrieve my JSON array such that I can later use it to populate the options in my return ?
Sidebar.jsx
import React, { useEffect, useState } from "react";
import { getFleets } from "../services/FleetService";
const Sidebar = () => {
const [data, setData] = useState([]);
useEffect(() => {
const setFleets = async () => {
const fleets = await getFleets();
console.log(fleets);
setData(fleets);
}
setFleets();
}, [data]);
return (
<>
// Add data to <select> </select>
);
};
The way your code works, since data is part of the dependency array sent to useEffect, every time data changes the effect runs, which changes data, which runs the effect again ...resulting in the infinite loop.
The simple fix is to remove data from the dependency array, and explicitly specifying an empty array [] as the second parameter of useEffect. This will make the effect run only exactly once, when the component is first rendered.
You need to explicitly specify an empty array because when the second parameter isn't specified at all, the effect will run on every render, bringing back the infinite loop issue.
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.
I have recently been trying to create a web app with NextJS. I know some basics in web development but I was a little lost when using NextJS as I didn't do any React either before.
I've tried fetching data from an API and using this data in my page. I struggled a bit but in the end I got it working with the help of getServerSideProps.
My question is, how could I use getServerSideProps multiple times in my application so that I can fetch many other routes ? I've tried using getServerSideProps in a different file, using its response in a function that I then export as a component and use it so I can "get components of getServerSideProps responses" if it makes sense, but had many different errors when trying to do so.
Could someone explain how it actually works and how I could resolve my issue, and if it doesn't work that way, how could I make it work?
Here's an example using Coinbase's API :
import { useState } from 'react'
import fetch from 'isomorphic-fetch'
export const getServerSideProps = async () => {
const res = await fetch('https://api.coinbase.com/v2/prices/ETH-USD/buy')
const data = await res.json()
return {
props: {
ethprice: data
}
}
};
I then use "ethprice" in my Home function such as :
export default function Home({ ethprice }) {
return (
[page content, divs, text etc...]
{etherprice.data.amount}
Thanks!
getServerSideProps is specific to that particular file, you can't just use it in any way you want.
const Example = (props) => {
return // this is your component
}
export const getStaticProps = async () => {
// this will provide props specifically for 'Example'
}
More than that getStaticProps will only be run once on static page generation and never again, along with fetching the props for that particular component only. So you can't get live data from it, only data required to generate the page (like page title).
You can have a look at getServerSideProps if you're looking for something more dynamic that can fetch props at runtime. After that you can pass those props down to children if you need to.
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.