useEffect() keeps re-rendering, how to stop it from re-rendering - reactjs

import React from "react";
import { ThemeProvider } from "styled-components";
import { theme } from "./theme-default";
import { HashRouter as Router, Route, Switch } from "react-router-dom";
import { GlobalStyle } from "./themes";
import { Security } from '#okta/okta-react';
import { OktaAuth } from "#okta/okta-auth-js";
import { FeeSitePageHeader } from "./app/fee--site-page-header";
import { FeeSitePageFooter } from "./app/fee-site-page-footer";
import Dashboard from "./app/Dashboard/Dashboard";
import Logout from "./app/Logout/Logout";
import Login from "./app/Login/Login";
function App() {
const config = {
issuer: 'https://dev-95779092.okta.com/',
clientId: '***',
redirectUri: window.location.origin + '/?redirect_url=/login/callback'
};
const authClient = new OktaAuth(config);
function restoreOriginalUri() {
console.log(restoreOriginalUri);
};
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router >
<FeeSitePageHeader />
<Security oktaAuth={authClient} restoreOriginalUri={restoreOriginalUri}>
<Switch>
<Route path="/" exact>
<Dashboard />
</Route>
</Switch>
</Security>
<FeeSitePageFooter />
</Router>
</ThemeProvider>
);
}
export default App;
This is the dashboard component.
import { useOktaAuth } from '#okta/okta-react';
import React from 'react';
import * as Styled from "./Dashboard.styled";
function Dashboard(){
const { authState, oktaAuth } = useOktaAuth();
React.useEffect(() => {
oktaAuth.signInWithRedirect();
if (window.location.search?.includes('redirect_url=/login/callback')) {
console.log("Check if the browser contains redirect");
}
}, [])
return(
<>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
</>
);
}
export default Dashboard;
When the application starts, call goes to OKTA, authenticate logic does its part and url changes to "http://localhost:8080/#/dashboard?redirect_url=/login/callback". After this I want useEffects to get called, Problem is Dashboard keeps re-rendering itself how to stop re-rendering here?

See the docs when you are using the functional component you shouldn't recreate the octaAuth every time.
https://github.com/okta/okta-react#creating-react-router-routes-with-function-based-components
Then you call signInWithRedirect in useEffect every time when component mounts. It should be wrapped with authState.isAuthenticated. Example in docs https://github.com/okta/okta-react#show-login-and-logout-buttons-function-based

When you change the URL you "reload" the page hence - useEffect runs again.

Pass the parameter inside the useEffect hook
React.useEffect(() => {
oktaAuth.signInWithRedirect();
if (window.location.search?.includes('redirect_url=/login/callback')) {
console.log("Check if the browser contains redirect");
}
}, [authState])

Related

Why is my useContext not working in my app and my browser remains loading?

Im trying to use useContext hook to pass variables and functions through different components without lifting them to the main App.js component. I was trying to do this and it seemed to compile correctly but when i go to my browser my app is stucked in a blank page and remains loading.
LoginContext.js: In this component i store the user data in an object using the useState hook and i use jwt_decode to decode the use token and get all the data i need to store.
import React, { createContext, useState } from "react";
import jwt_decode from 'jwt-decode';
const LoginContext = createContext();
export function LoginProvider({children}) {
const [user, setUser] = useState({})
function handleCallbackResponse(response){
var userData = jwt_decode(response.credential); //Token with the login user Data
setUser(userData); //Store user Data
/* console.log(userData) */
document.getElementById('signInDiv').hidden = true;
}
function handleSignOut(event) {
setUser({}) //Empy the user Data
document.getElementById('signInDiv').hidden = false;
}
return(
<LoginProvider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginProvider>
);
}
export default LoginContext
The i have my Login.js which uses LoginContext: Here i use the user to show the different data of the logged in use and the handleCallbackResponse to do my Login.
import React, { useContext, useEffect } from 'react'
import LoginContext from '../LoginContext';
const Login = () => {
const {user, handleCallbackResponse, handleSignOut} = useContext(LoginContext)
useEffect(()=>{
/*global google*/
google.accounts.id.initialize({
client_id:"My client ID",
callback: handleCallbackResponse
})
google.accounts.id.prompt();
google.accounts.id.renderButton(
document.getElementById('signInDiv'),
{theme: 'outline', size: 'medium'}
)
}, []);
return (
<div>
<div id="signInDiv"></div>
{
//If user objetc is not empty show sign out button
Object.keys(user).length !== 0 &&
<button onClick={(e)=>handleSignOut(e)}>Sign Out</button>
}
{user &&
<div>
<img src={user.picture} alt="" />
<h3>{user.name}</h3>
</div>
}
</div>
)
}
export default Login
App.js:
import './App.css';
import Login from './atoms/Login';
import { BrowserRouter , Routes, Route } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import { LoginProvider } from './LoginContext';
import PrivateRoutes from './utils/PrivateRoutes';
function App() {
return (
<LoginProvider>
<BrowserRouter>
<Routes>
{/* <Route element={<PrivateRoutes/>}>
</Route> */}
<Route exact path="/dashboard" element={<Dashboard/>}/>
<Route path="/" element={<Login/>} />
</Routes>
</BrowserRouter>
</LoginProvider>
);
}
export default App;
For some reason my application runs with no error but in the browser it remains loading with a blank page and im not able to inspect the page.
Instead of:
<LoginProvider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginProvider>
);
Replace with
<LoginContext.Provider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginContext.Provider>

