I have a component which once once it rendered I need to redirect a user to another path and I'm using useEffect hook of react but it's getting rendered over and over and over without stopping:
const App: FunctionComponent<{}> = () => {
const [message, setMessage] = useState("");
useEffect(() => {
if (condition) {
setMessage("you are being redirected");
setTimeout(() => {
location.href = `http://localhost:4000/myurl`;
}, 2000);
} else {
setMessage("you are logged in");
setTimeout(() => {
<Redirect to={"/pages"} />;
}, 2000);
}
}, [message]);
return (
<>
{message}
<BrowserRouter>
<Route exact path="/login/stb" render={() => <Code />} />
</BrowserRouter>
</>
);
};
export default App;
It looks like setMessage is setting a message state variable in the component. This occurs during every run of useEffect. State changes will cause your component to rerender.
Basically, the flow causing the loop is this:
Initial component render
useEffect is run
message state is updated
Component rerenders due to state change
useEffect is run as it's trigged on message change
Back to step 3
If you want useEffect to only run on initial render, and redirect after a user has logged in, you could change it to something like this:
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
if (someConditionHere) {
setLoggedIn(true);
}, []);
return (
loggedIn ? <Redirect to={"/pages"} /> :
<BrowserRouter>
<Route exact path="/login/stb" render={() => <Code />} />
</BrowserRouter>
);
I don't know everything about the setup, so that's simplifying and making some assumptions.
Related
I don't know why, changing the props state inside useEffect causes infinite loop of errors. I used them first locally declaring within the function without using props which was running ok.
EDIT:
Home.js
import Axios from "axios";
import React, { useEffect, useState } from "react";
function Home(props) {
// const [details, setDetails] = useState({});
// const [login, setLogin] = useState(false);
useEffect(() => {
try {
const data = localStorage.getItem("expensesAccDetails");
if (data) {
Axios.post("http://localhost:3001/eachCollectionData", {
collection: data,
}).then((res) => {
if (res.data.err) {
console.log("Error");
} else {
console.log(res.data[0]);
props.setLogin(true);
props.setUserdetails(res.data[0]);
}
});
}
} catch (err) {
console.log(err);
}
}, []);
return props.login ? (
<div>
<div>Welcome {props.setUserdetails.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
}
export default Home;
App.js
function App() {
const [login, setLogin] = useState(false);
const [userdetails, setUserdetails] = useState({});
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
setLogin={setLogin}
login={login}
setUserdetails={setUserdetails}
userdetails={userdetails}
/>
<Bars login={login} />
</>
}
/>
<Routes>
<Router>
);
Here I initialized the states directly in App.js so I don't have to declare it on every page for the route renders. I just passed them as props to every component.
I suggest to create a componente Home with the post and two sub-component inside:
const Home = () => {
const [userDetails, setUserDetails] = useState({});
const [login, setLogin] = useState(false);
useEffect(() => {
// api call
}, []);
return (
<>
<Welcome login={login} details={userDetails} />
<Bars login={login} details={userDetails} />
</>
);
};
where Welcome is the following:
const Welcome = ({ userdetails, login }) => (
<>
login ? (
<div>
<div>Welcome {userdetails.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
</>
);
A better solution is to use only one state variable:
const [userDetails, setUserDetails] = useState(null);
and test if userDetails is null as you test login is true.
An alternative if you have to maintain the call as you write before, you can use two state as the follow:
function App() {
const [userdetails, setUserdetails] = useState(null);
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
setUserdetails={setUserdetails}
/>
<Bars login={!!userdetails} />
</>
}
/>
<Routes>
<Router>
);
and on Home component use a local state:
const Home = ({setUserdetails}) => {
const [userDetailsLocal, setUserDetailsLocal] = useState(null);
useEffect(() => {
// api call
// ... on response received:
setUserdetails(res.data[0]);
setUserDetailsLocal(res.data[0]);
// ...
}, []);
userDetailsLocal ? (
<div>
<div>Welcome {userDetailsLocal.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
};
I advise to follow Max arquitecture for your solution. the problem lies in the Router behavior. React Router is not part of React core, so you must use it outside your react logic.
from documentation of React Router:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render.
https://v5.reactrouter.com/web/api/Route/component
Edit:
ok, you make me write it. A solution could be like:
function App() {
const [login, setLogin] = useState(false);
const [userdetails, setUserdetails] = useState({});
useEffect(() => {
try {
const data = localStorage.getItem("expensesAccDetails");
if (data) {
Axios.post("http://localhost:3001/eachCollectionData", {
collection: data,
}).then((res) => {
if (res.data.err) {
console.log("Error");
} else {
console.log(res.data[0]);
setLogin(true);
setUserdetails(res.data[0]);
}
});
}
} catch (err) {
console.log(err);
}
}, []);
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
login={login}
userdetails={userdetails}
/>
<Bars login={login} />
</>
}
/>
<Routes>
<Router>
);
I am busy with a React website that Uses Material UI, Everything on Local seems to be working fine but its only when I push to a production server do things fail. If I go to the site "https://wheremysiteis.com" it should redirect the user to the login page, which seems to break and looks like this after redirecton:
If I go to "https://wheremysiteis.com/login" the site seems to load perfectly normal with no issues. The same issue happens if I send a user a password reset link that goes to "https://wheremysiteis.com/ForgotPassword/{id}" The page seems to break:
I have reason to believe it is a redirect issue, here is my code for the routing. I am using React Router v6
const authService = new AuthService();
export const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const setUser = useSetRecoilState(UserAtom);
const history = useNavigate();
const location = useLocation();
// Set token upon application open
Interceptors.setupTokenInterceptor();
Interceptors.setupUnauthorizedInterceptor(axiosInstance, history);
useEffect(() => {
if (!location.pathname.includes('/ForgotPassword')){
setLoading(true);
authService.UserInfo();
authService.getAccountDetails().then(result => {
if (result.isSuccess){
setUser(result.value);
}
})
authService.Csfr().then(result => {
const cookies = new Cookies();
var ExistingToken = localStorage.getItem('__RequestVerificationToken');
if (ExistingToken) {
localStorage.removeItem('__RequestVerificationToken');
localStorage.removeItem('__formtoken');
localStorage.setItem('__RequestVerificationToken', result.value.CookieToken);
localStorage.setItem('__formtoken', result.value.FormToken);
} else {
localStorage.setItem('__RequestVerificationToken', result.value.CookieToken);
localStorage.setItem('__formtoken', result.value.FormToken);
}
cookies.remove('__RequestVerificationToken');
cookies.remove('__formtoken');
setLoading(false);
}).catch(error => { console.error(error) });
}
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return (
<div>
{loading ? <IntermediateGlobalLoading /> :
<div>
<Routes>
<Route path={login} element={<LoginPage />} />
<Route path='/ForgotPassword/:id' element={<ForgotPasswordPage />} />
</Routes>
<Routes>
<Route path='/*' element={<Dashboard />} />
</Routes>
</div>
}
</div>
);
}
The only console errors I do find on the production site is this:
I create a let variable in my parent component with the boolean false. I pass that variable as a prop to the child component. I change the variable to true through a function in the parent component.
My problem is that the value of the prop in the child component is still false when I console.log it afterwards.
Parent:
function App() {
let success = false
const changePW = async ({password, repeatPW}) => {
success = true
console.log(`Success App0: ${success}`)
console.log('ChangePW')
return (
<BrowserRouter>
<div className="container">
<Header/>
<Route path='/' exact render={(props) => (
<>
<AddPasswort onChange = {changePW} success = {success}/>
<Footer />
</>
)}/>
<Route path='/about' component={About} />
<Route path='/success' component={Success} />
<Route path='/error' component={Error} />
</div>
</BrowserRouter>
);
}
export default App;
Child
const AddPasswort = ({onChange,success}) => {
const[password, setPassword] = useState('')
const[repeatPW, setRepeatPW] = useState('')
// create a history object
const history = useHistory()
const onSubmit = async (e) => {
e.preventDefault();
await onChange({password, repeatPW})
console.log(success)
// navigate to the success page
if (success){
console.log('success')
history.push("/success")
}
else{
console.log('error')
history.push("/error")
}
}
}
...
}
export default withRouter(AddPasswort);
I thought the problem was that the function does not wait for onChange to finish so I made it asynch, but that did not resolve the issue
because success is not a state,
only changing state will re-render component.
try
const [success, setSuccess] = useState(false);
to change the value of success to true , do
setState(true)
this should solve your problem
My code is a front end with a request from API using axios.
It checks cookie to know if logged in or not for authentication, so if there is no cookie, it redirects to the home page.
const PrivateRoute: FunctionComponent<AuthProps> = ({
component: Component,
path,
}: AuthProps) => {
const [isAuth, setisAuth] = useState(false);
useEffect(() => {
axios
.get('http://localhost:5000/api/v1/checkcookie', {
withCredentials: true,
})
.then((response) => {
setisAuth(response.data.cookie);
});
});
return (
<Route
render={() => {
return isAuth === true ? <Component /> : <Redirect to="/" />;
}}
/>
);
};
const App: React.FC = () => {
return (
<Router>
<ThemeProvider theme={theme}>
<Layout>
<Switch>
<Route path="/" exact component={Landing} />
<PrivateRoute path="/inquiries" component={Inquiries} />
<PrivateRoute
path="/create-article"
component={CreateArticle}
/>
<Route path="/login" component={Login} />
</Switch>
</Layout>
</ThemeProvider>
</Router>
);
};
But the isAuth state will change after a request from API, but doesn't change in:
isAuth === true ? <Component /> : <Redirect to="/" />;
I just want to make sure that it has the last isAuth value.
How do I make sure that it changes state before coming to the condition?
Try to add a loading state.
Additional notes:
You will need catch to handle errors.
Add an empty deps array to make sure it runs only once.
const PrivateRoute: FunctionComponent<AuthProps> = ({
component: Component,
path,
}: AuthProps) => {
const [isLoading, setIsLoading] = useState(true);
const [isAuth, setisAuth] = useState(false);
useEffect(() => {
setIsLoading(true);
axios
.get('http://localhost:5000/api/v1/checkcookie', {
withCredentials: true,
})
.then((response) => {
setisAuth(response.data.cookie);
})
.catch(console.log)
.finally(() => {
setIsLoading(false);
});
}, []);
if (isLoading) {
return <></>;
}
return (
<Route
render={() => {
return isAuth === true ? <Component /> : <Redirect to="/" />;
}}
/>
);
};
Is response.data.cookie a boolean value? If, for example, response.data.cookie = '32094jwef9u23sdkf' then isAuth === true will never evaluate true and redirect will always render. Also, it is common, and more correct, to never test directly against true or false, just use the javascript truthy/falsey-ness of variable values.
Check the react-router-dom auth-workflow example.
The thing to notice here is that the auth check is in the render prop. The reason for this is the private route will only be rendered once when the Router is rendered, but the matched routed will render/rerender its children when necessary.
Try moving the auth check into the render prop.
Add a loading state and return null while the auth check is pending, use .finally block to signal not loading.
Save a boolean isAuth value if cookie exists or not.
Code:
const PrivateRoute: FunctionComponent<AuthProps> = ({
component: Component,
path,
}: AuthProps) => {
return (
<Route
render={(routeProps) => {
const [isAuth, setIsAuth] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
axios
.get('http://localhost:5000/api/v1/checkcookie', {
withCredentials: true,
})
.then((response) => {
setIsAuth(!!response.data.cookie);
})
.finally(() => setLoading(false));
}, []);
if (loading) return null;
return isAuth ? <Component {...routeProps} /> : <Redirect to="/" />;
}}
/>
);
};
I have a loading screen and I want to hide it when a page is done loading but I can only do that on the initial page load. I want to display the loading screen when the user changes route and hide it hide it when the page is done loading.
I want something similar to the pace.js library so it can detect when the page is done loading.
If you want to wait some async request for each page, there are few ways:
You can create some global loader and insert it near Router:
<div>
<Loader />
<Switch>
<Route path="/" exact> <Home /> </Route>
<Route path="/users"> <Users /> </Route>
<Route path="/articles"> <Article /> </Route>
...
</Switch>
</div>
Then create custom hook:
// useGlobalLoading.js
import ...
export default (isLoading) => {
const dispatch = ...;
useEffect(() => {
dispatch(showLoading());
return () => {
dispatch(hideLoading());
}
}, [isLoading])
}
and then in any component
...
const Users = () => {
const [isLoading, setLoading] = useState(true);
const [users, setUsers] = useState([]);
useGlobalLoading(isLoading);
const fetch = useCallback(async () => {
try {
const response = await api.getUsers(...);
setUsers(response.data.users);
setIsLoading(false);
} catch (error) { ... }
}, []);
if (isLoading) {
return null;
}
return <div>{users ... }</div>
}
From other side you can create reusable component Loader, and import it to all components where you have to show it:
// Users.js or Articles.js
return (
<>
{isLoading && <Loader />}
{!isLoading && <div>{users...}</div>}
</>
)