useHistory - Cannot read property 'push' of undefined - javascript

When the home button is pressed I want to redirect it to the home page '/ '. But I am getting this error. What can I do ? :
import { Route, useHistory } from 'react-router-dom/cjs/react-router-dom.min';
const history = useHistory();
const homeButtonOnClickHandler = () =>{
history.push("/");
}
<Router>
<Header homeButtonOnClickHandler={homeButtonOnClickHandler}/>
<Switch>
<Route path='/result'>
<Result
ingredients={ingredients}
total={() => hesapla()}
/>
</Route>
<Route path='/'>
<Home
ingredients={ingredients}
total={() => calculate()}
ingredientAdd={(i) => ingredientAdd(i)}
ingredientRemove={(i) => ingredientRemove(i)}
selectedIngredients={ingredients}
isOrder={number}
/>
</Route>
</Switch>
</Router>

You are trying to use history hook outside of Router. Write your function inside Header component.
Try this inside Header component
const history = useHistory();
const homeButtonOnClickHandler = () =>{
history.push('/');
}

You just need to put hook and handler into component like here: link

Related

Exactly one route not working after deployed on Netlify

I have a react app in which i open a new tab to view products using a window.open(). This is working perfectly fine when i am working locally but after i deployed it to netlify that particular route says Page Not Found Error.
Below is my App.js
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
// for components
import AdminProtectRoute from "./components/AdminProtectRoute";
import UserProtectRoute from "./components/UserProtectRoute";
import Spinner from "./components/Spinner";
// for user
const BookDetails = lazy(() => import("./components/user/BookDetails"));
const UserInventory = lazy(() => import("./components/user/UserInventory"));
const UserIssuedBooks = lazy(() => import("./components/user/UserIssuedBooks"));
const UserRequested = lazy(() => import("./components/user/UserRequested"));
const ForgotLink = lazy(() => import("./components/user/ForgotLink"));
const ForgotPass = lazy(() => import("./components/user/ForgotPass"));
const Contact = lazy(() => import("./components/user/Contact"));
const WishList = lazy(() => import("./components/user/WishList"));
const MyAccount = lazy(() => import("./components/user/MyAccount"));
const EditProfile = lazy(() => import("./components/user/EditProfile"));
function App() {
return (
<div className="app">
<ToastContainer />
<Router>
<Suspense fallback={<Spinner />}>
<Routes>
<Route element={<UserProtectRoute />}>
<Route path="/users" element={<User />}>
<Route index path="userissued" element={<UserIssuedBooks />} />
<Route path="userrequested" element={<UserRequested />} />
<Route path="userinventory" element={<UserInventory />} />
<Route path=":bookId" element={<BookDetails />} /> // this route is not working
<Route path="wishlist" element={<WishList />} />
</Route>
<Route path="/account" element={<MyAccount />} />
<Route path="/edit-profile" element={<EditProfile />} />
</Route>
</Routes>
</Suspense>
</Router>
</div>
);
}
export default App;
Below i my handler function in one of my components which is triggered by a button click that creates a new tab for that :bookId route:
const handleView = () => {
window.open('http://localhost:3000/users/' + book._id, "_blank")
}
When i replace http://localhost:3000 with https://my_app.netlify.app and deploy it, this function creates a new tab but gives me "Page not Found" Error but locally it creates a new tab and renders the required component as well.
Also i tried to enter the :bookId in my same tabs url search bar like given below:
https://my_app.netlify.app/users/63cb6d482ef3b2
but even this is not working
I tried my solve this but i unable to do so. Please help. Thanks in advance.
You have to setup the redirects, create a netlify.toml in the root of your project, and add something like:
[[redirects]]
from = "/*"
to = "/"
status = 200
Check : https://docs.netlify.com/configure-builds/file-based-configuration/#redirects

React Router Redirect not firing after history.push used

