Redirect using react-router in redux middleware - react-router

I've created a middleware that checks if a request returns an invalid access response. If the status is a 401, I want to redirect the user to the login page
Here's the middleware code
import React from 'react';
import { push, replace } from 'react-router-redux';
const auth_check = ({ getState }) => {
return (next) => (action) => {
if(action.payload != undefined && action.payload.status==401){
push('login');
console.log('session expired');
}
// Call the next dispatch method in the middleware chain.
let returnValue = next(action);
return returnValue
}
}
export default auth_check;
Including it in index.js
...
const store = createStore(reducers, undefined,
compose(
applyMiddleware(promise,auth_check)
)
);
const history = syncHistoryWithStore(browserHistory, store);
ReactDOM.render(
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>
, document.querySelector('.app'));
The push method does not redirect the page. I am sure that the code goes through that section since the log is showing

if you prefer Redux style actions, the library also provides a set of action creators and a middleware to capture them and redirect them to your history instance.
for: push(location), replace(location), go(number), goBack(), goForward()
You must install routerMiddleware for these action creators to work.
import { routerMiddleware, push } from 'react-router-redux'
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
// Dispatch from anywhere like normal.
store.dispatch(push('/foo'))
Also React Router provides singleton versions of history (browserHistory and hashHistory) that you can import and use from anywhere in your application.
import { browserHistory } from 'react-router'
if(action.payload != undefined && action.payload.status==401){
browserHistory.push('login');
console.log('session expired');
}
btw for check auth you may use onEnter or redux-auth-wrapper

use onEnter method of react-router. call a function which will check for the access. Example:
function checkAccess() {
//some logic to check
if(notAuthorized){ window.location.href= "/login"; }
}

Related

i want to redirect user after login based on their role and stop them to access this page in React.js

I had did this using Redux library and MongoDB and its works fine with this but now i am doing same thing with mysql so its not working well. This logic always redirect all users to admin dashboard. i want do like, if i do isAdmin="true" it will redirect to admin dashboard and stop to going coordinator dashboard. and if isCoordinator="true" then redirect to coordinator dashboard and not able to access admin dashboard. how can i that?
*This is my Admin.js file. where i did logic to private access path.
import { useSelector } from "react-redux";
import { Route, Routes, Link, useNavigate } from "react-router-dom";
import UsersList from "./UsersList";
import UsersEntry from "./UsersEntry";
import Projects from "./projects/Projects";
import "./Admin.css"
export default function Admin() {
let navigate = useNavigate();
const userState = useSelector(state=> state.loginUserReducer)
const {currentUser} =userState;
useEffect(() => {
// This code check Role of user who logged in and if not coordinator then restrict(stop) to going on this private page. By getItem and check === !currentUser.isCoordinator.
if(localStorage.getItem('currentUser') === null || !currentUser.isAdmin){
navigate('/coordinators');
if(localStorage.getItem('currentUser') === null || !currentUser.isCoordinator){
navigate('/');
}
}
}, [])
*This is UserAction.js fie.
export const loginUser = (user) => async (dispatch) => {
dispatch({type: "USER_LOGIN_REQUEST"});
try {
const response = await axios.post("http://localhost:4000/login",user);
console.log(response.data);
dispatch({type:"USER_LOGIN_SUCCESS", payload: response.data});
localStorage.setItem('currentUser',JSON.stringify(response.data));
window.location.href = "/admin";
} catch (error) {
dispatch({type: "USER_LOGIN_FAIL", payload: error})
}
}
*This is UserReducer.js.
switch (action.type){
case "USER_LOGIN_REQUEST":
return{
loading:true,
};
case "USER_LOGIN_SUCCESS":
return{
success: true,
loading: false,
currentusers: action.payload,
};
case "USER_LOGIN_FAIL":
return{
error: action.payload,
loading:false,
};
default:
return state;
}
};
*This is Store.js.
import thunk from 'redux-thunk';
import {composeWithDevTools} from 'redux-devtools-extension';
import {loginUserReducer} from './UserReducer';
const currentUser = localStorage.getItem('currentUser') ? JSON.parse(localStorage.getItem('currentUser')) : null
const rootReducer = combineReducers({loginUserReducer : loginUserReducer});
const initialState = {
loginUserReducer: {
currentUser : currentUser
}
}
const middleware = [thunk]
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
In your useEffect inside the dependency array pass the currentUser so whenever it is changed useEffect will be triggered. In current scenario when the array is empty it just happens on the page load the very first time.
Here is the possible thing to try:
useEffect(() => {
// This code check Role of user who logged in and if not coordinator then restrict(stop) to going on this private page. By getItem and check === !currentUser.isCoordinator.
if(localStorage.getItem('currentUser') === null || !currentUser.isAdmin){
navigate('/coordinators');
if(localStorage.getItem('currentUser') === null || !currentUser.isCoordinator){
navigate('/');
}
}
}, [currentUser]) <================
Secondly also check your nested if condition. You can use an else if as well so incase its not admin it will go inside if and if it is admin then it can go inside the else if block. But that is totally up to you, just a suggestion.

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

