I want to have a AI Voice assistant Alan AI to be integrated in my react app
So basically want to redirect pages using voice command.
But the problem is that, I want to use useHistory hook's push method to change pages, but whenever I access the variable it is undefined, I do not want to use window.location.href as it refreshes the app every time,
Can anyone suggest me where I am wrong usnig this hook/ or any other alternatives
import { useCallback, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { useCartContext } from "../context/cart_context";
import { useThemeContext } from "../context/theme_context";
import alanBtn from "#alan-ai/alan-sdk-web";
const COMMANDS = {
OPEN_CART: "open-cart",
TOGGLE_THEME: "toggle-theme",
};
const useAlan = () => {
let history = useHistory();
const { theme, toggleTheme } = useThemeContext();
const { cart } = useCartContext();
const [alanInstance, setAlanInstance] = useState(null);
const openCart = useCallback(() => {
if (cart.length < 1) {
alanInstance.playText("cart is empty");
return;
}
alanInstance.playText("opening cart");
history.push("/cart");
// window.location.href = "/cart";
}, [alanInstance]);
const changeTheme = useCallback(() => {
alanInstance.playText("changing theme");
toggleTheme();
});
useEffect(() => {
window.addEventListener(COMMANDS.OPEN_CART, openCart);
window.addEventListener(COMMANDS.TOGGLE_THEME, changeTheme);
return () => {
window.removeEventListener(COMMANDS.OPEN_CART, openCart);
window.removeEventListener(COMMANDS.TOGGLE_THEME, changeTheme);
};
}, [openCart, changeTheme]);
useEffect(() => {
// history.push("/cart");
// this also gives same error
if (alanInstance != null) return;
setAlanInstance(
alanBtn({
key: process.env.REACT_APP_ALAN_KEY,
onCommand: ({ command }) => {
window.dispatchEvent(new CustomEvent(command));
},
})
);
}, []);
return null;
};
export default useAlan;
Every thing work's fine, voice is detected and the openCart function is ran,
it's just that history is undefined
Error:
TypeError: Cannot read property 'push' of undefined
(anonymous function)
C:/Users/Sachin Verma/Desktop/Web Dev/React-E-Commerce-v2/src/hooks/useAlan.js:28
25 | }
26 | alanInstance.playText("opening cart");
27 |
> 28 | history.push("/cart");
| ^ 29 | // window.location.href = "/cart";
30 | }, [alanInstance]);
31 |
App.js code:
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { useThemeContext } from "./context/theme_context";
import useAlan from "./hooks/useAlan";
import { Navbar, Sidebar, Footer } from "./components";
import {
Home,
SingleProduct,
Cart,
Checkout,
Error,
About,
Products,
PrivateRoute,
AuthWrapper,
Scan,
History,
ItemList,
} from "./pages";
function App() {
const { theme } = useThemeContext();
useAlan();
useEffect(() => {
if (theme === "dark-theme") {
// set dark mode theme
document.documentElement.className = "dark-theme";
} else {
// remove dark mode
document.documentElement.className = "light-theme";
}
}, [theme]);
return (
<AuthWrapper>
<Router>
<Navbar />
<Sidebar />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/about">
<About />
</Route>
<Route exact path="/cart">
<Cart />
</Route>
<Route exact path="/products">
<Products />
</Route>
<PrivateRoute exact path="/history">
<History />
</PrivateRoute>
<PrivateRoute exact path="/scan">
<Scan />
<ItemList />
</PrivateRoute>
<Route exact path="/products/:id" children={<SingleProduct />} />
<PrivateRoute exact path="/checkout">
<Checkout />
</PrivateRoute>
<Route path="*">
<Error />
</Route>
</Switch>
<Footer />
</Router>
</AuthWrapper>
);
}
export default App;
This happens when you are not properly nesting your application inside a valid Router which provides context for the history object. Make sure your top-level code is put inside a proper Router object context:
import { BrowserRouter } from "react-router-dom";
function App() {
return (
<BrowserRouter>
...(components that use react-router-hooks)
</BrowserRouter>
);
}
Same goes for React-Native: NativeRouter.
useHistory has changed in v6, useHistory is now useNavigate and we can use it as follows:
instead of:
const history = useHistory()
history.push('/')
we now use:
const navigate = useNavigate()
naviaget('/')
If you work in App.js, you will need to move your
<Router> </Router>
of there to your index.js
and wrap it like that:
<Router>
<App />
</Router>
and it should work like a charm.
Related
Following this tutorial series to try to build a simple React, Electron, and firebase app.
This project mirrors this demo project. I'm getting a lot of compile errors, mostly outdated content and dependencies, but managed to fix most of them up. The main thing I'm struggling with now is upgrading some code from react-router v5 to v6, specifically in app.js
import React, { useState, useEffect } from "react";
import { Router, Routes, Route, Navigate } from "react-router-dom";
import AddMoviePage from "../pages/add-movie-page";
import EditMoviePage from "../pages/edit-movie-page";
import AccountPage from "../pages/account-page";
import MoviesPage from "../pages/movies-page";
import NotFoundPage from "../pages/not-found-page";
import { auth } from "../data/firebase";
import Nav from "./nav";
import { createMemoryHistory } from "history";
function AuthenticatedRoute(props) {
const { isAuthenticated, children, ...routeProps } = props;
return <Route {...routeProps}>{isAuthenticated ? children : <Navigate to="/account" />}</Route>;
}
function App() {
const [user, setUser] = useState(null);
const isAuthenticated = user !== null;
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((currentUser) => {
setUser(currentUser);
});
return unsubscribe;
}, []);
const history = createMemoryHistory();
console.log(history);
return (
<Router history={history}>
<Nav user={user} />
<Routes>
<Route path="/account">
<AccountPage user={user} />
</Route>
<AuthenticatedRoute path="/" exact isAuthenticated={isAuthenticated}>
<MoviesPage user={user} />
</AuthenticatedRoute>
<AuthenticatedRoute path="/add" isAuthenticated={isAuthenticated}>
<AddMoviePage user={user} />
</AuthenticatedRoute>
<AuthenticatedRoute path="/edit/:id" isAuthenticated={isAuthenticated}>
<EditMoviePage user={user} />
</AuthenticatedRoute>
<Route path="*">
<NotFoundPage />
</Route>
</Routes>
</Router>
);
}
export default App;
I'm getting the following error and can't really figure out what's going on:
Uncaught TypeError: Cannot read properties of undefined (reading 'pathname')
The above error occurred in the <Router> component.
Issues
The main issue here is that you are importing and using the low-level Router component instead of one of the high-level routers (i.e. BrowserRouter, MemoryRouter, HashRouter, etc). The Router component has a couple required props and history isn't one of them.
Router Interface:
declare function Router(
props: RouterProps
): React.ReactElement | null;
interface RouterProps {
basename?: string;
children?: React.ReactNode;
location: Partial<Location> | string; // <-- required!
navigationType?: NavigationType;
navigator: Navigator; // <-- required!
static?: boolean;
}
The high-level routers all instantiate/manage a history reference internally and pass the required props and render the base Router.
Additional issues found in the code:
Another issue is that in react-router-dom#6 custom route components are no longer valid. Only Route components can be rendered by the Routes component. You'll instead convert your older v5 custom route components, a.k.a. AuthenticatedRoute, either into Wrapper components that render the children prop, or as the preferred method a Layout Route.
A final related issue is that Route components and only be rendered by the Routes component or other Route components in the case of building nested routes. In other words, the only valid children components of a Route component is another Route component. The routed content you want to be rendered on a route is passed to the Route component's element prop.
Solution
Convert AuthenticatedRoute to a layout route.
import { Navigate, Outlet } from 'react-router-dom';
function AuthenticatedRoute({ isAuthenticated }) {
if (isAuthenticated === undefined) {
// Don't render the protected content or redirect until we confirm
// authentication status.
return null; // or loading indicator/spinner/etc
}
return isAuthenticated ? <Outlet /> : <Navigate to="/account" replace />;
}
It seems you are wanting to really use a MemoryRouter since you are instantiating your own MemoryHistory object. Import and render the MemoryRouter directly. Move the route "children" onto their respective route's element prop.
Example:
...
import {
MemoryRouter as Router, // <-- import high-level router
Routes,
Route,
Navigate,
Outlet
} from "react-router-dom";
...
function AuthenticatedRoute({ isAuthenticated }) {
if (isAuthenticated === undefined) {
return null; // or loading indicator/spinner/etc
}
return isAuthenticated ? <Outlet /> : <Navigate to="/account" replace />;
}
function App() {
const [user, setUser] = useState(); // <-- initially not auth'd or unauth'd
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((currentUser) => {
setUser(currentUser); // <-- sets to user object or null
});
return unsubscribe;
}, []);
return (
<Router> // <-- Now really a MemoryRouter
<Nav user={user} />
<Routes>
<Route path="/account" element={<AccountPage user={user} />} />
<Route element={<AuthenticatedRoute isAuthenticated={user} />}>
<Route path="/" element={<MoviesPage user={user} />} />
<Route path="/add" element={<AddMoviePage user={user} />} />
<Route path="/edit/:id" element={<EditMoviePage user={user} />} />
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Router>
);
}
export default App;
I created a component in my project that consists of a simple button that returns to the previous page, using useNavigate hook.
As it is written in the documentation, just passing -1 as a parameter to the hook would be enough to go back one page. But nothing happens.
The component code:
import { useNavigate } from 'react-router-dom'
import './go-back.styles.scss'
const GoBack = () => {
const navigate = useNavigate()
const handleClick = () => {
navigate(-1)
}
return (
<button
className='go-back'
onClick={handleClick}
>
go back
</button>
)
}
export default GoBack
The app.js code:
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
import Header from '../components/header/header.component'
import Footer from '../components/footer/footer.component'
import './App.scss'
const App = () => {
const HomePage = lazy(() => import('../pages/home/home.page'))
const SearchPage = lazy(() => import('../pages/search/search.page'))
const MostraPage = lazy(() => import('../pages/mostra/mostra.page'))
const AuthPage = lazy(() => import('../pages/auth/auth.page'))
const AccountPage = lazy(() => import('../pages/account/account.page'))
const PrenotaPage = lazy(()=> import('../pages/prenota/prenota.page'))
const SectionPage = lazy(() => import('../pages/section/section.page'))
return (
<div className='app'>
<Header />
<Suspense fallback={<span>Loading...</span>}>
<Routes>
<Route exact path='/' element={<HomePage />} />
<Route exact path='/auth:p' element={<AuthPage />} />
<Route exact path='/search' element={<SearchPage />} />
<Route exact path='/search:id' element={<SectionPage />} />
<Route exact path='/mostra' element={<MostraPage />} />
<Route exact path='/prenota' element={<PrenotaPage/>} />
<Route exact path='/profile' element={<AccountPage />} />
<Route exact path='*' element={<span>Page not found</span>} />
</Routes>
</Suspense>
<Footer />
</div>
)
}
export default App
The index.js code:
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import store, { persistor } from './redux/store/store'
import App from './app/App'
import reportWebVitals from './reportWebVitals'
import { PersistGate } from 'redux-persist/integration/react'
import { BrowserRouter } from 'react-router-dom'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
<BrowserRouter>
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>
</BrowserRouter>
)
reportWebVitals()
I thank in advance anyone who tries to help.
In the project, when rendering to another page I used the useNavigate hook and passed { replace: true } as the second parameter.
However, in this way the navigation will replace the current entry in the history stack instead of adding a new one by not then making the GoBack component work properly.
So it was enough to remove { replace: true } from the calls to useNavigate and now it works.
I have made an authentication page where I can register / log in / log out.. when the user logs in, the ./ homepage is a privateroute
I am trying to make a message page, and later on additional pages beyond the home page, however when I try to navigate to the chat page and type into the url ./chat page .. it bounces back to the homepage ./
does anyone know why this is happening?
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Profile from "./Profile";
import Register from "./register";
import VerifyEmail from "./VerifyEmail";
import Login from "./Login";
import Chat from "./Chat";
import { useState, useEffect } from "react";
import { AuthProvider } from "./AuthContext";
import { auth } from "./firebase";
import { onAuthStateChanged } from "firebase/auth";
import PrivateRoute from "./PrivateRoute";
import { Navigate } from "react-router-dom";
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [timeActive, setTimeActive] = useState(false);
useEffect(() => {
onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
});
}, []);
return (
<Router>
<AuthProvider value={{ currentUser, timeActive, setTimeActive }}>
<Routes>
<Route
exact
path="/"
element={
<PrivateRoute>
<Profile />
</PrivateRoute>
}
/>
<Route
exact
path="/chat"
element={
<PrivateRoute>
<Chat />
</PrivateRoute>
}
/>
<Route
path="/login"
element={
!currentUser?.emailVerified ? (
<Login />
) : (
<Navigate to="/" replace />
)
}
/>
<Route
path="/register"
element={
!currentUser?.emailVerified ? (
<Register />
) : (
<Navigate to="/" replace />
)
}
/>
<Route path="/verify-email" element={<VerifyEmail />} />
</Routes>
</AuthProvider>
</Router>
);
}
export default App;
//privateroute.js
import { Navigate } from "react-router-dom";
import { useAuthValue } from "./AuthContext";
export default function PrivateRoute({ children }) {
const { currentUser } = useAuthValue();
if (!currentUser?.emailVerified) {
return <Navigate to="/login" replace />;
}
return children;
}
Given is an application with 3 pages:
"Mainpage"
"PrivatPage"
"UserManagementPage"
After successful login the user is redirected to a "PrivatPage". On the "PrivatPage" the user has the possibility to return to the "MainPage" or to go to the "UserManagementPage". The part with the redirecting from the "MainPage" to the "PrivatPage" works.
The code looks like this:
import PublicPage from "./components/PublicPage";
import PrivatePage from "./components/PrivatePage";
import UserManagementPage from "./components/UserManagementPage";
import React, { useState, Component } from "react";
import { connect } from "react-redux";
const mapStateToProps = state => {
return state;
};
class App extends Component {
render() {
const accessToken = this.props.accessToken;
console.log(accessToken);
let workspace;
if (accessToken) {
workspace = <PrivatePage />;
} else {
workspace = <PublicPage />;
}
return <div className="App">{workspace}</div>;
}
}
export default connect(mapStateToProps)(App);
But how do I use the conditionals to get to the "UserManagementPage" ?
if you consider functional components, you could use BrowserRouter as follows with react-router-dom.
If you need to handle authentication, you can f.e. build a custom <PrivateRoute /> component and use this on your protected routes instead of <Route />. I always keep these routes in a separate file and import them in App.js.
Here for demo purposes routes in App.js:
import { BrowserRouter as Router } from "react-router-dom";
// import your page components
// and add everything else you want to add to your component
const App = () => {
return (
<>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/private" element={<PrivatePage />} />
<Route path="/public" element={<PublicPage />} />
<Route path="/user" element={<UserManagementPage />} />
</Routes>
</Router>
</>
);
};
export default App;
Adding on to private blocks answer you would then in your components use the
<Redirect to='/your-route' />
You would then create a boolean state variable and once it return true you could redirect immediatley like this (where you are rendering jsx):
render() {
{booleanState && <Redirect to='/your-route' />}
}
Protected Routes.js:
In protected routes you can see I'm using directly false in if statement but I'm still able to see that page why?
import React from 'react';
import { Route } from 'react-router-dom';
// import Auth from './User/Auth';
import Error401 from './Error/401';
// create a component for protected route
console.log('Routes.js');
export const ProtectedRoute = ({ element: Element, ...rest }) => {
console.log("Function Called")
return (
<Route {...rest} render={props => {
if(false){
return <Element {...props} />
}else{
return <Error401 />
}
}
} />
)
}
App.js:
This is app.js where I'm using protected routes component
import './App.css';
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Layout } from 'antd';
import { MoneyCollectOutlined } from '#ant-design/icons';
import Login from './Components/User/Login';
import Signup from './Components/User/Signup';
import {ProtectedRoute} from './Components/Routes';
import Error404 from './Components/Error/404';
function App() {
return (
<BrowserRouter>
<Layout style={{minHeight:"100vh"}}>
<Layout.Header>
<h1 style={{color:"white"}} align="center"> <MoneyCollectOutlined/>MoneyG</h1>
</Layout.Header>
<Layout.Content style={{minHeight:"100%"}}>
<Routes>
<ProtectedRoute exact path="/register" element={<Signup/>} />
<ProtectedRoute exact path="/login" element={<Login/>} />
<Route path="*" element={<Error404/>} />
</Routes>
</Layout.Content>
</Layout>
</BrowserRouter>
);
}
export default App;
First, <Routes> elements should only have <Route> elements as children. You should move your protection logic down a layer.
Secondly, the render prop doesn't exist anymore in V6. It was replaced in favor of element. See doc.
Here is how you might tackle it:
<Routes>
<Route exact path="/register" element={(
<ProtectedRoute>
<Signup/>
</ProtectedRoute>
)} />
<Route exact path="/login" element={(
<ProtectedRoute>
<Login/>
</ProtectedRoute>
)} />
<Route path="*" element={<Error404/>} />
</Routes>
And:
const ProtectedRoute = () => {
if (condition) { return <Error401 />; } // You might as well use Navigate here
return children;
};
you can use createContext & useContext
//store/AuthApi.jsx
import { createContext } from "react";
const AuthApi = createContext();
export default AuthApi;
Then define the context app.jsx
import React, from 'react'
import { AllRoutes } from 'routes/Routes';
import { BrowserRouter as Router } from "react-router-dom";
import AuthApi from 'store/AuthApi';
const App = () => {
const [user, setUser] = useState(false);
useEffect(() => {
// you can get user from localStorage or Cookie(js-cookie npm)
//then you can change user state true or false
}, [])
return (
<>
<AuthApi.Provider value={{ user, setUser }}>
<Router>
<AllRoutes />
</Router>
</AuthApi.Provider>
<Toast />
</>
)
}
export default App
then see AllRoutes
//routes/Routes
import React, { useContext } from "react";
import { Routes, Route } from "react-router-dom";
import { SignIn, SignUp, Dashboard } from "pages";
import AuthApi from "store/AuthApi";
export const ProtectedRouting = () => {
return (
<Routes >
<Route path='/' exact element={<Dashboard />} />
// add more protected routes
</Routes>
)
}
export const AuthRouting = () => {
return (
<Routes >
<Route exact={true} path='/sign-in' element={<SignIn />} />
<Route exact={true} path='/sign-up' element={<SignUp />} />
</Routes>
)
}
export const AllRoutes = ()=> {
const context = useContext(AuthApi);
console.log(context.user)
return (
context.user ?
<ProtectedRouting />
: <AuthRouting />
)
}
pages/SignIn.jsx
import React,{ useContext } from 'react';
import AuthApi from "store/AuthApi";
const SignIn = () => {
const context = useContext(AuthApi);
const signInSubmit =(e)=> {
e.preventDefault();
//post request to signin
// if login is successfull then save user or token in cookie or localStorage or something
context?.setUser(true);
//...
}
return (
//signin code here
<form onSubmit={signInSubmit}>
///input here
</form>
)
}
export default SignIn