Dynamically look up data at request time using GetServerSideProps - next.js

I'm using getServerSideProps to dynamically look up data at request time.
First my application will get data from pageNumber = 1.
export default function Index({ cmsData }: IndexProps) {
return (
// soem code
<button> Button Example</button>
)
}
export const getServerSideProps: GetServerSideProps = async ({ pageNumber }) => {
//Some code to get data from page number
return {
props: {
.....
},
}
}
I want to when click on Button Example, my app will fetch data again with pageNumber = 2.
How can I do that ?

Based on your comment you can do this:
Have a function to make API calls.
Get the query parameter from the context object to get the pageNumber. (If you always want to make call for the first page, you can directly pass 1).
Pass the props to the NextPage component (named Index) and initialise state using that.
In index component, you can also use add an event handler on button to make another API call and set state.
function makeAPICall(pageNumber : number) {
//Code to make API Call and return data
}
export default function Index({ cmsData }: IndexProps) {
const [data,setData] = useState(cmsData);
return (
// soem code
<button onclick={async() => {
let result = await makeAPICall(2);
setData(result.data);
}}> Button Example</button>
)
}
export const getServerSideProps: GetServerSideProps = async (context ) => {
let pageNumber = context.query.pageNumber;
const result = await makeAPICall(pageNumber);
let data= result.data;
//Some code to get data from page number
return {
props: {
cmsData : data;
},
}
}
Your first API call will always be made when you request the page from the browser because you are using GSSP. After that it will be triggered by your click.

Related

React Query - useQuery callback dependent on route parameter? [duplicate]

When page is refreshed query is lost, disappears from react-query-devtools.
Before Next.js, I was using a react and react-router where I would pull a parameter from the router like this:
const { id } = useParams();
It worked then. With the help of the, Next.js Routing documentation
I have replaced useParams with:
import { usePZDetailData } from "../../hooks/usePZData";
import { useRouter } from "next/router";
const PZDetail = () => {
const router = useRouter();
const { id } = router.query;
const { } = usePZDetailData(id);
return <></>;
};
export default PZDetail;
Does not work on refresh. I found a similar topic, but manually using 'refetch' from react-query in useEffects doesn't seem like a good solution. How to do it then?
Edit
Referring to the comment, I am enclosing the rest of the code, the react-query hook. Together with the one already placed above, it forms a whole.
const fetchPZDetailData = (id) => {
return axiosInstance.get(`documents/pzs/${id}`);
};
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {});
};
Edit 2
I attach PZList page code with <Link> implementation
import Link from "next/link";
import React from "react";
import TableModel from "../../components/TableModel";
import { usePZSData } from "../../hooks/usePZData";
import { createColumnHelper } from "#tanstack/react-table";
type PZProps = {
id: number;
title: string;
entry_into_storage_date: string;
};
const index = () => {
const { data: PZS, isLoading } = usePZSData();
const columnHelper = createColumnHelper<PZProps>();
const columns = [
columnHelper.accessor("title", {
cell: (info) => (
<span>
<Link
href={`/pzs/${info.row.original.id}`}
>{`Dokument ${info.row.original.id}`}</Link>
</span>
),
header: "Tytuł",
}),
columnHelper.accessor("entry_into_storage_date", {
header: "Data wprowadzenia na stan ",
}),
];
return (
<div>
{isLoading ? (
"loading "
) : (
<TableModel data={PZS?.data} columns={columns} />
)}
</div>
);
};
export default index;
What you're experiencing is due to the Next.js' Automatic Static Optimization.
If getServerSideProps or getInitialProps is present in a page, Next.js
will switch to render the page on-demand, per-request (meaning
Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your
page automatically by prerendering the page to static HTML.
During prerendering, the router's query object will be empty since we
do not have query information to provide during this phase. After
hydration, Next.js will trigger an update to your application to
provide the route parameters in the query object.
Since your page doesn't have getServerSideProps or getInitialProps, Next.js statically optimizes it automatically by prerendering it to static HTML. During this process the query string is an empty object, meaning in the first render router.query.id will be undefined. The query string value is only updated after hydration, triggering another render.
In your case, you can work around this by disabling the query if id is undefined. You can do so by passing the enabled option to the useQuery call.
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {
enabled: id
});
};
This will prevent making the request to the API if id is not defined during first render, and will make the request once its value is known after hydration.

why does this vuelidate function not work within setup, but only outside, within methods?