How to provide context from contextApi for a specific set of routes in nextjs and preserve state with routing through linking?

I am using contextApi with nextjs and I'm having some trouble when providing a context just for certain routes. I am able to make the context available for just a few routes, but when I transition from one to the other through linking, I end up losing the state of my application.
I have three files inside my pages folder:
index.tsx,
Dashboard/index.tsx and
SignIn/index.tsx.
If I import the provider inside the files Dashboard/index.tsx and SignIn/index.tsx and go from one page to the other by pressing a Link component from next/link, the whole state is set back to the initial state.
The content of the Dashboard/index.tsx file
import React from 'react';
import Dashboard from '../../app/views/Dashboard';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<Dashboard />
</AuthProvider>
);
export default Index;
This is the contend of the SignIn/index.tsx file:
import React from 'react';
import SignIn from '../../app/views/SignIn';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<SignIn />
</AuthProvider>
);
export default Index;
The views folder is where I create the components that will be rendered.
The content of the file views/SignIn/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const SignIn: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="Dashboard">Go back to Dashboard</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default SignIn;
And the content of the file views/Dashboard/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const Dashboard: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="SignIn">Go back to sign in page</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default Dashboard;
I am able to access the context inside both /Dashboard and /SignIn, but when I press the link, the state comes back to the initial one. I figured out that the whole provider is rerenderized and therefore the new state becomes the initial state, but I wasn't able to go around this issue in a "best practices manner".
If I put the provider inside _app.tsx, I can maintain the state when transitioning between pages, but I end up providing this state to the / route as well, which I am trying to avoid.
I was able to go around this by doing the following, but it really does not seem to be the best solution for me.
I removed the Providers from Pages/SignIn/index.tsx and Pages/Dashboard/index.tsx and used the following snippet for the _app.tsx file:
import React from 'react';
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { AuthProvider } from '../contexts/auth';
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const router = useRouter();
const AuthProviderRoutes = ['/SignIn', '/Dashboard'];
return (
<>
{AuthProviderRoutes.includes(router.pathname) ? (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
) : <Component {...pageProps} />}
</>
);
};
export default App;
Does anyone have a better solution?

how to use logout button without authProvider

hi react admin community,
I want to use logout button with custom component and router. I have checked documentation but not found any solution.
please suggest to me how I can use it.
below I added my code.
Adminroot Component
This is Admin Component.
import React from 'react';
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from "../users/users";
import Dashboard from './dashboard';
import MyLogoutButton from '../auth/logout';
const authProvider = {
logout: params => Promise.resolve(),
};
function Adminroot(props) {
const dataProvider = jsonServerProvider('http://jsonplaceholder.typicode.com');
return (
<div>
<Admin logoutButton={MyLogoutButton} loginPage={false} dashboard={Dashboard} dataProvider={dataProvider}>
<Resource name="users" list={UserList} />
</Admin>
</div>
);
}
export default Adminroot;
MyLogoutButton Component
This component contains default code which provides react-admin for logout.
Now, when clicks on Logout button. by default redirects to /login Url.
There renders a Logout component (below written the logout component code) that contains logout logic and redirect to /signin but it shows blank page until I refresh the page.
import React, { forwardRef } from 'react';
import { useLogout } from 'react-admin';
import MenuItem from '#material-ui/core/MenuItem';
import ExitIcon from '#material-ui/icons/PowerSettingsNew';
const MyLogoutButton = forwardRef((props, ref) => {
const logout = useLogout();
const handleClick = () => logout();
return (
<MenuItem
onClick={handleClick}
ref={ref}
>
<ExitIcon /> Logout
</MenuItem>
);
});
export default MyLogoutButton;
Logout Component
import React , { useContext } from 'react';
import { AppContext } from '../../AppContext';
import { Redirect } from 'react-router-dom';
function Logout(props){
const {handleSignOut} = useContext(AppContext);
handleSignOut();
return props.history.push('/signin');
}
export default Logout;

