Batching with useQuery react hooks getting back undefined - undefined

I am currently working on a project which requires me to make multiple queries/mutations. I tried setting up my apollo client with BatchHttpLink and I can see the data I am requesting in the network tab in the browser. It is coming back at an array of objects instead of JSON.
But the issue is when I try to grab the data in my component data is undefined. I tried using HttpLink instead of BatchHttpLink and I can get the data back from the hook.
My suspicion is the shape of the object that comes back from the response is different, I tried looking into documentation but I can't find much about batching.
Currently using "#apollo/client#^3.0.2"
Here's my client set up.
import { ApolloClient, InMemoryCache, ApolloLink, from } from '#apollo/client'
import { BatchHttpLink } from '#apollo/client/link/batch-http'
import { onError } from '#apollo/client/link/error'
const BASE_URL = 'http://localhost:4000'
const httpLink = new BatchHttpLink({
uri: BASE_URL,
credentials: 'include',
})
const csrfMiddleware = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
'X-CSRF-Token': getCSRFToken(),
},
}))
return forward(operation)
})
const errorMiddleware = onError(({ networkError }) => {
if (networkError && 'statusCode' in networkError && networkError.statusCode === 401) {
window.location.assign('/accounts/login')
}
})
const client = new ApolloClient({
link: from([errorMiddleware, csrfMiddleware, httpLink]),
cache: new InMemoryCache(),
})
This is the react hook I'm trying to console log.
const {data} = useQuery(GET_USER_PERMISSIONS_AND_PREFERENCES)

Figured it out. You need to add another middleware to return the data that the useQuery hook can recognize. The data that comes back in the batch call is an array of objects shaped
{
payload: {
data: { ... }
}
}
So something like this did the trick for me
const batchParseMiddleware = new ApolloLink((operation, forward) => {
return forward(operation).map((data: any) => data.payload)
})

I have been having a similar issue, and have so far only been able to solve it by breaking batching and converting to a normal HttpLink

Related

I can't fill a request response using axios in state variable in React.js with Next.js