I'm using vuelidate with the composition API and I don't understand why the v$.validate() works correctly when I put within methods, after setup, but not within setup.
So this works:
setup() {
// inspired from
// https://vuelidate-next.netlify.app/#alternative-syntax-composition-api
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
return {
state,
v$
}
},
methods: {
async submitForm () {
const result = await this.v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
}
but, this doesn't work:
setup() {
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
const submitForm = async () {
// **ERROR : Uncaught (in promise) TypeError: v$.$validate is not a function**
const result = await v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
return {
state,
v$,
submitForm
}
}
That's a bit of a pain, because I use a composable for the axios call where the state is an argument. Would be easier to keep the entire code in one place.
Composition API
useVuelidate returns a computed, so you need to use .value when accessing any of it's properties, like $error, $validate inside the setup function.
In the template it is unwrapped for you.

Show dynamic loader when requesting ssr props

The problem:
getServerSideProps is blocking the whole site on subsequent requests, even when only the props are requested and all other js is already loaded.
So I was wondering if it is possible to add a loading component to each page (something like the dynamic layout: https://github.com/vercel/next.js/tree/canary/examples/layout-component)
and show it instantly while waiting till the props are loaded.
I know about the Router from nextjs and the events but how would I show the loader when I got only the url on routeChangeStart?
I use getInitialProps now. Thought it was deprecated, but it's not.
I can now do something like this:
const MyPage = (props) => {
const [data, setData] = useState(null)
useEffect(() => {
if (props.data) {
setData(props.data)
} else {
// fetch client side and setData
}
});
return (
<>
{data === null && <LoadingMyPage/>}
{data && <TheActualContent/>}
</>
);
}
MyPage.getInitialProps = async () => {
if (window) return {data: undefined};
// fetch server side
return {data: ...}
}

Setting initial state using recoil in a nextjs app with ISR and SWR

I'm trying to figure out how to set my initial recoil state while still using nextjs`s ISR feature.
So I made a product.ts file inside of a states directory the file contains the following code
const productsState = atom({
key: 'productState',
default: []
})
I thought about calling my api here and instead of setting the default as an empty array have it filled with data from the api call, but I'm sure I would lose out of ISR and SWR benefits that nextjs brings?
So I thought about setting initial state inside of the getStaticProps method
export const getStaticProps: GetStaticProps = async () => {
const res: Response = await fetch("http://127.0.0.1:8000/api/products");
const {data} = await res.json();
return {
props: {
data
},
revalidate: 10
}
}
But this would only run once on build time so data would be stale, so I made a hook to get my products using SWR
import useSWR from "swr";
import { baseUrl} from '../lib/fetcher'
export const useGetProducts = (path: boolean | string, options: {} = {}) => {
if (!path) {
throw new Error("Path is required")
}
const url = baseUrl + path
const {data: products, error} = useSWR(url, options)
return {products, error}
}
this is then called inside of the page component
const Home: NextPage = ({data}: InferGetStaticPropsType<typeof getStaticProps>) => {
const {products, error} = useGetProducts('/api/products', {
initialData: data,
})
}
Now I'm just wondering if this is a viable way to set initial state in recoil without sacrificing ISR and SWR benefits?
coming from vue/nuxt I would make a global store where I would call my api and set state to the api data, but it seems in react/recoil it's a bit different?

React redux separation of concerns

I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).
What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.
The async logic relying on redux-thunk middleware and q:
// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
...
}
function getAsync(date) {
return function (dispatch) {
return goGetAPIUrl(date).then(
val => dispatch(gotURL(val)),
error => dispatch(apologize(error))
);
};
}
export default function eventuallyGetAsync(event, date) {
if(event.which == 37...) {
return getAsync(date);
} else {
return {
type: "NOACTION"
}
}
}
Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
actions.eventuallyGetAsync(event, gridAppState.date);
});
}
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
}
App.propTypes = {
actions: PropTypes.object.isRequired,
gridAppState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.
What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?
You should handle the event in your component and call the correct action depending on the key pressed.
So when you dispatch an async action you can do something like
export default function getNextPhoto(currentDate) {
return (dispatch) => {
const newDate = calculateNewDate(currentDate);
dispatch(requestNewPhoto(newDate));
return photosService.getPhotoOfDate(newDate)
.then((response) => {
dispatch(newPhotoReceived(response.photoURL);
});
};
}
You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.
Your App would look like
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
if (event.which == 37) {
actions.getNextPhoto(gridAppState.date);
} else if (...) {
actions.getPrevPhoto(gridAppState.date);
}
// etc
});
}
}
By the way you re still missing your reducers that update your state in the Redux Store.

Resources