React Router and customHistory push not rendering new component - javascript

I am using Router and customHistory to help me redirect the pages, but the pages not render correctly.
The code works like this: if the user is authorized or log in, then the user should be redirected to "localhost:8080/dashboard" and see the dashboard(with data fetching from firebase) & header; if the use is log out, then the user should be redirect to "locahost:8080/" and see the log in button with the header.
However, after I successfully log in, the url is "localhost:8080/dashboard" without any data fetched from firebase, only things I can see are the header and login button. But if I hit "RETURN" with the current url which is "localhost:8080/dashboard", it will redirect to correct page with all data fetching from firebase, and no login button.
This is the github_link to the code.
I have spent times searching online, but do not find any positive result except this one. After reading the stackoverflow I feel my code has some problems with asynchronization. Any thoughts?
I really appreciate for your help! Thanks!
This is my AppRouter.js:
export const customHistory = createBrowserHistory();
const AppRouter = () => (
<Router history={customHistory}>
<div>
<Header />
<Switch>
<Route path="/" exact component={LoginPage} />
<Route path="/dashboard" component={ExpenseDashboardPage} />
<Route path="/create" component={AddExpensePage} />
<Route path="/edit/:id" component={EditExpensePage} />
<Route path="/help" component={HelpPage} />
<Route component={LoginPage} />
</Switch>
</div>
</Router>
);
This is my app.js
import React, { Children } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import "normalize.css/normalize.css"; //Normalize.css makes browsers render all elements more consistently and in line with modern standards.
import "./styles/styles.scss";
import AppRouter, { customHistory } from "./routers/AppRouter";
import configureStore from "./redux/store/configStore";
import { startSetExpenses } from "./redux/actions/expenses";
import { login, logout } from "./redux/actions/auth";
import "react-dates/lib/css/_datepicker.css";
import { firebase } from "./firebase/firebase";
//for testing: npm test -- --watch
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(<p>Loading...</p>, document.getElementById("app"));
let hasRendered = false;
const renderApp = () => {
if (!hasRendered) {
ReactDOM.render(jsx, document.getElementById("app"));
hasRendered = true;
}
};
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("log in");
store.dispatch(login(user.uid));
store.dispatch(startSetExpenses()).then(() => {
renderApp();
if (customHistory.location.pathname === "/") {
customHistory.push("/dashboard");
}
});
} else {
console.log("log out");
store.dispatch(logout());
renderApp();
customHistory.push("/");
}
});
This is the header.js
import React from "react";
import { BrowserRouter, Route, Switch, Link, NavLink } from "react-router-dom";
import { connect } from "react-redux";
import { startLogout } from "../redux/actions/auth";
export const Header = ({ startLogout }) => (
<header>
<h1>Expensify</h1>
<NavLink to="/" activeClassName="is-active">
Dashboard
</NavLink>
<NavLink to="/create" activeClassName="is-active">
CreateExpense
</NavLink>
<button onClick={startLogout}>Logout</button>
</header>
);
const mapDispatchToProps = (dispatch) => ({
startLogout: () => dispatch(startLogout()),
});
export default connect(undefined, mapDispatchToProps)(Header);

Related

Issue with history.push