I'm working with React.js and I have the following problem:
import axios from "axios";
export default function Home() {
const [products, setProducts] = useState([]);
const ax = axios.create({ headers: { Accept: 'application/json' }});
function test() {
const res = ax.get("https://vtexstore.codeby.com.br/api/catalog_system/pub/products/search").then((response) => {
// expected the setProducts to be filled with the return of this request
setProducts(response.data);
});
}
test();
// and when I get here to see if the products have been filled, I get an empty array [ ]
console.log(products);
/*
as the products variable was not filled within the axios promise by setProducts,
there is no way to throw the products array here in the HTML to make a forEach or
a map to look cute together with the tags
*/
return (
<sup>how sad, with the product array empty, I can't put the data here '-'</sup>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
See how the result comes out in the IDE console:
I'm in Visual Studio not knowing what to do, I'm new to ReactJS with NextJS and from an early age I've been trying to see if I could solve this problem, but without success.
What can I do to bring the products to the HTML page?
UPDATE: As per the solution below, I created a possible workaround that indicates a path that could have returned a solution
ax.get("https://vtexstore.codeby.com.br/api/catalog_system/pub/products/search/", {})
.then((response) => setProducts(response.data))
.catch((error) => {
console.log(error); // AxiosError {message: 'Network Error', name: 'AxiosError', ...}
console.log(error.status); // undefined
console.log(error.code); // ERR_NETWORK
});
useEffect(() => {
console.log(products);
}, []);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.2/umd/react-dom.production.min.js"></script>
and I'm getting the same error that I put in the comments of the first answer below:
but when I change the setProducts by the console.log to see if it returns the same result, this appears in the terminal where my next.js application is running
that:
ax.get("https://vtexstore.codeby.com.br/api/catalog_system/pub/products/search/", {})
.then((response) => console.log(response.data.length)) // returns the length of the products array
returns this when I update my app:
NOTE: That's why I'm not able to understand my application in Next.js. I'm following all the correct guidelines, writing the code perfectly using axios and when I run the application on the website it gives a network error and doesn't show exactly the amount of products that were displayed in the terminal where my application is running.
I've already configured all the request headers correctly, enabling CORS to allow external requests with other API's, and I still don't succeed in returning the data to my application's page.
Wrap the stuff you have to fetch products inside useEffect hook
useEffect(()=>{
const ax = axios.create({ headers: { Accept: 'application/json' }});
function test() {
const res = ax.get("https://vtexstore.codeby.com.br/api/catalog_system/pub/products/search").then((response) => {
// expected the setProducts to be filled with the return of this request
setProducts(response.data);
console.log(response.data)
});
}
test();
},[])
Then in your return of the component, you can use map on products array with null and undefined checks
Like
{products && products.map(product=>{})}

How to save imported JSON file with Expo Filesystem

I have been working on a React Native project with Expo that uses a json file to store local data. I am importing the data like so
import data from '../database.json'
I am making changes (adding and removing) to the imported JSON by using data.push(new_data). These changes are not persistent when I close the app because I cannot figure out how to save them. I have looked at using the expo-file-system library as so:
import * as FileSystem from 'expo-file-system';
...
FileSystem.writeAsStringAsync(FileSystem.documentDirectory + 'database.json', data);
This is from looking at examples in the API documentations. This however always throws promise rejections and doesn't end up writing the file. Can you point me in the right direction?
Also, should I import the database.json in a different way so I will already have the uri to save it to?
The documentation doesn't give an example of it's returned props in promises, so I was overlooking it for longer than I care to admit 😅. I was really dedicated to figuring this out so I could use the Expo solution, and totally missed the return Promise for createFileAsync, so hopefully this saves someone a significant amount of time in the future.
import * as FileSystem from 'expo-file-system';
const { StorageAccessFramework } = FileSystem;
const saveFile = async () => {
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
// Check if permission granted
if (permissions.granted) {
// Get the directory uri that was approved
let directoryUri = permissions.directoryUri;
let data = "Hello World";
// Create file and pass it's SAF URI
await StorageAccessFramework.createFileAsync(directoryUri, "filename", "application/json").then(async(fileUri) => {
// Save data to newly created file
await FileSystem.writeAsStringAsync(fileUri, data, { encoding: FileSystem.EncodingType.UTF8 });
})
.catch((e) => {
console.log(e);
});
} else {
alert("You must allow permission to save.")
}
}
Use AsyncStorage instead. The react native package is deprecated but working, or use #react-native-community/async-storage and convert json to string (AsyncStorage can only store strings)
Set item
import AsyncStorage from '#react-native-community/async-storage';
...
await AsyncStorage.setItem('myData', JSON.stringify(data))
Get item
const data = await AsyncStorage.getItem('myData')
I found #JayMax answer very helpful however it's only for Android.
On iOS all you need to do is use Sharing.shareAsync and then you can save data to the file. Check this example:
const fileUri = FileSystem.documentDirectory + 'data.txt';
FileSystem.writeAsStringAsync(fileUri, 'here goes your data from JSON. You can stringify it :)', {
encoding: FileSystem.EncodingType.UTF8,
});
const UTI = 'public.text';
Sharing.shareAsync(fileUri, {UTI}).catch((error) => {
console.log(error);
});
If you using AsyncStorage, it only store for small data. Maybe 6mb or 10 mb.
You can use expo fileSystem
import * as FileSystem from 'expo-file-system';
...
FileSystem.writeAsStringAsync(FileSystem.documentDirectory + 'database.json', data);
Convert your data (Type json to string) Such as this:
writeData = async () => {
var persons = ''
await axios.get(`http://192.168.0.48:4000/api/sql/student`)
.then(res => {
persons = res.data
})
await FileSystem.writeAsStringAsync(FileSystem.documentDirectory + `offline_queue_stored.json`, JSON.stringify(persons));
}
#1.If the JSON File is in your Project Folder (PC/Laptop)
import data from './database.json';
#2. If the JSON File is in your Phone
import * as FileSystem from 'expo-file-system';
import * as DocumentPicker from 'expo-document-picker';
this.state = {
fileURI: null,
};
componentDidMount = () =>{
this._pickDocument();
}
_pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({});
this.setState({
fileURI: result.uri
})
let fileData = await FileSystem.readAsStringAsync(this.state.fileURI)
console.log(fileData)
};

Accessing Vuex Store Before Page Load NuxtJS

Context: I am trying to get Google Maps place data via the place_id on the beforeEnter() route guard. Essentially, I want the data to load when someone enters the url exactly www.example.com/place/{place_id}. Currently, everything works directly when I use my autocomplete input and then enter the route but it does not work when I directly access the url from a fresh tab. I've been able to solve this using the beforeEnter() route guard in traditional Vue, but cannot solve for this using Nuxt. Please help!
Question: How can I access the Vuex Store before a page loads in Nuxt?
Error: Any solution I try (see below) I either end up with a blank page or the page will not load (I think it is stuck in a loop and cannot resolve the Promise).
Attempted Solutions:
Using Middleware like below:
middleware({ store, params }) {
return store.dispatch('myModule/fetchLocation', params.id)
}
Using asyncData like below:
data(){
return{
filteredLocation: {}
}
}
// snip
async asyncData({ store, params }) {
const { data } = await store.dispatch('myModule/fetchLocation', params.id)
return filteredLocation = data
}
I tried looking into fetch, but apparently you no longer have access to context
Example Code:
In one of my store modules:
/* global google */
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
export const state = () => ({
selectedLocation: {}
})
export const actions = {
fetchLocation({ commit }, params) {
return new Promise((resolve) => {
Vue.$gmapApiPromiseLazy().then(() => {
const request = {
placeId: params,
fields: [
'name',
'rating',
'formatted_phone_number',
'geometry',
'place_id',
'website',
'review',
'user_ratings_total',
'photo',
'vicinity',
'price_level'
]
}
const service = new google.maps.places.PlacesService(
document.createElement('div')
)
service.getDetails(request, function(place, status) {
if (status === 'OK') {
commit('SET_PLACE', place)
resolve()
}
})
})
})
}
}
export const mutations = {
SET_PLACE: (state, selection) => {
state.selectedInstructor = selection
}
}
EDIT: I already have it in a plugin named google-maps.js and in my nuxt.config.js file I have:
plugins: [
{ src: '~/plugins/google-maps.js' }
]
//
//
build: {
transpile: [/^vue2-google-maps.js($|\/)/],
extend(config, ctx) {}
}
Using Middleware is how we can access Vuex before page loads. try putting the configuration part in a custom Nuxt plugin.
Create a file in Plugins folder (you can name it global.js).
Put this
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
in global.js.
Then add the plugin in nuxt.config.js like this.
plugins: [
'~/plugins/global.js'
]
Also, make sure you're using underscore before 'page_id' name in your folder structure.

Next.js Redirect from / to another page

I'm new in Next.js and I'm wondering how to redirect from start page ( / ) to /hello-nextjs for example. Once user loads a page and after that determine if path === / redirect to /hello-nextjs
In react-router we do something like:
<Switch>
<Route path="/hello-nextjs" exact component={HelloNextjs} />
<Redirect to="/hello-nextjs" /> // or <Route path="/" exact render={() => <Redirect to="/hello-nextjs" />} />
</Switch>
Update: Next.js >= 13 with AppDir enabled
You can use next/navigation to redirect both in client components and server components.
Ex. in pages :
import { redirect } from 'next/navigation';
export default async function Home({ params }) {
redirect('/hello-nextjs');
// ...
}
Ex. In client components:
'use client';
import { useEffect } from 'react';
import { redirect } from 'next/navigation';
export const Home= () => {
useEffect(() => {
redirect('/hello-nextjs');
}, []);
return <p></p>;
};
Update: Next.js >= 12.1
As #warfield pointed out in his answer from next.js >= 12.1 relative URLs are no longer allowed in redirects and using them will throw an error. I'm reposting here his answer for more visibility :
To redirect using middleware with Next.js >= 12.1:
Create a middleware.ts (or .js) file at the same level as your pages directory
Export a middleware function
Create an absolute URL and pass it to redirect
TypeScript example middleware.ts:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone()
if (url.pathname === '/') {
url.pathname = '/hello-nextjs'
return NextResponse.redirect(url)
}
}
Update: Next.js >= 12
Now you can do redirects using middleware, create a _middleware.js file inside the pages folder (or any sub folder inside pages)
import { NextResponse, NextRequest } from 'next/server'
export async function middleware(req, ev) {
const { pathname } = req.nextUrl
if (pathname == '/') {
return NextResponse.redirect('/hello-nextjs')
}
return NextResponse.next()
}
Update: Next.js >= 10
From Next.js 10 you can do server side redirects (see below for client side redirects) with a redirect key inside getServerSideProps or getStaticProps :
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
// or use context.resolvedUrl for conditional redirect
// if(context.resolvedUrl == "/")
if (!data) {
return {
redirect: {
destination: '/hello-nextjs',
permanent: false,
},
}
}
return {
props: {}, // will be passed to the page component as props
}
}
Note : Using getServerSideProps will force the app to SSR,also redirecting at build-time is not supported , If the redirects are known at build-time you can add those inside next.config.js
In next.js you can redirect after the page is loaded using Router ex :
import Router from 'next/router'
componentDidMount(){
const {pathname} = Router
if(pathname == '/' ){
Router.push('/hello-nextjs')
}
}
Or with Hooks :
import React, { useEffect } from "react";
import Router from 'next/router'
...
useEffect(() => {
const {pathname} = Router
if(pathname == '/' ){
Router.push('/hello-nextjs')
}
});
If you want to prevent the flashing before the redirect you can use a simple trick :
import React, { useEffect,useState } from "react";
import Router from 'next/router'
const myPage = ()=>{
const [loaded,setLoaded] = useState(false)
useEffect(() => {
const {pathname} = Router
// conditional redirect
if(pathname == '/' ){
// with router.push the page may be added to history
// the browser on history back will go back to this page and then forward again to the redirected page
// you can prevent this behaviour using location.replace
Router.push('/hello-nextjs')
//location.replace("/hello-nextjs")
}else{
setLoaded(true)
}
},[]);
if(!loaded){
return <div></div> //show nothing or a loader
}
return (
<p>
You will see this page only if pathname !== "/" , <br/>
</p>
)
}
export default myPage
I would say that in general is not a good/elegant approach to do client redirects when you can use next.config.js redirects or even better use conditional render of components.
I have create a simple repo with all the examples above here.
Caveat
First, you should asses whether you need client-side redirection (within React), server-side redirection (301 HTTP response) or server-side redirection + authentication (301 HTTP response but also having some logic to check authentication).
This is the most complete answer I could write. But, in most scenarios, you do not need any of this. Just redirect as you would do in any React app. Prefer client-side redirections first. Just using useEffect + router.push, and that's it.
Server-side redirection are tempting, in particular when you want to "secure" private pages, but you should assess whether you really need them. Usually, you don't. They induce unexpected complexity, like managing auth token and refresh token. Instead, you may want to add a gateway server, a reverse proxy or whatever upfront server to your architecture for instance to handle those kind of checks.
Keep in mind that Next.js are just React app, and using Next.js advanced features like SSR comes at a cost that should be justified in your context.
Next 9.5 update
As stated by #Arthur in the comments, 9.5 also include the possibilities to setup redirects in next.config.js.
The limitations of this feature are not yet clear to me, but they seem to be global redirections, e.g. when you need to move a page or to allow access only during a limited period.
So they are not meant to handle authentication for instance, because they don't seem to have access to the request context. Again, to be confirmed.
Next 10 new doc update
This solution is specific to redirection depending on authentication.
Authentication patterns are now documented
I am not fond of authenticated from getServerSideProps, because it's in my opinion quite too late and can be difficult to set up with advanced patterns such as handling refresh token. But that's the official solution.
You may also want to check the approach documented in this ticket based on how Vercel's dashboard works (at the time of writing), that prevents flash of unauthenticated content
Next 10.2 header and cookies based rewrites update
Next 10.2 introduces Rewrites based on headers and cookies.
That's a great way to redirect server-side, based on the presence of an authentication cookie or header.
However, keep in mind that this is not a secure redirection. User can alter their request headers with a false token. You still need a gateway, a reverse proxy or an upfront server to actually check token validity and correctly set the headers.
Edit: note that the URL won't change. A rewrite points an URL to an existing page of your application, without changing the URL => it allows you to have "virtual" URLs.
Example use case: imagine you have a page src/contact.tsx, that is translated, and i18n redirection setup. You can translate the page name itself ("contact") by rewriting /de/kontact to /de/contact.
Next 12 update
Now middlewares gives you full-control on server-side redirects.
However, keep in mind again, that most of the time a client-side redirect and check is just enough.
Outdated Next 9.4 answer (links are dead sorry)
Hi, here is an example component working in all scenarios:
Vulcan next starter withPrivate access
Example usage here
The answer is massive, so sorry if I somehow break SO rules, but I don't want to paste a 180 lines piece of code. There is no easy pattern to handle redirection in Next, if you want to both support SSR and static export.
The following scenarios each need a specific pattern:
server side rendering: we render the page if allowed, HTTP redirect if not
static rendering (server-side): we render nothing, but we still include the page into the build
client side rendering, after a static export: we check client side if the user is auth, and redirect or not. We display nothing (or a loader) during this check or if we are redirecting.
client side rendering after a client redirect using next/router: same behaviour.
client side rendering after SSR: we use props passed by getInitialProps to tell if the user is allowed, directly at first render. It's just a bit faster, you avoid a blank flash.
At the time of writing (Next 9.4), you have to use getInitialProps, not getServerSideProps, otherwise you lose the ability to do next export.
Even more outdated old answer (works, but will have a messy static render)
Semi-official example
The with-cookie-auth examples redirect in getInitialProps. I am not sure whether it's a valid pattern or not yet, but here's the code:
Profile.getInitialProps = async ctx => {
const { token } = nextCookie(ctx)
const apiUrl = getHost(ctx.req) + '/api/profile'
const redirectOnError = () =>
typeof window !== 'undefined'
? Router.push('/login')
: ctx.res.writeHead(302, { Location: '/login' }).end()
try {
const response = await fetch(apiUrl, {
credentials: 'include',
headers: {
Authorization: JSON.stringify({ token }),
},
})
if (response.ok) {
const js = await response.json()
console.log('js', js)
return js
} else {
// https://github.com/developit/unfetch#caveats
return await redirectOnError()
}
} catch (error) {
// Implementation or Network error
return redirectOnError()
}
}
It handles both server side and client side. The fetch call is the one that actually get the auth token, you might want to encapsulate this into a separate function.
What I would advise instead
 1. Redirect on server-side render (avoid flash during SSR)