Warning : update in state transition when accessing the app via a callback page and updating the Context from here

I have an app composed of two pages, a Home page and a Callback page. After authentication on a tiers service, such as a social network or other, the user is redirected to the Callback page of my app. The app uses the react-context api and I would like to store all the data found in the query string of the callback page. Shortly I want to open a page of my app, not the home page, and update the context of the app from this page.
This is what is achieved in this tutorial for Auth0. The app is composed of 5 very short files, I will reproduce them here.
The problem is that I get a warning when I access http://127.0.0.1:3000/callback in a browser. The warning is Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
In the Auth0 tutorial, this warning doesn't show up but the state update is made within a dedicated function, I don't know how to reproduce this work with my own components.
To reproduce the warning, use create-react-app and use the files at the end of the post.
The goal of this toy example is to set unused_data in the state of Context component, but without triggering the warning (unused_data is actually set).
EDIT : Well, I learned redux and everything works fine when I use redux, so I still don't know why I got an error, but everything is just so much simpler now :)
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Context from "./Context";
import Callback from './Callback'
import Home from './Home'
class App extends Component {
render() {
return (
<div className="App">
<Context>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/callback" component={Callback} />
</Switch>
</Router>
</Context>
</div>
);
}
}
export default App;
Context.js
import React, {Component} from "react";
import {Provider} from "./contextDefs";
class Context extends Component {
state = {
unused_data: ""
};
changeState = () => {
this.setValue()
}
setValue = () => {
this.setState({
unused_data: "has been set up",
})
}
render() {
const providerValue = {
...this.state,
changeState: this.changeState,
};
return (
<Provider value={providerValue}>
{this.props.children}
</Provider>
);
}
}
export default Context;
Callback.js
import React from "react";
import {Redirect} from "react-router-dom";
import {Consumer} from "./contextDefs";
const callback = props => (
<Consumer>
{({changeState}) => {
changeState();
return <Redirect to="/"/>;
}
}
</Consumer>
)
export default callback;
contextDefs.js
import { createContext } from "react";
const Context = createContext({
unused_data: "",
changeState: () => {},
});
export const Provider = Context.Provider;
export const Consumer = Context.Consumer;
Home.js
import React from "react";
const AuthTwitter = props => {
return (
<div>
Test
</div>
);
}
export default AuthTwitter;

Component updated in react-test-renderer when Redirect is rendered

I'm currently writing test for my login form written in react-redux using react-router-dom.
My component looks like this:
class LoginForm extends Component {
render() {
const {
isAuthenticated,
...
} = this.props
if (isAuthenticated) {
return (
<Redirect to="/" />
)
}
return (
<div style={{ ...styles.main }}>
...
</div>
)
}
}
To test this component I have written the following test:
import React from "react"
import renderer from "react-test-renderer"
import { MemoryRouter } from "react-router-dom"
import LoginPage from "./LoginPage"
it("renders a redirect if the user is authenticated", () => {
const tree = renderer
.create(
<MemoryRouter>
<LoginPage
isAuthenticated={ true } />
</MemoryRouter>
)
.toJSON()
expect(tree).toMatchSnapshot()
})
This test mounts the component correctly and "renders" the Redirect, but it also seems to triggers an update. This rerenders the redirect and gives me the following warning in my test output:
console.error node_modules\warning\warning.js:51
Warning: You tried to redirect to the same route you're currently on: "/"
How can I prevent this warning from showing up?
Thanks!

Resources