How to pass const to multiple components / Spliting React-Redux-Router files

I am creating a Spotify app with its API. I want 4 views (like '/', 'nowPlaying', 'favouriteArtists', 'favouriteSongs').
I need to setAccessToken for using functions like getMyCurrentPlaybackState() in every new page, right?. Idk if I need to if(params.access_token){spotifyWebApi.setAccessToken(params.access_token)} in every container that will use functions like getMyCurrentPlaybackState(). I was thinking of creating a Spotify.jsx container that handle the store of the Spotify Object (which is used in the token and in every container that use spotify functions). But with this Spotify.jsx i don't know either if it is a good approach nor how to connect its needed spotifyWebApi const to every container file and token file.
For better understanding of my idea: I would create a Token.jsx that has getHashParams() and a Playing.jsx that has getNowPlaying(). Every one needs the spotifyWebApi const.
import React, { Component } from 'react';
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor(){
super();
const params = this.getHashParams();
this.state = {
loggedIn: params.access_token ? true : false,
nowPlaying: {
name: 'Not Checked',
image: ''
}
}
if (params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
}
getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ( e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
this.setState({
nowPlaying: {
name: response.item.name,
image: response.item.album.images[0].url
}
})
})
}
}
Your title mentions Redux, but I don't see your code utilizing it. With Redux, you could get the access_token and then store it in state. This will allow you to use it in any Redux connected component.
Also, with Redux, you can use Redux Thunk (or similar) middleware that will allow you to use Redux actions to call an API. So then you would just write the different API calls as Redux actions, which would allow you to call them from any component, and have the results added to your Redux store (which again, can be used in any Redux connected component).
So, for example, your getNowPlaying() function could be an action looking something like this:
function getNowPlaying() {
return function (dispatch, getState) {
// get the token and init the api
const access_token = getState().spotify.access_token
spotifyWebApi.setAccessToken(access_token)
return spotifyWebApi.getMyCurrentPlaybackState().then((response) => {
dispatch({
type: 'SET_NOW_PLAYING',
name: response.item.name,
image: response.item.album.images[0].url
})
})
}
}
Note: You'll need to configure the Redux reducer for "spotify" (or however you want to structure your store) to store the data you need.
So, you could then call getNowPlaying() from any component. It stores the results in the redux store, which you could also use from any connected component. And you can use the same technique of getting the access_token from the store when needed in the actions.
Alternatively, if you didn't want to use Redux, you could provide context values to all child components, using React's Context features. You could do this with that token that each component would need in your setup. But Redux, in my opinion, is the better option for you here.
Instead of passing this const to other components, I would create a SpotifyUtils.jsx and inside it declare the const. And in this helper file I would export functions so other components can use them.
For example:
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
let token = null
export function isLoggedIn() {
return !!token
}
export function setAccessToke(_token) {
token = _token;
spotifyWebApi.setAccessToken(_token);
}
export function getNowPlaying(){
return spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
return {
name: response.item.name,
image: response.item.album.images[0].url
}
})
}
So that in the components you can use them like so:
import React, { Component } from 'react';
import {
isLoggedIn,
setAccessToken,
getNowPlaying,
} from 'helpers/SpotifyUtils'
class App extends Component {
constructor(){
super();
this.state = {
loggedIn: isLoggedIn(),
nowPlaying: {
name: 'Not Checked',
image: ''
}
}
getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ( e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
componentDidMount() {
if (!this.state.loggedIn) {
const params = this.getHashParams();
if (params.access_token) {
setAccessToken(params.access_token)
getNowPlaying()
.then(nowPlaying => this.setState({ nowPlaying }))
}
}
}
}
This will enable your spotifyWebApi const to be reused in any component you import the helper functions. I am particularly found of this pattern, creating utils or helpers in a generic fashion so that you can reuse code easily. Also if spotify Web Api releases a breaking change, your refactor will be easier because you will only need to refactor the SpotifyUtils.jsx file since it will be the only file using import Spotify from 'spotify-web-api-js'

display json data with react and redux

I am attempting to load some local json data with redux and display in react app. But i'm getting the pageId is undefined in the reducer.
Not sure what I am doing wrong here, I think it might be something wrong with how I'm passing the data but im very new to redux so i'm not sure.
Data
const page = [
{"title":"Mollis Condimentum Sem Ridiculus"},
{"title":"Pharetra Tellus Amet Commodo"}
]
export default page;
Action
const getPage = (pageId) => {
const page = { pageId: pageId }
return {
type: 'GET_PAGE_SUCCESS',
payload: page
}
}
export default getPage
Reducer
import getPage from '../actions/actionCreators'
import pageData from './../data/pageData';
const defaultState = pageData
const pageReducer = (state = defaultState, action) => {
if (action.type = 'GET_PAGE_SUCCESS') {
state.page[action.payload.pageId].title = action.payload
}
return state
}
export default PageReducer
Component
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import getpage from '../../actions/actionCreators'
const mapStateToProps = (state, props) => {
const page = state.page[props.pageId]
return { page }
}
class Page extends Component {
componentDidMount () {
this.props.getpage(this.props.pageId)
}
render() {
return (<div><PageContainer pageId={0} /></div>)
}
}
const PageContainer = connect(mapStateToProps, { getpage })(page)
export default Page
I've modified your code into a working JSFiddle for reference: https://jsfiddle.net/qodof048/11/
I tried to keep it as close to your example, but let me explain the changes I made to get it working (also note that JSFiddle does not use the ES6 import syntax).
1) Your PageContainer was not constructed correctly. The last parameter should have been a reference to the Page component (not 'page').
const PageContainer = connect(mapStateToProps, { getPageSimple, getPageAsync })(PageComponent)
2) You used PageContainer in the Page component, but PageContainer is the 'wrapper' around Page. You use PageContainer instead of Page in your render method, so it loads the data (maps state and actions).
ReactDOM.render(
<Provider store={store}>
<div>
<PageContainer pageId="0" async={false} />
<PageContainer pageId="1" async={true} />
</div>
</Provider>,
document.getElementById('root')
);
3) The store was mixed up a bit. If I understood your example correctly you want to load a page into the local store from the pageData array, which simulates a server call maybe. In that case you intialState can't be pageData, but rather is an empty object. Think of it like a local database you're going to fill. The call to your action getPage then gets the page (here from your array) and dispatches it into the store, which will save it there.
const getPageSimple = (pageId) => {
const page = pageDatabase[pageId]; // this call would be to the server
// then you dispatch the page you got into state
return {
type: 'GET_PAGE_SUCCESS',
payload: {
id: pageId,
page: page
}
}
}
4) I've added an async example to the JSFiddle to explain how you would actually fetch the page from the server (since the simple example would not be sufficient). This needs the thunk middleware for redux to work (since you need access to the dispatch method in order to async call it). The setTimeout simulates a long running call.
const getPageAsync = (pageId)=>{
return (dispatch, getState) => {
setTimeout(()=>{
const page = pageDatabase[pageId]; // this call would be to the server, simulating with a setTimeout
console.log("dispatching");
// then you dispatch the page you got into state
dispatch({
type: 'GET_PAGE_SUCCESS',
payload: {
id: pageId,
page: page
}
});
}, 2000);
}
}
The JSFiddle loads 2 containers, one with your simple getPage and one with the async version, which loads the title after 2 seconds.
Hope that helps you along on your react/redux journey.
Hey I see a small mistake in you component, I think. You are doing this.props.pageId, when you are setting page and not pageId on the component's props. So shouldn't it be this.props.getPage(this.props.page.pageId) instead? Could that be it?
Also a small side note, an important tip for using redux is to not mutate state. In you reducer where you are doing state.page[action.payload.pageId].title = action.payload you should probably not set state like that, but instead return a new object called newState which is identical to state, but with the title updated. It is important to treat objects as immutable in Redux. Cheers