I'm working on react Disney+ clone etc and I was trying to do something like: if user isnt authorized then show login page but if authorized then show content. I used useHistory for this. And it works for a second, it just starts to download login page (background image is loading, but text of login page is visible) and then it disappears and content page is shown. Url changes for a second too.
App.js
function App() {
return (
<div className="App">
<Router>
<Header />
<Switch>
<Route path="/login">
<Login/>
</Route>
<Route path="/detail/:id">
<Detail/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Router>
</div>
);
}
Header.js
import React from 'react'
import {selectUserName, selectUserPhoto, setUserLogin, setUserSignOut} from '../../features/user/userSlice' ;
import { useDispatch, useSelector } from "react-redux" ;
import { auth, provider} from "../../firebase"
import { useHistory} from 'react-router-dom';
const Header = () => {
const dispatch = useDispatch()
const userName = useSelector(selectUserName);
const userPhoto = useSelector(selectUserPhoto);
const history = useHistory();
const signIn = () => {
auth.signInWithPopup(provider)
.then((result) => {
let user = result.user;
dispatch(setUserLogin({
name: user.displayName,
email: user.email,
photo: user.photoURL
}))
history.push('/');
})
}
const signOut = () => {
auth.signOut()
.then(() => {
dispatch(setUserSignOut());
history.push('/login');
})
}
}
Based on your issue you can handle Routes in a different way. So you have routes which can only shown during unauthorised situation and some routes only shown for authorised user. For that you can have following implementation.
First you can create ProtectedRoute function.
import React from "react";
import { Redirect, Route } from "react-router-dom";
function ProtectedRoute({ component: Component, ...restOfProps }) {
const isAuthenticated = localStorage.getItem("isAuthenticated");
console.log("this", isAuthenticated);
return (
<Route
{...restOfProps}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
export default ProtectedRoute;
And then you can use this function in your main App where you will declare your routes with component.
import ProtectedRoute from "./component/ProtectedRoute";
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path="/login" component={Login} />
<ProtectedRoute path="/protected" component={ProtectedComponent} />
</BrowserRouter>
</div>
);
}
export default App;

Default route always execute in react router

I am working on a project where I am using the strikingDash template. Here I face some issues of routing while changing the routes from URL.
auth.js
import React, { lazy, Suspense } from "react"
import { Spin } from "antd"
import { Switch, Route, Redirect } from "react-router-dom"
import AuthLayout from "../container/profile/authentication/Index"
const Login = lazy(() =>
import("../container/profile/authentication/overview/SignIn")
)
const SignUp = lazy(() =>
import("../container/profile/authentication/overview/SignUp")
)
const ForgetPassword = lazy(() =>
import("../container/profile/authentication/overview/ForgetPassword")
)
const EmailConfirmation = lazy(() =>
import("../container/profile/authentication/overview/EmailConfirmation")
)
const VerificationPage = lazy(() =>
import("../container/profile/authentication/overview/VerificationPage")
)
const NotFound = () => {
console.log("NOT FOUND")
return <Redirect to="/" />
}
const FrontendRoutes = () => {
return (
<Switch>
<Suspense
fallback={
<div className="spin">
<Spin />
</div>
}
>
<Route exact path="/verification" component={VerificationPage} />
<Route exact path="/email-confirmation" component={EmailConfirmation} />
<Route exact path="/forgetPassword" component={ForgetPassword} />
<Route exact path="/signup" component={SignUp} />
<Route exact path="/" component={Login} />
<Route component={NotFound} />
</Suspense>
</Switch>
)
}
export default AuthLayout(FrontendRoutes)
App.js
import React, { useEffect, useState } from "react";
import { hot } from "react-hot-loader/root";
import { Provider, useSelector } from "react-redux";
import { ThemeProvider } from "styled-components";
import { BrowserRouter as Router, Redirect, Route } from "react-router-dom";
import { ConfigProvider } from "antd";
import store from "./redux/store";
import Admin from "./routes/admin";
import Auth from "./routes/auth";
import "./static/css/style.css";
import config from "./config/config";
import ProtectedRoute from "./components/utilities/protectedRoute";
const { theme } = config;
const ProviderConfig = () => {
const { rtl, isLoggedIn, topMenu, darkMode } = useSelector(state => {
return {
darkMode: state.ChangeLayoutMode.data,
rtl: state.ChangeLayoutMode.rtlData,
topMenu: state.ChangeLayoutMode.topMenu,
isLoggedIn: state.Authentication.login,
};
});
const [path, setPath] = useState(window.location.pathname);
useEffect(() => {
let unmounted = false;
if (!unmounted) {
setPath(window.location.pathname);
}
// eslint-disable-next-line no-return-assign
return () => (unmounted = true);
}, [setPath]);
return (
<ConfigProvider direction={rtl ? "rtl" : "ltr"}>
<ThemeProvider theme={{ ...theme, rtl, topMenu, darkMode }}>
<Router basename={process.env.PUBLIC_URL}>
{!isLoggedIn ? <>{console.log("INSIDE PUBLIC")}<Route path="/" component={Auth} /></> : <ProtectedRoute path="/admin" component={Admin} />}
{isLoggedIn && (path === process.env.PUBLIC_URL || path === `${process.env.PUBLIC_URL}/`) && (
<Redirect to="/admin" />
)}
</Router>
</ThemeProvider>
</ConfigProvider>
);
};
function App() {
return (
<Provider store={store}>
<ProviderConfig />
</Provider>
);
}
export default hot(App);
Whenever I change the URL to another route as I described above in Frontend Routes. Then it will always print console statements like these:
INSIDE PUBLIC
NOT FOUND
INSIDE PUBLIC
NOT FOUND
Expected Behaviour: Whenever I update the URL it will render the component according to the switch case and return it back
Actual Behaviour: Whenever I update the URL it will render the component as well as the default component. I think Switch here renders multiple components, but I don't know why.
I just resolved the issue by moving the Switch Tag inside the Suspense tag in the auth.js file.
The problem should be in the order of your pages: the root path works as a collector of all the pages, you should try to add the exact keyword to the Router path. Here the reference for the differences between the different notations.
<Route exact path="/" component={Login} />

