React - <Link> tag displays the dynamic url in browser but does not render the page unless the browser is refreshed - javascript

I am a newbie trying to build a React blog. Routes etc work fine except when a dynamic route comes into question. The Link does not render unless refreshed.
Here is App.js where the Routes are defined
function App() {
const [username, setUsername] = useState('');
const [loggedIn, setLoggedIn] = useState(false);
return (
<>
<Router history={customHistory}>
<div className="App">
<UserContext.Provider value={{ username, setUsername, loggedIn, setLoggedIn }}>
<Navigation />
<Switch>
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
<Route exact path="/blog/:id" component={BlogDetails} />
<Route path="/content" component={Content} />
<Route path="/logout" component={Logout} />
<Route exact path="/" component={Home} />
</Switch>
</UserContext.Provider>
</div>
</Router>
</>
)
}
export default App;
Here is BlogItems.js which calls the :
import { Link } from 'react-router-dom';
function BlogItems() {
const [blogList, setBlogList] = useState([])
useEffect(() => {
Axios.get("http://localhost:3001/api/get"
, { withCredentials: true })
.then((res) => {
setBlogList(res.data);
});
}, []);
return (
<div className="blog-list">
{blogList && blogList.map((blog) => {
return (
<div className="blog-elems" key={blog && blog.id}>
<Link to={`/blog/${blog.id}`}>{blog.title}</Link>
<h6>By <span>Ritu Rawat</span> on {DateFormater(blog.dated)}</h6>
<div className="blog-list-body">{blog && blog.body.slice(0, 200) + '...'}</div>
</div>
);
})}
</div>
) }
export default BlogItems;
and this is the actual BLogDetails Page which needs to be called from the dynamic route:
const BlogDetails = (props) => {
console.log(props);
//const { username } = useContext(UserContext);
const [blog, setBlog] = useState(null);
const { id } = useParams();
useEffect(() => {
console.log("BLOG ARTICLE");
Axios.get(`http://localhost:3001/api/blog/${id}`
, { withCredentials: true })
.then((res) => {
setBlog(res.data[0]);
});
}, [id]);
return (
<>
<div className="blog-background">
<div className="blog-list">
<div className="blog-elems" key={blog && blog.id}>
<h2> {blog && blog.title}</h2>
<h6>By <span>Ritu Rawat</span> on {blog && DateFormater(blog.dated)}</h6>
<div className="blog-list-body">{blog && blog.body}</div>
</div>
<Comments id={id} />
</div>
</div>
</>
)
}
export default BlogDetails;

Related