This is the most common case. You want to redirect at this point to avoid the initial page flashing on first load.
MyApp.getInitialProps = async appContext => {
const currentUser = await getCurrentUser(); // define this beforehand
const appProps = await App.getInitialProps(appContext);
// check that we are in SSR mode (NOT static and NOT client-side)
if (typeof window === "undefined" && appContext.ctx.res.writeHead) {
if (!currentUser && !isPublicRoute(appContext.router.pathname)) {
appContext.ctx.res.writeHead(302, { Location: "/account/login" });
appContext.ctx.res.end();
}
}
return { ...appProps, currentUser };
};
 2. Redirect in componentDidMount (useful when SSR is disabled, eg in static mode)
This is a fallback for client side rendering.
componentDidMount() {
const { currentUser, router } = this.props;
if (!currentUser && !isPublicRoute(router.pathname)) {
Router.push("/account/login");
}
}
I could not avoid flashing the initial page in static mode add this point, because you can't redirect during the static build, but it seems better than the usual approaches. I'll try to edit as I make progress.
Full example is here
Relevant issue, which sadly ends up with a client only answer
New issue I've opened regarding redirecton
There are three approaches.
1.Redirect on events or functions:
import Router from 'next/router';
<button type="button" onClick={() => Router.push('/myroute')} />
2.Redirect with hooks:
import Router , {useRouter} from 'next/router';
const router = useRouter()
<button type="button" onClick={() => router.push('/myroute')} />
3.Redirect with Link:
based on Nextjs docs the <a> tag is neccessary inside the link for things like open in a new tab!
import Link from 'next/link';
<Link href="/myroute">
<a>myroute</a>
</Link>
There are some other options for serverside routing which is asPath. in all described approaches you can add asPath to redirect both client and server side.
Edit 13.12.2022
1.Redirect with Link doesn't require anchor tag anymore!
import Link from 'next/link';
<Link href="/myroute">
my route
</Link>
2.Use Nextj.js Redirects
in next.config.js
module.exports = {
async redirects() {
return [
{
source: '/someroute',
destination: '/myroute',
permanent: true,
},
]
},
}
Next.js 10+ is offering us some extra and elegant solution to make a redirection.
SERVER-SIDE - you should use getServerSideProps
The example below assume that we have some extra session to check (but can be
anything that you want). If the session is empty and we are on the server-side
(context.res), that's mean that the user is not logged in and we should
redirect to the login page (/login).. In another way we can pass session
to props and redirect to the /dashboard:
import { getSession } from 'next-auth/client';
export const getServerSideProps = async (context) => {
const session = await getSession(context);
if(context.res && !session) {
return {
redirect: {
permanent: false,
destination: '/login'
}
}
}
return {
props: { session },
redirect: {
permanent: false,
destination: '/dashboard'
}
}
}
CLIENT-SIDE - you can use for example useRouter hook:
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/client';
const router = useRouter();
const [ session, loading ] = useSession();
if (typeof window !== 'undefined' && loading) return null;
if (typeof window !== 'undefined' && !session) {
router.push('/login');
}
router.push('/dashboard');
More info here: https://github.com/vercel/next.js/discussions/14890
Valid for NextJS 9.5.0+
Create next.config.js file
add source and destination url (you can set to permanent redirect if external domain)
module.exports = {
async redirects() {
return [
{
source: '/team',
destination: '/about',
permanent: false,
},
{
source: "/blog",
destination:
"https://blog.dundermifflin.com",
permanent: true,
},
];
},
};
https://github.com/vercel/next.js/tree/canary/examples/redirects
Here are 2 copy-paste-level examples: one for the Browser and one for the Server.
https://dev.to/justincy/client-side-and-server-side-redirection-in-next-js-3ile
Let's say you want to redirect from your root (/) to a page called home: (/home)
In your main index file, paste this:
Client Side
import { useRouter } from 'next/router'
function RedirectPage() {
const router = useRouter()
// Make sure we're in the browser
if (typeof window !== 'undefined') {
router.push('/home')
}
}
export default RedirectPage
Server Side
import { useRouter } from 'next/router'
function RedirectPage({ ctx }) {
const router = useRouter()
// Make sure we're in the browser
if (typeof window !== 'undefined') {
router.push('/home');
return;
}
}
RedirectPage.getInitialProps = ctx => {
// We check for ctx.res to make sure we're on the server.
if (ctx.res) {
ctx.res.writeHead(302, { Location: '/home' });
ctx.res.end();
}
return { };
}
export default RedirectPage
#Nico's answer solves the issue when you are using classes.
If you are using function you cannot use componentDidMount. Instead you can use React Hooks useEffect .
import React, {useEffect} from 'react';
export default function App() {
const classes = useStyles();
useEffect(() => {
const {pathname} = Router
if(pathname == '/' ){
Router.push('/templates/mainpage1')
}
}
, []);
return (
null
)
}
In 2019 React introduced hooks. which are much faster and efficient than classes.
In NextJs v9.5 and above you can configure redirects and rewrites in the next.config.js file.
But if you are using trailingSlash: true ensure that the source path ends with a slash for proper matching.
module.exports = {
trailingSlash: true,
async redirects() {
return [
{
source: '/old/:slug/', // Notice the slash at the end
destination: '/new/:slug',
permanent: false,
},
]
},
}
You also need to account for other plugins and configurations that may affect routing, for example next-images.
Documentation: https://nextjs.org/docs/api-reference/next.config.js/redirects
redirect-to.ts
import Router from "next/router";
export default function redirectTo(
destination: any,
{ res, status }: any = {}
): void {
if (res) {
res.writeHead(status || 302, { Location: destination });
res.end();
} else if (destination[0] === "/" && destination[1] !== "/") {
Router.push(destination);
} else {
window.location = destination;
}
}
_app.tsx
import App, {AppContext} from 'next/app'
import Router from "next/router"
import React from 'react'
import redirectTo from "../utils/redirect-to"
export default class MyApp extends App {
public static async getInitialProps({Component, ctx}: AppContext): Promise<{pageProps: {}}> {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
if (ctx.pathname === "" || ctx.pathname === "/_error") {
redirectTo("/hello-next-js", { res: ctx.res, status: 301 }); <== Redirect-To
return {pageProps};
}
return {pageProps};
}
render() {
const {Component, pageProps} = this.props;
return <Component {...pageProps}/>
}
}
I have implemented this functionality in my Next.JS app by defining a root page this does the redirect server side and client side. Here is the code for the root page:
import { useEffect } from "react";
import Router from "next/router";
const redirectTo = "/hello-nextjs";
const RootPage = () => {
useEffect(() => Router.push(redirectTo));
return null;
};
RootPage.getInitialProps = (ctx) => {
if (ctx.req) {
ctx.res.writeHead(302, { Location: redirectTo });
ctx.res.end();
}
};
export default RootPage;
Next.js >= 12.1
Relative URLs are no longer allowed in redirects and will throw:
Error: URLs is malformed. Please use only absolute URLs.
To redirect using middleware with Next.js >= 12.1:
Create a middleware.ts (or .js) file at the same level as your pages directory
Export a middleware function
Create an absolute URL and pass it to redirect
TypeScript example middleware.ts:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone()
if (url.pathname === '/') {
url.pathname = '/hello-nextjs'
return NextResponse.redirect(url)
}
}
🤷‍♂️ useEffect will redirect but jump immediately back to current page
✅ useLayoutEffect works like a charm:
const router = useRouter();
useLayoutEffect(() => {
router.isFallback && router.replace("/course");
}, [router]);
ℹ️ I've used the same code above for useEffect.
If your intention is to ensure your app is running like a SPA and wanting to intercept an incoming invalid (or valid) pathname, which the user pasted into the address bar, then here's a fast/hacky way to do that.
Assume your paths are,
enum ERoutes {
HOME = '/',
ABOUT = '/about',
CONTACT = '/contact'
}
Add a custom _error page if you don't have one already, and add this to it:
import React from 'react';
import { NextPage } from 'next';
import { useDispatch } from 'react-redux';
import { useRouter } from 'next/router';
const Error: NextPage = () => {
const { asPath, push } = useRouter();
const dispatch = useDispatch();
React.useEffect(() => {
const routeValid = Object.values(ERoutes).includes(asPath);
if (routeValid) {
// do some stuff, such as assigning redux state to then render SPA content in your index page
} else {
// you can either continue to render this _error component, or redirect to your index page,
// where you may have your own error component that is displayed based on your app state.
// In my case, I always redirect to '/' (as you can see below, where I push('/'), but before doing so,
// I dispatch relevant redux actions based on the situation
}
// I redirect to root always, but you can redirect only if routeValid === true
push('/');
}, []);
return (
<div>Error because '{asPath}' does not exist</div>
);
};
export default Error;
Redirects
Starting from Next.js 9.5 you are now able to create a list of redirects in next.config.js under the redirects key:
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/about',
destination: '/',
permanent: true,
},
];
},
};
Ofiicial Docs
Here's the middleware solution to avoid URLs is malformed. Please use only absolute URLs error.
Also, using paths object may be the cleaner way to handle redirection.
// pages/_middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(req: NextRequest) {
const { pathname, origin } = req.nextUrl;
const paths: { [key: string]: string } = {
'/admin/appearance': `${origin}/admin/appearance/theme`,
};
const rePath = paths[pathname];
if (rePath) return NextResponse.redirect(rePath);
else return NextResponse.next();
}
You can set a base path. Next Js allows you to do this. For example, to use /login instead of / (the default), open next.config.js and add the basePath config:
const nextConfig = {
basePath: "/login",
};
module.exports = nextConfig;
You can also check out their docs here https://nextjs.org/docs/api-reference/next.config.js/basepath

