How do we get window.location.hash from a Next.js application? - next.js

If we stick window.location.hash in useEffect, it will always erroneously return '0'. Apparently it has to do with SSR.
I need to reliably be able to get the hash portion of the URL for my project. How should I best go about it?

Server side code needs to wait until the code is loaded in the browser to use browser APIs.
Vanilla js server-side compatible
const [isMounted, setMounted] = useState(false);
useEffect(() => {
if (isMounted) {
console.log('hash', window.location.hash);
} else {
setMounted(true);
}
}, [isMounted]);
if(!isMounted) return <>Loading...</>;
Using next/router
import { useRouter } from 'next/router';
const { asPath } = useRouter();
useEffect(()=>{
const hash = asPath.split('#')[1];
}, [ asPath ]);
FYI you're code shouldn't return a zero. The first culprit that comes to mind is when a shorthand condition is used without an else.
window && window.location.hash
this should have an else
(window && window.location.hash) || null
or
window && window.location.hash ? window.location.hash : null

Extending #Sean W's answer, if you want to get a specific hash value from a hash key, you can use URLSearchParams:
// example path: /#error=unauthorized_client&error_code=401error_description=Something+went+wrong
import { useRouter } from 'next/router';
const { asPath } = useRouter();
useEffect(() => {
const hash = (asPath as string).split("#")[1]; // error=unauthorized_client&error_code=401error_description=Something+went+wrong
const parsedHash = new URLSearchParams(hash);
const errorHash = parsedHash.get("error_description"); // Something went wrong
}, []); // `asPath` omitted from dependencies as ESLint states it won't change

Related

How do I stop auth middleware in NextJS applying to server-side/pre-rendering requests?

I'm trying to require the user to be logged in to access certain routes.
I have added the following middleware, as per the docs, but am having difficulty getting it to work.
I thought the issue was down to the server-side pre-rendered page always being created while unauthenticated, but I'm no longer sure. Middleware should only run on client-side requests from a browser, right?
When I include the block marked with 🟡s, the redirect does not happen.
When it's removed, the redirect always happens, even if the user is logged in.
Note that we're using "next": "^12.3.3", and we're not ready to upgrade to next v13 yet.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import * as auth from 'src/lib/auth';
const pathsNeedingAuth = [
'/dashboard$',
'/account/(password|reset|delete)$',
];
export default async function middleware(request: NextRequest) {
if (typeof window === 'undefined') { // 🟡
return NextResponse.next(); // 🟡
} // 🟡
const pathNeedsAuth = pathsNeedingAuth.some(
(path) => new RegExp(path).test(request.nextUrl.pathname),
);
if (!pathNeedsAuth) {
return NextResponse.next();
}
const isAuthed = await auth.isAuthenticated();
if (isAuthed) {
return NextResponse.next();
}
const url = request.nextUrl.clone();
url.searchParams.set('redirectUrl', url.pathname);
url.pathname = '/account/login';
return NextResponse.redirect(url);
}
export const config = {
matcher: [...pathsNeedingAuth],
};
Any help will be appreciated! I know the structure of the code is a little odd, but that's simply from changing it while trying various things.
Redirects are incompatible with next export.

MSW(Mock Service Worker) in Next js first render not working

I use msw with Next js. But at First render, cannot connect api
this is index.tsx
import { useQuery } from "#tanstack/react-query";
import axios from "axios";
const Home = () => {
const getFruit = async () => {
const { data } = await axios.get("/api");
return data;
};
const { data } = useQuery(["dfa"], getFruit);
console.log("data: ", data);
return <div>Hello world</div>;
};
export default Home;
And i capture log in dev tool
In terminal compiling /_error (client and server).. error is showing.
I write code in mocks/index.ts like
async function initMocks() {
if (typeof window === "undefined") {
const { server } = await import("./server");
server.listen();
} else {
const { worker } = await import("./browser");
worker.start();
}
}
initMocks();
export {};
Also I check this code is running before index.tsx.
I think msw work late then first rendering. Is it right? How can I solve this problem?

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: ...}
}

How to combine state sanitizer with existing middleware in React-Redux