My react route is redirecting to localhost:3000 and not the page it should redirect to

I have 2 components both are exactly the same. one is redirected to when I click on a Navlink inside of my navbar that I created using react-bootstrap. The other component that is exactly the same just redirects to localhost:3000 and not "./member" when I click on the html button that should redirect to that component. Please help me.
the html button and the function to redirect look like
import {Link, Route, withRouter, useHistory} from 'react-router-dom'
const Posts = (props) => {
const dispatch = useDispatch();
const history = useHistory();
const getProfile = async (member) => {
// const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
history.push('/member')
}
return (
<div>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</div>
)
}
export default withRouter(Posts);
The routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
</Switch>
)
}
export default Routes
The component that I am trying to redirect to is exactly the same as one that is redirected to and working when I click on it from the navlink. I have downgraded to history 4.10.1
My index.js is
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Router, Route } from 'react-router-dom';
import * as history from 'history';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer)
const userHistory = history.createBrowserHistory();
ReactDOM.render(
<Provider store = {store}>
<Router history={userHistory}>
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
</Router>
</Provider>,
document.getElementById('root'));
serviceWorker.unregister();
When I wrap the app route in the url goes to ./member but it does not load.
<Switch>
<Route path="/" exact component={app} />
<Route path="/member" component={Member} />
</Switch>
<button onClick={() => history.push(`/${p.publisher}`)}>Profile</button>

How to fix useEffect loader spinner problem