Loading local json file into redux store with axios, but it fails after I refresh in another route

So I'm practicing React and Redux, and I'm loading a local json file into the store like this ...
import { LOAD_BOOKS } from "./booksConstants";
import axios from "axios";
export const loadBooks = data => {
return {
type: LOAD_BOOKS,
payload: data
};
};
export const asyncLoadBooks = () => {
return async dispatch => {
const response = await axios.get("books.json");
const data = response.data.books;
dispatch(loadBooks(data));
};
};
And here's the reducer ...
import { LOAD_BOOKS } from "./booksConstants";
import { createReducer } from "../../store/reducerUtil";
const initialState = {
books: []
};
export const loadBooks = (state, payload) => {
return {
...state,
books: payload
};
};
export default createReducer(initialState, {
[LOAD_BOOKS]: loadBooks
});
And I'm connecting the App.js to the store with connect() and firing the 'asyncLoadBooks()' in 'componentDidMount()' like this ...
componentDidMount() {
try {
this.props.asyncLoadBooks();
} catch (error) {
console.log(error);
}
}
And everything is working just fine when I loop over the data and display them, however, if I'm on any other route other than "/" and refresh the app manually it gives me this error Failed to load resource: the server responded with a status of 404 (Not Found)
I tried to move the methods to the constructor instead of 'componentDidMount' but it didn't work.
What should I do here? And please keep in mind that I want to use axios and redux to practice them.
Edit
I put a console.log into each async action creator and apparently when I'm on any route other than the home "/" it tries to get the JSON file from this path and can't find it GET http://localhost:3000/category/books.json 404 (Not Found)
How can I solve this?
Ok, guys, I figured it out, the problem was in axios trying to the fetch the JSON file from different paths when you're on different routes, I fixed that by setting a global default baseURL for axios in the index.js file like this ...
import axios from "axios";
axios.defaults.baseURL = "http://localhost:3000/";
And now you can refresh in any route and the data will be fetched successfully.