After login it won't to navigate me to the other page [duplicate]

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 21 days ago.
I have a problem now because it won't navigate me after I click log in, it takes cookies, but won't relocate. You can show my components look right now. Everything is fine, except when I click log in, it won't relocates me to the home page.
Components:
<App /> component
function App() {
const [userData, setUserData] = useState();
const [login, setLogin] = useState()
return (
<div className="App">
<Router>
<Routes>
<Route element={<PrivateRoutes login={login} userData=
{userData} />}>
<Route element={<Home />} path="/" exact />
<Route element={<Products />} path="/products" />
</Route>
<Route element={<Login setUserData={setUserData} setLogin=
{setLogin} />}
path="/login" />
</Routes>
</Router>
</div>
);
}
<Login /> component
const Login = ({ setUserData }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [checking, setChecking] = useState(true)
const [loginStatus, setLoginStatus] = useState("");
Axios.defaults.withCredentials = true;
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
navigate("/");
});
};
useEffect(() => {
if (userData?.loggedIn) {
navigate("/");
}
}, [userData]);
}
return (
<div>
<div>Prijava</div>
<div>
<form onSubmit={login}>
<label>Korisničko ime</label>
<input
type="text"
onChange={(e) => {
setUsername(e.target.value);
}}
/>
<label>Lozinka</label>
<input
type="password"
onChange={(e) => {
setPassword(e.target.value);
}}
></input>
<button>Prijavi se</button>
</form>
</div>
</div>
);
};
<PrivateRoutes /> component
const PrivateRoutes = ({userData, login}) => {
return !login ? <p>Checking...</p>: userData?.loggedIn ? <Outlet
/> : <Navigate to="/login" />;
};
Console log error, after implementing code.
Newest App and PrivateRoutes version
When the page loads, userData is not set, because the useEffect runs after the JSX is rendered. Hence your condition is falling to <Navigate to="/login" /> instead of <Outlet/>.
You could use an additional state, called checking, for example, use it to display some loading message while the data is fetched.
Also, move the useEffect to check the login state on load inside PrivateRoutes. Changes your components as follows.
App:
function App() {
const [userData, setUserData] = useState();
const [checking, setChecking] = useState(true);
useEffect(() => {
Axios.get("http://localhost:3001/login")
.then((response) => {
if (response.data.loggedIn == true) {
setUserData(response.data);
}
return;
})
.catch((error) => {
console.log(error);
})
.finally(() => {
setChecking(false);
});
}, []);
return (
<div className="App">
<Router>
<Routes>
<Route element={<PrivateRoutes userData={userData} checking={checking} />}>
<Route element={<Home />} path="/" exact />
<Route element={<Products />} path="/products" />
</Route>
<Route element={<Login setUserData={setUserData} userData={userData} />} path="/login" />
</Routes>
</Router>
</div>
);
}
PrivateRoutes:
const PrivateRoutes = ({ userData, checking }) => {
return checking ? <p>Checking...</p> : userData?.loggedIn ? <Outlet /> : <Navigate to="/login" />;
};
Login:
import { useNavigate } from "react-router-dom";
const Login = ({ setUserData, userData }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
Axios.defaults.withCredentials = true;
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
});
};
useEffect(() => {
if (userData) {
navigate("/");
}
}, [userData]);
return (
<div>
<form>
<div>Prijava</div>
<div>
<form onSubmit={login}>
<label>Korisničko ime</label>
<input
type="text"
onChange={(e) => {
setUsername(e.target.value);
}}
/>
<label>Lozinka</label>
<input
type="password"
onChange={(e) => {
setPassword(e.target.value);
}}
></input>
<button>Prijavi se</button>
</form>
</div>
</form>
</div>
);
};
To resolve the issue with the userData state being undefined during the initial render, you can initialize it with a default value in the state declaration, for example:
const [userData, setUserData] = useState({ loggedIn: false });
Additionally, you can wrap the conditional rendering in a useEffect hook that depends on userData:
useEffect(() => {
if (userData?.loggedIn) {
// Render the Outlet component
} else {
// Render the Navigate component
}
}, [userData]);

How to pass location state and URL params using React-Router?