This is a useEffect gotcha and I run into it at least once a month. :(
Anyway,
I have a component that is rendering one of two components based on a state condition.
This works fine except for one problem. I get the infamous "flicker" render of the previous component. I am trying to mask this with a third component - dumb loader spinner. This is where the problem occurs. I can't get the dumb thing to work.
My working code is the following. The only relevant parts are those with comments.
Scroll further down for my non-working pseudo code solution.
Thank you.
import React, {useState} from 'react';
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import { withRouter } from "react-router";
import {Switch} from 'react-router';
import LandingWithoutClients from './PageComponents/Landing';
import LandingWithClients from './PageComponents/Landing/LandingWithClients';
import Workflows from "./PageComponents/Workflows";
import SaveAndLoad from "./PageComponents/SaveAndLoad";
import Calendar from "./PageComponents/Calendar";
import Navbar from "./PageComponents/Navigation/Navbar";
import Authentication from './PageComponents/Authentication'
import Navigation from "./PageComponents/Navigation";
import { MuiPickersUtilsProvider } from 'material-ui-pickers';
import MomentUtils from '#date-io/moment';
import db from "./services/indexDB";
import SaveIcon from "#material-ui/icons/Save";
function App(props){
const [clientExistsState, updateClientsExistsState] = useState(false);
db.clients.toArray(function(data){
if(data[0]){
updateClientsExistsState(true)
}else{
updateClientsExistsState(false)
}
})
let Nav = clientExistsState ? Navbar : Navigation
//_____________________________________________________If clientsExists assign Landing with LandingWithClients otherwise assign Landing with LandingWithoutClients
let Landing = clientExistsState ? LandingWithClients : LandingWithoutClients
//___________________________________________________________________________________
function redirectToClientsList(){
window.location.href = "/";
}
function redirectToCalendar(){
window.location.href = "/calendar";
}
function redirectToAuthentication(){
window.location.href = "/authentication";
}
function redirectToSaveAndLoad(){
window.location.href = "/save-and-load";
}
return (
<div className="App">
<Provider>
<MuiPickersUtilsProvider utils={MomentUtils}>
<BrowserRouter>
<div>
<Nav
endpointProps = {props}
redirectToClientsList = {redirectToClientsList}
redirectToCalendar={redirectToCalendar}
redirectToAuthentication={redirectToAuthentication}
redirectToSaveAndLoad={redirectToSaveAndLoad}
/>
<Switch>
<Route exact path="/" component={Landing} /> {/* Assign Landing Component*/}
<Route exact path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact path="/calendar" component={Calendar} />
<Route exact path="/authentication" component={Authentication} />
<Route exact path="/save-and-load" component={SaveAndLoad} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</Provider>
</div>
);
}
export default withRouter(App);
here is a pseudo code fix with two useEffect instances
function App(props){
// code ...
cons [ loaderBool, setLoaderBool] = useState(true);
let Landing = Loader;
useEffect(() => {
if (loaderBool) {
setTimeout(() => {
setLoaderBool(false)
},500)
}
}, [])
useEffect(() => {
if (loaderBool) {
Landing = Loader
} else {
Landing = clientExistsState ? LandingWithClients : LandingWithoutClients
}
}, [loaderBool])
return(
<div>
<Route exact path="/" component={Landing} />
</div>
)
}
I fixed it like this:
import React, {useState, useEffect} from 'react';
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import { withRouter } from "react-router";
import {Switch} from 'react-router';
import LandingWithoutClients from './PageComponents/Landing';
import LandingWithClients from './PageComponents/Landing/LandingWithClients';
import Workflows from "./PageComponents/Workflows";
import SaveAndLoad from "./PageComponents/SaveAndLoad";
import Calendar from "./PageComponents/Calendar";
import Navbar from "./PageComponents/Navigation/Navbar";
import Loader from './PageComponents/Loader';
import Authentication from './PageComponents/Authentication'
import Navigation from "./PageComponents/Navigation";
import { MuiPickersUtilsProvider } from 'material-ui-pickers';
import MomentUtils from '#date-io/moment';
import db from "./services/indexDB";
import SaveIcon from "#material-ui/icons/Save";
import Context,{Provider} from "./services/context";
// if client is active display Navigation.
// if client is not active then display NavigationWitSlide
// create new landing page
function App(props){
const [loaderBool, setLoaderBool] = useState(true)
const [clientExistsState, updateClientsExistsState] = useState(false);
db.clients.toArray(function(data){
if(data[0]){
updateClientsExistsState(true)
}else{
updateClientsExistsState(false)
}
})
let Nav = clientExistsState ? Navbar : Navigation
let Landing = clientExistsState ? LandingWithClients : LandingWithoutClients
function redirectToClientsList(){
window.location.href = "/";
}
function redirectToCalendar(){
window.location.href = "/calendar";
}
function redirectToAuthentication(){
window.location.href = "/authentication";
}
function redirectToSaveAndLoad(){
window.location.href = "/save-and-load";
}
// check if clients exists
useEffect(()=>{
setTimeout(()=>{
setLoaderBool(false)
},500)
},[])
return (
<div className="App">
<Provider>
<MuiPickersUtilsProvider utils={MomentUtils}>
<BrowserRouter>
<div>
<Nav
endpointProps = {props}
redirectToClientsList = {redirectToClientsList}
redirectToCalendar={redirectToCalendar}
redirectToAuthentication={redirectToAuthentication}
redirectToSaveAndLoad={redirectToSaveAndLoad}
/>
<Switch>
<Route exact path="/" component={(function(){
if(loaderBool){
return Loader
}else{
return Landing
}
}())} />
<Route exact path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact path="/calendar" component={Calendar} />
<Route exact path="/authentication" component={Authentication} />
<Route exact path="/save-and-load" component={SaveAndLoad} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</Provider>
</div>
);
}
export default withRouter(App);
Try useMemo.
const Landing = useMemo(() => {
if (!loaderBool) {
if (clientExistsState) {
return LandingWithClients;
}
return LandingWithoutClients;
}
return Loader;
}, [clientExistsState, loaderBool]);

React-router-dom using "#" in Link path does not navigate to Component

I'm trying to create a feature where when a user clicks on a <Link>, they navigate to another component (Post) and scroll directly to a Comment. The path satisfies the requirements for the <Route> definition, but when I use a "#" as part of the URL, the redirect does not take affect:
Route: <Route path="/post/:id/:hash" component={Post} />
URL: https://b881s.codesandbox.io/post/4/#ve33e
However, what's interesting is that the feature works as expected when I use a "#" instead of "#".
URL: https://b881s.codesandbox.io/post/4/#ve33e
I've tried to find any mentions of "#" being some sort of special character to react-router-dom, but have not found anything. Maybe there's something I'm fundamentally missing here?
Here's the sandbox with working code: https://codesandbox.io/s/scrollintoview-with-refs-and-redux-b881s
App.js
import React from "react";
import ReactDOM from "react-dom";
import Home from "./Home";
import Posts from "./Posts";
import Post from "./Post";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import store from "./store";
import { Provider } from "react-redux";
import "./styles.css";
const App = () => {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/posts" component={Posts} />
<Route path="/post/:id/:hash" component={Post} />
<Route path="/post/:id/" component={Post} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Posts.js
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
class Posts extends React.Component {
createPostList = () => {
const { posts } = this.props;
if (posts.posts) {
return posts.posts.map(post => {
return (
<div key={post.id} className="post">
<Link to={`/post/${post.id}`}>{post.title}</Link>
<p>{post.text}</p>
<div>
{post.comments.map(comment => {
return (
<div>
<Link to={`/post/${post.id}/#${[comment.id]}`}>
{comment.id}
</Link>
</div>
);
})}
</div>
</div>
);
});
} else {
return <h4>Loading...</h4>;
}
};
render() {
return <div>{this.createPostList()}</div>;
}
}
const mapStateToProps = state => {
return {
posts: state.posts
};
};
export default connect(mapStateToProps)(Posts);
Anything after # in a URL string is called hash. You can access the hash for a given location using location.hash. So in your routes you won't need to mention :hash. You should instead read the hash through the location object injected to the component by the Route component.
Your Route:
<Route path="/post/:id" component={Post} />
To read hash in Post component:
class Post extends React.Component {
render() {
const {location} = this.props;
console.log(location.hash);
...
}
}
Use %23 as a hash sign, should definitely solve it.
More information about it: https://en.wikipedia.org/wiki/Percent-encoding
Here is a forked from you, that I use %23 to represent #
https://codesandbox.io/s/scrollintoview-with-refs-and-redux-z8pz8

Categories

Resources