Material UI showing Half white screen - reactjs

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:

Related

Changing props state using api causes infinite loop

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>
);

React context requires 2 state updates for consumers to re-render

So I have a straight forward app that requires you to login to see a dashboard. I've based my auth flow off of https://reactrouter.com/web/example/auth-workflow which in return bases their flow off of https://usehooks.com/useAuth/
Currently, when a user logs in it calls a function within the context provider to sign in and that function updates the state of the context with the user data retrieved from the server. This is reflected in React dev tools under my context providers as shown in the teacher attribute:
When the context state has successfully been updated I then use useHistory().push("dashboard/main") from the react-router API to go to the dashboard page. The dashboard is a consumer of the context provider but the teacher value is still null when I try rendering the page- even though React dev tools clearly shows the value has been updated. When I log in again, the dashboard will successfully render, so, ultimately, it takes two context updates in order for my Dashboard to reflect the changes and render. See my following code snippets (irrelevant code has been redacted):
App.js
const App = () => {
return (
<AuthProvider>
<div className="App">
<Switch>
<Route path="/" exact >
<Home setIsFetching={setIsFetching} />
</Route>
<ProtectedRoute path="/dashboard/:page" >
<Dashboard
handleToaster={handleToaster}
/>
</ProtectedRoute>
<ProtectedRoute path="/dashboard">
<Redirect to="/dashboard/main"/>
</ProtectedRoute>
<Route path="*">
<PageNotFound/>
</Route>
</Switch>
<Toaster display={toaster.display} setDisplay={(displayed) => setToaster({...toaster, display: displayed})}>{toaster.body}</Toaster>
</div>
</AuthProvider>
);}
AuthProvider.js
const AuthProvider = ({children}) => {
const auth = useProvideAuth();
return(
<TeacherContext.Provider value={auth}>
{children}
</TeacherContext.Provider>
);};
AuthHooks.js
export const TeacherContext = createContext();
export const useProvideAuth = () => {
const [teacher, setTeacher] = useState(null);
const memoizedTeacher = useMemo(() => ({teacher}), [teacher]);
const signin = (data) => {
fetch(`/api/authenticate`, {method: "POST", body: JSON.stringify(data), headers: JSON_HEADER})
.then(response => Promise.all([response.ok, response.json()]))
.then(([ok, body]) => {
if(ok){
setTeacher(body);
}else{
return {...body};
}
})
.catch(() => alert(SERVER_ERROR));
};
const register = (data) => {
fetch(`/api/createuser`, {method: "POST", body: JSON.stringify(data), headers: JSON_HEADER})
.then(response => Promise.all([response.ok, response.json()]))
.then(([ok, body]) => {
if(ok){
setTeacher(body);
}else{
return {...body};
}
})
.catch(() => alert(SERVER_ERROR));
};
const refreshTeacher = async () => {
let resp = await fetch("/api/teacher");
if (!resp.ok)
throw new Error(SERVER_ERROR);
else
await resp.json().then(data => {
setTeacher(data);
});
};
const signout = () => {
STORAGE.clear();
setTeacher(null);
};
return {
...memoizedTeacher,
setTeacher,
signin,
signout,
refreshTeacher,
register
};
};
export const useAuth = () => {
return useContext(TeacherContext);
};
ProtectedRoute.js
const ProtectedRoute = ({children, path}) => {
let auth = useAuth();
return (
<Route path={path}>
{
auth.teacher
? children
: <Redirect to="/"/>
}
</Route>
);
};
Home.js
const Home = ({setIsFetching}) => {
let teacherObject = useAuth();
let history = useHistory();
const handleFormSubmission = (e) => {
e.preventDefault();
const isLoginForm = modalContent === "login";
const data = isLoginForm ? loginObject : registrationObject;
const potentialSignInErrors = isLoginForm ?
teacherObject.signin(data) : teacherObject.register(data);
if(potentialSignInErrors)
setErrors(potentialSignInErrors);
else{
*******MY ATTEMPT TO PUSH TO THE DASHBOARD AFTER USING TEACHEROBJECT.SIGNIN********
history.replace("/dashboard/main");
}
};
};)};
Dashboard.js
const Dashboard = ({handleToaster}) => {
const [expanded, setExpanded] = useState(true);
return (
<div className={"dashboardwrapper"}>
<Sidebar
expanded={expanded}
setExpanded={setExpanded}
/>
<div className={"dash-main-wrapper"}>
<DashNav/>
<Switch>
<Route path="/dashboard/classroom" exact>
<Classroom handleToaster={handleToaster} />
</Route>
<Route path="/dashboard/progressreport" exact>
<ProgressReport/>
</Route>
<Route path="/dashboard/help" exact>
<Help/>
</Route>
<Route path="/dashboard/goalcenter" exact>
<GoalCenter />
</Route>
<Route path="/dashboard/goalcenter/create" exact>
<CreateGoal />
</Route>
<Route path="/dashboard/profile" exact>
<Profile />
</Route>
<Route path="/dashboard/test" exact>
<Test />
</Route>
<Route path="/dashboard/main" exact>
<DashMain/>
</Route>
</Switch>
</div>
</div>
);
};
Let me know if there's anything that stands out to you that would be preventing my Dashboard from rendering with the updated context values the first time instead of having to update it twice. Do let me know if you need more insight into my code or if I missed something- I'm also fairly new to SO. Also, any pointers on the structure of my app would be greatly appreciated as this is my first React project. Thank you.
I think the problem is in the handleFormSubmission function:
const handleFormSubmission = (e) => {
e.preventDefault();
const isLoginForm = modalContent === "login";
const data = isLoginForm ? loginObject : registrationObject;
const potentialSignInErrors = isLoginForm ?
teacherObject.signin(data) : teacherObject.register(data);
if(potentialSignInErrors)
setErrors(potentialSignInErrors);
else{
history.replace("/dashboard/main");
}
};
You call teacherObject.signin(data) or teacherObject.register(data) and then you sequentially change the history state.
The problem is that you can't be sure the teacher state has been updated, before history.replace is called.
I've made a simplified version of your home component to give an example how you could approach the problem
function handleSignin(auth) {
auth.signin("data...");
}
const Home = () => {
const auth = useAuth();
useEffect(() => {
if (auth.teacher !== null) {
// state has updated and teacher is defined, do stuff
}
}, [auth]);
return <button onClick={() => handleSignin(auth)}>Sign In</button>;
};
So when auth changes, check if teacher has a value and do something with it.