When I click on the link in the HoverBooks Component to get to a new page where I can render the book location state in Book component, but when I press on it nothing happens. I think the error is in Route:
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/book:/book.Key">
<Book />
</Route>
<Route path="/signin">
<Signin />
</Route>
<Route path="/">
<Header />
<Home />
</Route>
</Switch>
</Router>
</div>
)
}
export default App
import React from 'react'
import { useLocation } from 'react-router-dom'
const Book = () => {
const {
state: { book },
} = useLocation()
console.log({ book })
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
export default Book
const HoverBooks = ({ ...book }) => {
const [inHoverBooks, setInHoverBooks] = React.useState(false)
return (
<>
<Link
to={{
pathName: `/book/${book.key}`,
state: {
book,
},
}}
>
<img
onMouseLeave={() => setInHoverBooks(false)}
onMouseEnter={() => setInHoverBooks(true)}
src={book.image}
key={book.key}
/>
</Link>
{inHoverBooks && (
<div className="hover__containter">
<h3>{book.bookName}</h3>
<h2>{book.by}</h2>
<h2>{book.Narreted}</h2>
<h2>{book.length}</h2>
<h2>{book.rating}</h2>
</div>
)}
</>
)
}
export default HoverBooks
Below is the correct form, e.g. /:someName, to define a route with URL params:
<Route path="/book/:bookKey">
<Book />
</Route>
And here is the right syntax to make a Link for the above route:
<Link
to={{
pathname: `/book/SOME_BOOK_KEY`, // replace SOME_BOOK_KEY with some value
state: {
book, // e.g. const book = { key: 'js', bookName: 'Learn JavaScript'}
},
}}
>
<img src="some_src" alt="something" />
</Link>
And you useParams and useLocation react-hooks to access the "URL params" and "location state" in a component:
const Book = () => {
const {
state: { book },
} = useLocation()
const { bookKey } = useParams();
console.log(book, bookKey)
// prints "book" object (from location state) and "bookKey" (from URL params)
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
I would suggest you to add typescript to your ReactJS app. It helps you find errors early by doing "static Type-checking".
With react router you need to pass the component you want to render to the Route like this
const ComponentA = (props) => {...}
<Route path="/component-a" component={ComponentA} />
And here is how to link to component a
<Link to="/component-a" >Go to component A</Link>

how to show a component on specific pages

I want to show a navbar whenever user is logged in
when ever user presses login button i replace the page with home page
i've tried to define use state and on condition of if path name is not equal to /login or /step-two set it to true then in the app i said if usestate was equal by true show the navbar
but i don't see navbar until i refresh the page
here is what i have tried in useeffect
const [hasNavBar, setNavBar] = useState(false)
useEffect(() => {
console.log(history.location.pathname);
if (history.location.pathname !== '/login'|| history.location.pathname !=='/step-two'
) {
setNavBar(true)
}
}, [])
here is the whole code
const App = () => {
const [hasNavBar, setNavBar] = useState(false)
useEffect(() => {
console.log(history.location.pathname);
if (history.location.pathname !== '/login' ||
history.location.pathname !== '/step-two'
) {
setNavBar(true)
}
}, [])
return (
<div>
<SNackbar></SNackbar>
<Router history={history}>
<div>
<Route path="/login" exact component={FirstLogin} />
<Route path="/step-two" exact component={SecondLogin} />
<Route path="/home" exact component={Home} />
<Route path='/ranking-list' exact component={RankingListPage} />
</div>
</Router>
<div>
{hasNavBar ?
<div>
<BottomNavBar />
</div> : ''
}
</div>
</div>
)
};
export default App;
update
i tried to use history.listen bu still i see the same error
const App = () => {
const [hasNavBar,setnavbar]=useState(false)
useEffect(() => {
history.listen(()=>{
if (history.location.pathname === '/'||history.location.pathname === '/steptwo') {
setnavbar(false);
}else{setnavbar(true);}
})
if (localStorage.getItem("token") && history.location.pathname === '/') {
history.replace('/home');
}
}, [])
return (
<div>
<SNackbar></SNackbar>
<Router history={history}>
<div>
<Route path="/" exact component={FirstLogin} />
<Route path="/steptwo" exact component={SecondLogin} />
<Route path="/home" exact component={Home} />
<Route path='/ranking-list' exact component={RankingListPage} />
</div>
</Router>
{hasnavbar?
<div
style={{ position: 'fixed', bottom: '0px', width: "100%", height: '80px' }}>
<BottomNavBar />
</div>:''
}
</div>
)
};
export default App;
as #lissettdm said
you need to use history.listen to call an function whenever history changes
but no need to Wrap App inside Router
if you don't use history.listen usestate only would change whenever you refresh the page as the useeffect would be run on refresh
you could define its default to false and whenever it is not in the condition it would be true a else statement so that whenever you change the route it would be called and make hasnavbar to true
const [hasnavbar,setnavbar]=useState(false)
useEffect(() => {
history.listen(()=>{
if (history.location.pathname === '/login'||history.location.pathname === '/steptwo') {
setnavbar(false);
}else{setnavbar(true);}
})
}, [])
here is the whole code
const App = () => {
const [hasnavbar,setnavbar]=useState(false)
useEffect(() => {
history.listen(()=>{
if (history.location.pathname === '/login'||history.location.pathname === '/steptwo') {
setnavbar(false);
}else{setnavbar(true);}
})
}, [])
return (
<div>
<SNackbar></SNackbar>
<Router history={history}>
<div>
<Route path="/login" exact component={FirstLogin} />
<Route path="/steptwo" exact component={SecondLogin} />
<Route path="/home" exact component={Home} />
<Route path='/ranking-list' exact component={RankingListPage} />
</div>
</Router>
{hasnavbar?
<div>
<BottomNavBar />
</div>:''
}
</div>
)
};
export default App;
Wrap App inside Router component and listen for history changes:
index.js
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById("root")
);
App.js
const history = useHistory();
useEffect(() => {
return history.listen(location => {
if (
history.location.pathname !== "/login" &&
history.location.pathname !== "/step-two"
) {
setNavBar(true);
}
});
}, []);
See working example: https://stackblitz.com/edit/react-5xd5d1?file=src%2FApp.js
Change your useEffect's logic from || to &&, right now it doesn't make sense.
Try to use a useLocation hook instead:
import { useLocation } from 'react-router-dom';
...
let location = useLocation();
...
if (location.pathname !== '/login' && location.pathname !== '/step-two') { ... }

Protected route not working correctly with React and Firebase

I'm building a small app with firebase and react and currently working on implementing the authentication. I've set the onAuthStateChanged in my app component as a side effect and whenever user is logged in it should be redirected to a desired component from ProtectedRoute.
This works correctly but unfortunately when refreshing the page the ProtectedRoute is not rendering correct component and is just firing redirection.
I get what is happening: on refresh user is empty and only after then it change so I would expect to see a screen flicker and a proper redirection.
Could you please look at below code and maybe tell me how to fix this behavior?
App component:
const App = () => {
const [authUser, setAuthUser] = useState<firebase.User | null>(null);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
authUser ? setAuthUser(authUser) : setAuthUser(null);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute exact path={ROUTES.HOME} component={Home} />
<ProtectedRoute exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</div>
</Router>
</AuthUserContext.Provider>
);
};
Protected Route:
interface Props extends RouteProps {
component?: any;
children?: any;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
...rest
}) => {
const authUser = useContext(AuthUserContext);
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};
Found the fix. Had to add the flag checking for user authentication status (default value of that flag is set to true). Flag needs to be passed to ProtectedRoute as prop and if is True then render some loading component:
App component:
const App = () => {
const [authUser, setAuthUser] = useState(false);
const [authPending, setAuthPending] = useState(true);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
setAuthUser(!!authUser);
setAuthPending(false);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Switch>
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.HOME}
component={Home}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.ACCOUNT}
component={Account}
/>
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</Switch>
</div>
</Router>
</AuthUserContext.Provider>
);
};
ProtectedRoute:
interface Props extends RouteProps {
component?: any;
children?: any;
pendingAuth: boolean;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
pendingAuth,
...rest
}) => {
const authUser = useContext(AuthUserContext);
if (pendingAuth) {
return <div>Authenticating</div>;
}
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};