Get params outside of a component

I would like to retrieve the current params outside of a component, and as far as I can tell React Router does not provide a convenient way of doing that.
Sometime back before 0.13 the router had getCurrentParams() which is what I used to use.
Now the best thing I can figure out is:
// Copy and past contents of PatternUtils into my project
var PatternUtils = require('<copy of PatternUtils.js>')
const { remainingPathname, paramNames, paramValues } =
PatternUtils.matchPattern(
"<copy of path pattern with params I am interested in>",
window.location.pathname);
Is there a way to do this with React router?
you could use matchPath:
import { matchPath } from 'react-router'
const { params }= matchPath(window.location.pathname, {
path: "<copy of path pattern with params I am interested in>"
})
If you use matchPath(window.location.pathname, ...) within your render function, your component won't signal to be re-rendered on route changes. You can instead use react router's useLocation hook to fix this:
import { matchPath } from 'react-router'
import { useLocation } from 'react-router-dom'
function useParams(path) {
const { pathname } = useLocation()
const match = matchPath(pathname, { path })
return match?.params || {}
}
function MyComponent() {
const { id } = useParams('/pages/:id')
return <>Updates on route change: {id}</>
}
Note: matchPath will return null for paths which don't match.
If you use the object destructure pattern { params } = matchPath it may throw the following error:
Cannot destructure property 'params' of 'Object(...)(...)' as it is null.