useEffect is in infinite loop

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.

React - Is there a way to check when page is done loading after route change?

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>}
</>
)

History.push() redirects to protected route on logout

I'm setting up a basic authentication system with React and while signup and login actions correctly redirect and render the appropriate components, my logout action redirects to the protected route and renders the associated component, even though the authentication variable managed with the context API is successfully updated when logging out. The whole operation works in the end, as when I'm refreshing the page, I am successfully redirected to my login page.
I'm using Node.js to manage my sessions and dispatching the logout action works well as, as I said, the variable used with the Context API is updated. I'm using the Effect Hook on my Header component where the logout is initiated and I can see the auth variable being changed.
Here is my code:
AppRouter.js
export const history = createBrowserHistory();
const AppRouter = () => (
<Router history={history}>
<Switch>
<PublicRoute path="/" component={AuthPage} exact={true} />
<PrivateRoute path="/dashboard" component={DashboardPage} />
<Route component={NotFoundPage} />
</Switch>
</Router>
);
PublicRoute.js
const PublicRoute = ({ component: Component, ...rest }) => {
const { uid } = useContext(AuthContext);
useEffect(() => {
console.log("Public Route - Variable set to:", uid);
}, [uid])
return (
<Route
render={props =>
uid !== undefined ? (
<Redirect to="/dashboard" />
) : (
<Component {...props}/>
)
}
{...rest}
/>
)
};
PrivateRoute.js
const PrivateRoute = ({ component: Component, ...rest }) => {
const { uid } = useContext(AuthContext);
useEffect(() => {
console.log("Private Route - Variable set to:", uid);
}, [uid])
return (
<Route
render={props =>
uid !== undefined ? (
<div>
<Header />
<Component {...props}/>
</div>
) : (
<Redirect to="/" />
)
}
{...rest}
/>
)
};
Header.js
export const Header = () => {
const { uid, dispatch } = useContext(AuthContext);
useEffect(() => {
console.log("Header - Variable set to:", uid);
// console.log("HIST", history);
}, [uid])
const logout = async () => {
const result = await startLogout();
if (result.type !== undefined) {
dispatch(result); // Works well
// window.location.href = '/';
// history.push('/');
history.replace('/');
} else {
console.log(result);
}
}
return (
<header className="header">
<div className="container">
<div className="header__content">
<Link className="header__title" to="/dashboard">
<h1>A React App</h1>
</Link>
<button className="button button--link" onClick={logout}>Logout</button>
</div>
</div>
</header>
);
};
I tried both history.push('/') and history.replace('/'). Both these 2 methods work well as if I switch the path to an unknown route, my component that handles 404 is successfully rendered.
Below is my console output when I click the logout button. As you can see, the auth variable is well updated to undefined but that does not prevent my router to keep showing me the protected route. The router should not redirect me to the dashboard as my auth variable is set to undefined after logging out.
Header - Variable set to: {uid: undefined}
Private Route - Variable set to: {uid: undefined}
Public Route - Variable set to: {uid: undefined}
Header - Variable set to: {uid: undefined}
Private Route - Variable set to: {uid: undefined}
For the time being I'm using window.location.href = '/'; which works well, as it automatically reload the root page but I'd like to stick to react-router. Any thoughts? Thanks
in the private route pass renders props.. like this:
const PrivateRoute = ({ component: Component, ...rest }) => {
const { uid } = useContext(AuthContext);
useEffect(() => {
console.log("Private Route - Variable set to:", uid);
}, [uid])
return (
<Route
render={props =>
uid !== undefined ? (
<div>
<Header {...props} />
<Component {...props}/>
</div>
) : (
<Redirect to="/" />
)
}
{...rest}
/>
)
};
then in header use props to push history:
export const Header = (props) => {
const { uid, dispatch } = useContext(AuthContext);
useEffect(() => {
console.log("Header - Variable set to:", uid);
// console.log("HIST", history);
}, [uid])
const logout = async () => {
const result = await startLogout();
if (result.type !== undefined) {
dispatch(result); // Works well
// window.location.href = '/';
// history.push('/');
props.history.push('/');
} else {
console.log(result);
}
}

Resources