I am using react-router-dom and I am trying to push to the browser history using the history object from the useHistory hook. The path that I push to should trigger some logic within the router which will force a redirect to another path (which renders some content).
My issue is that the <Redirect /> does not seem to be doing anything and I'm not 100% sure I know why. I created a codepen to demonstrate the issue that I am having. (Same as code below). You can see the issue if you manually navigate the browser in the codepen to the main route e.g https://c629mk.csb.app/, you will see no content load.
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import {
Route,
Switch,
Redirect,
useHistory,
BrowserRouter,
useRouteMatch
} from "react-router-dom";
const HomeRouter = () => {
const { path } = useRouteMatch();
const someRouterSpecificLogic = true;
const initialPath = someRouterSpecificLogic ? "location" : "videos";
return (
<Switch>
<Route path={`${path}/location`} render={() => <h1>Location</h1>} />
<Route path={`${path}/videos`} render={() => <h1>Videos</h1>} />
<Redirect from={`${path}/`} to={`${path}/${initialPath}`} />
</Switch>
);
};
const AboutRouter = () => {
const { path } = useRouteMatch();
return (
<Switch>
<Route path={`${path}/history`} render={() => <h1>History</h1>} />
<Route path={`${path}/background`} render={() => <h1>Background</h1>} />
<Redirect from={`${path}/`} to={`${path}/history`} />
</Switch>
);
};
const useSomeAsyncHook = () => {
const [asyncResult, setAsyncResult] = useState(false);
useEffect(() => {
setTimeout(() => {
setAsyncResult("someValue");
}, 300);
});
return asyncResult;
};
const AppRouter = () => {
const history = useHistory();
const asycnResult = useSomeAsyncHook();
useEffect(() => {
if (asycnResult === "someValue") {
history.push("/home");
}
}, [history, asycnResult]);
return (
<>
<p>There should be other content rendering on this page:</p>
<Switch>
<Route path="/home" component={HomeRouter} />
<Route path="/about" component={AboutRouter} />
<Redirect from="/" to="/home" />
</Switch>
</>
);
};
const App = () => {
return (
<BrowserRouter>
<AppRouter />
</BrowserRouter>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
What I think is happening is this:
/ renders triggering the Redirect to /home
useEffect pushes / to the history again
Redirect logic does not work the second time around
I am wondering if anyone knows how to force the redirect to happen at 3 again?
This can be resolved by adding a loadState to the code. This way the routes / redirect logic only renders after the history.push has taken place.
I was confused because I thought that history.push would update the internal state of browserRouter and trigger a rerender of all child routes/ redirects. This is not the case. In my case history.push did not cause the Redirect component to rerender to it did not trigger another redirect and the user would just see a white screen. Adding a loadstate as shown below resolved the problem for me:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import {
Route,
Switch,
Redirect,
useHistory,
BrowserRouter,
useRouteMatch
} from "react-router-dom";
const HomeRouter = () => {
const { path } = useRouteMatch();
const someRouterSpecificLogic = true;
const initialPath = someRouterSpecificLogic ? "location" : "videos";
return (
<Switch>
<Route path={`${path}/location`} render={() => <h1>Location</h1>} />
<Route path={`${path}/videos`} render={() => <h1>Videos</h1>} />
<Redirect from={`${path}/`} to={`${path}/${initialPath}`} />
</Switch>
);
};
const AboutRouter = () => {
const { path } = useRouteMatch();
return (
<Switch>
<Route path={`${path}/history`} render={() => <h1>History</h1>} />
<Route path={`${path}/background`} render={() => <h1>Background</h1>} />
<Redirect from={`${path}/`} to={`${path}/history`} />
</Switch>
);
};
const useSomeAsyncHook = () => {
const [asyncResult, setAsyncResult] = useState(false);
const [asyncResultFetched, setAsyncResultFetched] = useState(false);
useEffect(() => {
setTimeout(() => {
setAsyncResult("someValue");
setAsyncResultFetched(true);
}, 300);
});
return { asyncResult, asyncResultFetched };
};
const AppRouter = () => {
const history = useHistory();
const { asycnResult, asyncResultFetched } = useSomeAsyncHook();
useEffect(() => {
if (asycnResult === "someValue") {
history.push("/home");
}
}, [history, asycnResult]);
if (!asyncResultFetched) {
return <h1>Loading...</h1>;
}
return (
<>
<p>There should be other content rendering on this page:</p>
<Switch>
<Route path="/home" component={HomeRouter} />
<Route path="/about" component={AboutRouter} />
<Redirect from="/" to="/home" />
</Switch>
</>
);
};
const App = () => {
return (
<BrowserRouter>
<AppRouter />
</BrowserRouter>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
Thank you to #Abdulrahman Ali and #Drew Reese for their assistance in helping me realise what my issue was in the comments of my question above

React Router V5 best way to use context variable in route

In My app I have my routes defined, as per below:
<BrowserRouter>
<Header />
<div className="App">
<Switch>
<Route exact path="/">
<Redirect to="/home" />
</Route>
<Route exact path={["/home", "/"]} component={Home} />
<Route path="/account/:id" render={(props: RouteComponentProps<any>) => <Account {...props} />} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>
What I want to know is, this can be tricky, If I wanted my route to have a prefix from my context i.e variable how would I do this, but the twist is the variable comes from an api response?
so what if i wanted the route /contextVariable/home but contextVariable is from an api response and is stored in a context value, I know how I would bring that variable into the component but how would the routes handle it i.e from not being /undefined/home as in the response would need to finish before being inserted into the route?
Any idea's?
I had once made a project that had similar requirement. In that, instead of declaring dynamic routes, I fetched a routes array from the state which was an object array with component, path, and few other parameters. By default I added the initial landing page and not found page:
const [routes, setRoutes] = React.useState([
{
component: HomeComponent,
path: '/',
},
{
component: NoMatchPage,
path: '*',
}
])
And then I had the request in a useEffect block which would update this state like so:
React.useEffect(()=>{
// call api()
const oldRoutes = routes;
const noMatchPage = oldRoutes.pop();
const newRoutes = [...oldRoutes,
responseFromApi.map(
routeItem =>
({
component: ComponentName,
path: routeItem.path
})
), noMatchPage]
setRoutes(newRoutes)
},[])
Edit 1 : Cause I'm forgetful
Sorry, I forgot the main part, here's how the Route rendering would be:
<Switch>
{
routes.map(routeItem =>
<Route path={routeItem.path} component={routeItem.component} />
)
}
</Switch>
Also if you want to avoid the extra code in useEffect, you could simply do this:
React.useEffect(()=>{
// call api()
setRoutes(responseFromApi.map(
routeItem =>
({
component: ComponentName,
path: routeItem.path
})
))
},[])
and then
<Switch>
<Route exact path={["/home", "/"]} component={Home} />
{
routes.map(routeItem =>
<Route path={routeItem.path} component={routeItem.component} />
)
}
<Route component={NotFound} />
</Switch>
Edit 2 : Cause I'm ignorant
In the case where the user enters the URL directly and Switch is unable to identify the Route and therefore loads the NotFoundPage, you could do the following:
Set a condition when you start loading your paths, inside your useEffect block:
const [loading, setLoading] = React.useState(false);
React.useEffect(() =>
{
setLoading(true);
// load paths
setLoading(false);
}, [])
While the fetch is in progress, show a Loader to the user:
return
(
<>
{
loading ?
<LoaderComponent /> :
<Switch>
// same as before
</Switch>
}
</>
)
Preferable show something for the user to read, so they don't get irritated, cause patience is a thing of the past. Hope this helps!
If you want to do this with a React Context then this is the pattern I'd suggest. Create a React Context that holds the API logic to fetch a "base path" and expose that out to consumers. Consumers will take the provided "base path" value and prepend it to all link targets and route paths.
Example:
BasePathProvider
import { createContext, useContext } from "react";
const BasePath = createContext({
basepath: ""
});
const BasePathProvider = ({ children }) => {
... logic to fetch basepath ...
return (
<BasePath.Provider value={{ basepath }}>
{children}
</BasePath.Provider>
);
};
const useBasePath = () => useContext(BasePath);
Header
const Header = () => {
const { basepath } = useBasePath();
return (
...
<Link to={`${basepath}/`}>Home</Link>
<Link to={`${basepath}/account/${/* some id value */}`}>
Account
</Link>
...
);
};
App
function App() {
return (
<div className="App">
<Header />
<BasePath.Consumer>
{({ basepath }) => (
<Switch>
<Redirect from={`${basepath}/`} exact to={`${basepath}/home`} />
<Route path={`${basepath}/home`} component={Home} />
<Route path={`${basepath}/account/:id`} component={Account} />
<Route component={NotFound} />
</Switch>
)}
</BasePath.Consumer>
</div>
);
}
index.js
import { BrowserRouter as Router } from "react-router-dom";
import BasePathProvider from "../path/to/BasePathProvider";
...
<Router>
<BasePathProvider>
<App />
</BasePathProvider>
</Router>
Note: You might also want/need to implement a "loading" state to conditionally render the BasePathProvider component's children until the basepath value has been fetched.

How to redirect in routes after async request?

I have this code in which I use react-router-dom v6 for routing
class App extends Component {
constructor(props) {
super(props);
this.state = {
accounts: [],
};
}
componentDidMount() {
getAccounts().then(r => this.setState({ // async req
accounts: r
}));
};
render() {
return (
<Routes>
<Route path="/" element={<AppLayout accounts={this.state.accounts} />}>
<Route index element={<Navigate to={`/account/1`} />}/>
<Route path="/account/:accountId" element={<TimelinePage/>}/>
<Route path="*" element={<NotFoundPage/>}/>
</Route>
</Routes>
);
}
}
I want to redirect from the home page to /accounts/${this.state.accounts[0]} but array this.state.accounts is filled after an asynchronous request before mounting the App component and it turns out that the first time I render, I get to <NotFoundPage/>. This is logical, but how can I get async data first and redirect only after that?
I made a temporary solution
<Route index element={<Navigate to={'/account/1'} />}/>`
But this does not guarantee that an element with ID 1 will always exist in the array
P.S. Array this.state.accounts contains objects that have the numeric property accountId. Inside the <TimelinePage/> component I use useParams() to get the account id and in the <AppLayout/> component I render each element of the array
Use the useNavigate hook to access the navigate function and issue an imperative redirect after the accounts have been fetched. This issue here though is that App is a class component and can't directly use React hooks.
Options are:
Convert App to a function component.
Create a custom withRouter HOC to access the hook and inject navigate as a prop.
App is simple enough that it would be trivial to convert to a function component.
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
const App = () => {
const navigate = useNavigate();
const [accounts, setAccounts] = useState([]);
useEffect(() => {
getAccounts()
.then(accounts => {
setAccounts(accounts);
navigate("/account/1", { replace: true });
});
}, []);
return (
<Routes>
<Route path="/" element={<AppLayout accounts={accounts} />}>
<Route path="/account/:accountId" element={<TimelinePage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}
If the class components are more complex it may be easier to create a custom withRouter HOC and pass in navigate as a prop.
import { useNavigate, ...other hooks... } from 'react-router-dom';
const withRouter = Component => props => {
const navigate = useNavigate();
...other hooks...
return (
<Component
{...props}
navigate={navigate}
...other hooks...
/>
);
};
App
class App extends Component {
state = {
accounts: [],
}
componentDidMount() {
const { navigate } = this.props;
getAccounts()
.then(accounts => {
this.setState({ accounts });
navigate("/account/1", { replace: true });
});
}
render() {
const { accounts } = this.state;
return (
<Routes>
<Route path="/" element={<AppLayout accounts={accounts} />}>
<Route path="/account/:accountId" element={<TimelinePage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}
}
export default withRouter(App);

how to redirect in react v6

I need to navigate back to the original requested URL after login.
For example, the user enters www.eCart.com/Cart as the user is not authenticated, it will navigate to the login page www.eCart.com/login.
Once authenticated, it should navigate back to www.eCart.com/Cart automatically
my protectedRoute.js looks like this
import React from 'react'
import { connect } from 'react-redux'
import { Navigate, Outlet, useLocation, useNavigate} from 'react-router-dom'
export const withRouter = (Component) => { //works only for react16-17 //hooks
const Wrapper = (props) => {
const location = useLocation()
const navigate = useNavigate();
return (
<Component
navigate = {navigate}
{...props}
location={location}
{...props}
/>
);
};
return Wrapper;
};
const ProtectedRoute = ({component:Component, auth,...rest}) => (
auth.isAuthenticated ? <Outlet /> : <Navigate to={`/login/${rest.location.search}`} replace />
)
const mapStateToProps = (state) => ({
auth: state.auth
})
export default connect(mapStateToProps)(withRouter(ProtectedRoute))
my app.js is like this
function App(props) {
useEffect(() => {
store.dispatch(setCurrentUser())
}, [])
const grabProductsFromStorage = () =>{
const userId = decodeUser().user.id
const cartProducts = JSON.parse(localStorage.getItem("products"))
const context = {products: cartProducts, userId}
store.dispatch(addToCart(context))
localStorage.removeItem("products")
}
if(localStorage.getItem("token") && localStorage.getItem("products")){
grabProductsFromStorage()
}
return (
<Provider store={store}>
<Router>
<Routes>
<Route exact path="/" element={<Landing/>} />
<Route exact path="/products/:id" element={<ProductDetails/>} />
<Route exact path="/" element={<ProtectedRoute/>}>
<Route exact
path="/dashboard/*"
element={<Dashboard {...props} nestedRoute={Home} />}
/>
<Route exact path="/cart" element={<Cart />} />
</Route>
<Route exact path="/register" element={<Register/>} />
<Route exact path="/login" element={<Login/>} />
</Routes>
</Router>
</Provider>
);
}
Also, I've seen somewhere to use state in Navigate with the location it but when I'm doing it it's throwing an error of Unexpected use of 'location'
You need to store that cart route first. while redirecting to login page from the cart if a user is not authenticated & redirected to the login page you need to store the cart route in your localstorage or somewhere in your state so after login you can check do we have afterlogin route then you can redirect the user to that page.
There are some approaches:
Redirect user to another page:
function Redirect() {
let navigate = useNavigate();
function handleClick() {
navigate('/home')
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
redirect to previous page
function Redirect() {
let navigate = useNavigate();
function handleClick() {
navigate(-1)
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
redirect user to the next page
function Redirect() {
let navigate = useNavigate();
function handleClick() {
navigate(1)
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);

Categories