React Router Redirect to component not redirecting

I need a simple redirect to a component but its not working not sure why. This is the code:
const HomePage = () => {
const [videos, setVideos] = useState([]);
const videoClicked = (video) => {
return <Redirect to='/video' />
}
if(videos === []){
return <div>Loading ...</div>
}
return (
<div>
{videos.map(video => (
<div onClick={() => videoClicked(video)}>
<VideoThumbnail video={video} />
</div>
))}
</div>
)
}
export default HomePage
I have a useEffect in my HomePage function that I didnt include in this snippet that gives videos values. It works and when I onClick the div it calls videoClicked but the redirect doesnt work.
This is my router:
const App = () => {
return (
<HashRouter>
<Switch>
<Route exact path="/video" component={VideoPage} />
<Route path="/" component={HomePage} />
</Switch>
</HashRouter>
)
}
Also when I get this working is it possible to redirect to component and pass props thru it instead of just passing a string in the to tag.
You can have a new state and redirect based on that:
const HomePage = () => {
const [videos, setVideos] = useState([]);
const [clicked, setClicked] = useState(false);
const videoClicked = (video) => {
setClicked(true);
// return <Redirect to='/video' />
}
if (videos === []) {
return <div>Loading ...</div>
}
return (
clicked ? <Redirect to={{
pathname: '/video',
state: { someData: 'test' }
}} /> : (
<div>
{videos.map(video => (
<div onClick={() => videoClicked(video)}>
<VideoThumbnail video={video} />
</div>
))}
</div>
)
)
}
export default HomePage
and you can use props.location.state.someData in the component you're redirected to.
you can consider using History HTML5 instead :) simple and straightforward

Categories

Resources