My redux store is fairly large; Redux Devtools suggests sanitizing my larger objects to improve performance.
I've followed the docs here: https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#excessive-use-of-memory-and-cpu
I've tried a number of combinations here, but none have given me the output I expect.
The current version, seen below, results in state being returned as a function, not an object. I know I'm doing something wrong, but I'm not sure what. Any guidance would be deeply appreciated.
Here's my store.js:
'use strict'
// libraries
import { createStore, applyMiddleware, compose } from 'redux'
// middleware
import logger from 'redux-logger'
import thunk from 'redux-thunk'
// reducers
import reducer from './reducers'
const withLogger = false ? (thunk, logger) : thunk
const isProd = process.env.NODE_ENV === 'production'
const middleware = isProd ? thunk : withLogger
const composeEnhancers = isProd
? compose
: window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
!!action.id
&& action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
? { ...action, data: '<<LONG_BLOB>>' }
: action
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(middleware)),
// The addition of this code breaks my store
window.__REDUX_DEVTOOLS_EXTENSION__
&& window.__REDUX_DEVTOOLS_EXTENSION__({
actionSanitizer,
stateSanitizer: state =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
})
// End breaking code
)
Second try
I've made a couple of updates, and can now see the sanitizers' effect in devtools - depending on placement in my createStore function. Unfortunately this changes my composeEnhancers behavior (fires, or does doesn't fire depending on placement)
// middleware with or without logger
const middlewareEnhancer =
true || ENV === 'production' // change to false to prevent logger output
? applyMiddleware(thunk, logger)
: applyMiddleware(thunk)
// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
!!action.id
&& action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
? { ...action, data: '<<LONG_BLOB>>' }
: action
// compose
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(middlewareEnhancer) ||
compose(middlewareEnhancer)
const store = createStore(
// createStore signature > reducer, preLoadedState, enhancer
rootReducer,
// devtools extension works when I place it here per the examples in docs
// BUT composed enhancers fail
// Obviously, since the format wouldn't match the createStore signature
// I have no idea how `__REDUX_DEVTOOLS_EXTENSION__` should be used in conjunction with composeEnhancers
undefined,
composeEnhancers,
// devtools extension fails when placed here
// composed enhancers run
window.__REDUX_DEVTOOLS_EXTENSION__
&& window.__REDUX_DEVTOOLS_EXTENSION__({
actionSanitizer,
stateSanitizer: state =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
})
)
Finally, persistence ftw!
I hate giving up; figured it out after rereading all the documentation posted by #markerikson. Always read the docs :'(
This may not be of use to anyone using configureStore and Redux Toolkit, but I'm documenting it regardless.
My big mistake was that actionSanitizer and stateSanitizer are Devtools Extension options, and should be added as such. Feel a fool, but at least I won't forget it.
The only thing left to do is implement redux-devtools-extension to avoid using window.__SOMEFUNC__ as suggested by markerikson.
The actual solution:
'use strict'
// libraries
import { createStore, applyMiddleware, compose } from 'redux'
// middleware
import logger from 'redux-logger'
import thunk from 'redux-thunk'
// reducers
import rootReducer from './reducers'
// middleware with or without logger
const middlewareEnhancer =
true || ENV === 'production' // change to false to prevent logger output
? applyMiddleware(thunk, logger)
: applyMiddleware(thunk)
// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
!!action.id
&& action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
? { ...action, data: '<<LONG_BLOB>>' }
: action
// compose
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// add sanitizers here as devtools options
// see https://github.com/zalmoxisus/redux-devtools-extension/tree/94f7e53800f4665bddc9b7438c5cc75cfb4547cc#12-advanced-store-setup
// section 1.2
actionSanitizer,
stateSanitizer: state =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
}) || compose
const enhancer = composeEnhancers(middlewareEnhancer)
const store = createStore(rootReducer, undefined, enhancer)
export default store
As a first observation, this line seems wrong:
const withLogger = false ? (thunk, logger) : thunk
I'd strongly encourage you to first switch over to using the configureStore function from our official Redux Toolkit package, which handles the store setup process for you. From there, you can still pass DevTools configuration options to configureStore() if desired.
Only to complete the answer for those using the redux toolkit, here is an example entry that works well for me.
const devToolsConfiguration = {
actionSanitizer: (action) => {
switch (true) {
case action.type.includes(RESOLVED):
return typeof action.payload !== 'undefined'
? { ...action, payload: '<<LONG_BLOB>>' }
: { ...action, results: '<<LONG_BLOB>>' };
/* ... more entries */
default:
return action;
}
},
stateSanitizer: (state) =>
state.data?.matrix
? { ...state, data: { ...state.data, matrix: '<<LONG_BLOB>>' } }
: state,
};
I then reference the configuration in the toolkit's configureStore function:
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: false,
serializableCheck: false,
immutableCheck: false,
}).prepend(middlewares),
preloadedState: initialState,
devTools: devToolsConfiguration, // <<< here
});

Next JS nested routing

-component
---->sidebar.js
---->exampleTabOne.js
---->exampleTabTwo.js
---->exampleTabThree.js
--pages
---->setting(which include all sidebar and those exampletabs)
i do've above folder structure in my nextJS project.
here as per nextjs doc
on localhost/setting i can easily view my page
but what i want to achieve is something like below:
1.localhost/setting/exampleTabOne
2.localhost/setting/exampleTabTwo/EXAMPLEID
3.localhost/setting/exampleTabThree/EXAMPLEID#something#something
the last part Url with # is something like inside tab content i ve another tabs so i want to fix it with Hash url so that while ssr i can easily open that inside tab too..
So, will you guys please suggest me how to solve this?
Here , In Next JS, We can achieve this by defining in server.js file.
// This file doesn't go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true);
const { pathname, query } = parsedUrl;
if (pathname === '/setting/exampleTabOne') {
app.render(req, res, '/setting', query);
} else if (pathname === '/setting/exampleTabTwo/EXAMPLEID') {
app.render(req, res, '/setting', query);
} else {
handle(req, res, parsedUrl);
}
}).listen(3000, err => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
});
Where In setting page we can dynamically load the respective component watching url pathname.

Resources