I am following a tutorial, and i'm having an issue with react context
I have my context component that looks like this
import React from 'react'
export const firebaseAuth = React.createContext()
const AuthProvider = props => {
const { children } = props
return (
<firebaseAuth.Provider
value={{
test: 'context is working',
}}
>
{children}
</firebaseAuth.Provider>
)
}
export default AuthProvider
And I am trying to test my context inside of my Router component, unfortunately test is undefined and i'm not sure how to debug it
import React, { useContext } from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import AuthProvider, { firebaseAuth } from './database/provider/AuthProvider'
const Router = () => {
const test = useContext(firebaseAuth)
console.log(test)
return (
<BrowserRouter>
<AuthProvider>
<Switch>
<Route path="/login/" component={SignIn} />
<Route component={NotFound} />
</Switch>
</AuthProvider>
</BrowserRouter>
)
}
export default Router
I know this question has been asked a bunch of time on here, and I have looked at those threads and still cannot find a solution.
any ideas?
Thanks
In order to get a value from context, there needs to be a provider somewhere higher up the component tree. So anything that's a descendant of <AuthProvider> can call useContext(firebaseAuth) and see {test: 'context is working'}. But Router is outside of <AuthProvider>, so it will only get the default value, which in your case is undefined.
Related
In react-router v5 i created history object like this:
import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
And then passed it to the Router:
import { Router, Switch, Route, Link } from "react-router-dom";
<Router history={history}>
... my routes
</Router>
I did it for the opportunity to usage history outside of component:
// store action
logout() {
this.user = null;
history.push('/');
}
This way I moved the logic to the store and the components were kept as clean as possible. But now, in react router v6 i cant do the same. I can still navigate using useNavigate() inside my component, but i cannot create a navigate to use its into my store. Is there any alternative?
Well, it turns out you can duplicate the behavior if you implement a custom router that instantiates the history state in the same manner as RRDv6 routers.
Examine the BrowserRouter implementation for example:
export function BrowserRouter({
basename,
children,
window
}: BrowserRouterProps) {
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window });
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
Create a CustomRouter that consumes a custom history object and manages the state:
const CustomRouter = ({ history, ...props }) => {
const [state, setState] = useState({
action: history.action,
location: history.location
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
{...props}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
This effectively proxies the custom history object into the Router and manages the navigation state.
From here you swap in the CustomRouter with custom history object for the existing Router imported from react-router-dom.
export default function App() {
return (
<CustomRouter history={history}>
<div className="App">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</div>
</CustomRouter>
);
}
Fork of your codesandbox:
Update
react-router-dom#6 surfaces a history router.
HistoryRouter
<unstable_HistoryRouter> takes an instance of the history library as
prop. This allows you to use that instance in non-React contexts or as
a global variable.
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import { createBrowserHistory } from "history";
const history = createBrowserHistory({ window });
ReactDOM.render(
<HistoryRouter history={history}>
{/* The rest of your app goes here */}
</HistoryRouter>,
root
);
There is this note:
This API is currently prefixed as unstable_ because you may
unintentionally add two versions of the history library to your app,
the one you have added to your package.json and whatever version React
Router uses internally. If it is allowed by your tooling, it's
recommended to not add history as a direct dependency and instead
rely on the nested dependency from the react-router package. Once we
have a mechanism to detect mis-matched versions, this API will remove
its unstable_ prefix.
Notes on RRDv6.4+
If you are using RRDv6.4+ and not using the Data routers the good-ish news is that unstable_HistoryRouter is still being exported through at least RRDv6.7.0. You can follow along the filed issue in the repo here.
If you are using the Data routers then the new "unstable" method is to use an attached navigate function from the router object directly.
Example:
import { createBrowserRouter } from 'react-router-dom';
const router = createBrowserRouter(...);
...
router.navigate(targetPath, options);
TypeScript solution of accepted answer
history object:
import { createBrowserHistory } from "history";
const customHistory = createBrowserHistory();
export default customHistory;
BrowserRouterProps is a react-router type.
export interface BrowserRouterProps {
basename?: string;
children?: React.ReactNode;
window?: Window;
}
CustomRouter:
import { useLayoutEffect, useState } from "react";
import { BrowserRouterProps, Router } from "react-router-dom";
import { BrowserHistory } from "history";
import customHistory from "./history";
interface Props extends BrowserRouterProps {
history: BrowserHistory;
}
export const CustomRouter = ({ basename, history, children }: Props) => {
const [state, setState] = useState({
action: history.action,
location: history.location,
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
navigator={customHistory}
location={state.location}
navigationType={state.action}
children={children}
basename={basename}
/>
);
};
use CustomRouter instead BrowserRouter
ReactDOM.render(
<React.StrictMode>
<CustomRouter history={customHistory}>
<App />
</CustomRouter>
</React.StrictMode>,
document.getElementById("root")
);
Typescript solution with HistoryRouter from react-router-dom
Different from other answers, this solution uses the HistoryRouter imported from react-router-dom and using TypeScript.
1. Create a new file with your CustomRouter component. I'm placing it at "./components/CustomRouter.tsx" in this example.
import React, { FC, PropsWithChildren } from "react";
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import { createBrowserHistory } from "history";
const history = createBrowserHistory({ window });
const CustomRouter: FC<PropsWithChildren> = ({ children, ...props }) => {
return (
<HistoryRouter history={history} {...props}>
{children}
</HistoryRouter>
);
};
export const rootNavigate = (to: string) => {
history.push(to);
};
export default CustomRouter;
2. Import the CustomRouter and use it in place of the BrowserRouter
[...]
import CustomRouter from "./components/CustomRouter";
[...]
ReactDOM.render(
<CustomRouter>
[...]
</CustomRouter>,
root
);
3. Import and use "rootNavigate" anyware to navigate.
import { rootNavigate } from "../components/CustomRouter";
function doAnything() {
alert("Ok, will do");
rootNavigate("/anywhere-you-want");
}
React Router Dom 6.5
Set up your routes with createBrowserRouter
import { createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "children",
element: <Children />,
},
]
}
])
import the router and use its navigate property method to redirect with your designated path
import router from "#router/index";
router.navigate("/auth/login");
Below approach worked for me, using HistoryRouter (React Router v6):
//1.
//create some file like HistoryRouterObject.ts, which will return the history
//object from anywhere
import { createBrowserHistory } from "history";
const historyObject = createBrowserHistory({ window });
export default historyObject;
//2.
//in your index.tsx, wrap your components with HistoryRouter and pass it the
//historyObject above
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import historyObject from './somewhere in your app/HistoryRouterObject'
root.render(
<HistoryRouter history={historyObject}>
//your components and Routers ...
</HistoryRouter>
);
//3.
//now you can use the history object in any non-react component function, like IsUserAuthenticated.ts,
import historyObject from './somewhere in your app/HistoryRouterObject'
function xyz(){
historyObject.replace('/');
//or
historyObject.push("/");
}
Install history:
yarn add history
Create a history.ts (or js) file:
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
Replace your BrowserRouter to a HistoryRouter from unstable_HistoryRouter, using your history as argument:
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import {history} from './history';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<HistoryRouter history={history}>
...
</HistoryRouter>
</React.StrictMode>
);
Now you can navigate outside React Components (ts or js files):
import { history } from './history';
...
history.push('/');
React Router 6 has a redirect component method you can use here:
https://reactrouter.com/en/main/fetch/redirect
As an exercise, I'm making a react app (still learning React) that implements a login system with firebase. Of course, to implement such a feature, react router is necessary and I have successfully implemented it. However, once the user logs in he should be able to see a sidebar alongside other content that is changed dynamically. I now need to again use react router to change those pages when a user clicks on a specific item in the sidebar without having to render the sidebar with each component. I have read the docs for nesting routers but just cant get it to work. Any help is greatly appreciated.
Here's the code:
App.js:
import "./App.css";
import LoginForm from "./components/LoginForm";
import { AuthProvider } from "./contexts/AuthContext";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import PrivateRoute from "./components/PrivateRoute";
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Switch>
<PrivateRoute exact path="/" component={Dashboard} />
<Route path="/login" component={LoginForm} />
</Switch>
</AuthProvider>
</Router>
</div>
);
}
export default App;
Dashboard.js:
import React from "react";
import { useAuth } from "../contexts/AuthContext";
import { useHistory } from "react-router";
import Sidebar from "./Sidebar/Sidebar";
import { useRouteMatch } from "react-router";
const Dashboard = () => {
const { currentUser, logout } = useAuth();
const history = useHistory();
let { path, url } = useRouteMatch();
const handleLogout = async () => {
try {
await logout();
history.push("/login");
} catch (error) {
console.log(error);
}
};
if (!currentUser) return null;
return (
<div>
<Sidebar logout={handleLogout} />
</div>
);
};
export default Dashboard;
PS. I'm quite new to react and any tip/critique is welcome
You can always conditionally render the sidebar.
function Sidebar() {
const { currentUser } = useAuth()
if (!currentUser) return null
// ...
}
Within your App component, just render the Sidebar component outside of the Switch:
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Sidebar />
<Routes />
</AuthProvider>
</Router>
</div>
);
}
function Routes() {
const { currentUser } = useAuth()
return (
<Switch>
{currentUser && <PrivateRoutes />}
<PublicRoutes />
</Switch>
)
}
Basically all you need to do is render the sidebar on all routes. If you need to render custom Sidebar content based off of routes, you can add another Switch within Sidebar. You can add as many Switch components as you want as long as they are within your Router.
Even though i understand what your trying to do, i don't think you should mind put the sidebar inside the component.
React is powerfull enough to cache a lots of stuffs and disable unnecessary renders. I think the path you should go its figure out how to use wisely useCallback useMemo, memo and make all the tricks to prevent re-renders inside the sidebar components. This way you can reuse the sidebarcomponent, or any component, without to think about location.
Whenever I console log props.match.params, I get an error:
TypeError: Cannot read property 'params' of undefined at App. I'm not sure this is relevant, but even if I console.log(props) I get four empty arrays.
Here is all the relevant code:
Home.js
import React from "react";
import App from "./App";
import { render } from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
const Home = () => {
return (
<div>
<Router>
<Route exact path="/">
<App />
</Route>
<Route path="/:roomCode" component={App} />
</Router>
</div>
);
};
export default Home;
App.js (only the relevant part)
const App = (props) => {
console.log(props.match.params);
};
export default App;
const appDiv = document.getElementById("app");
render(<App />, appDiv);
I have been trying to figure this out for the past two days. Nothing works. Also, history.push also doesn't work, returns a very similar error. I have a feeling react-router-dom is broken in my project.
Help is much appreciated.
Edit:
Here is the codesandbox: https://codesandbox.io/s/reverent-microservice-iosu2?file=/src/App.js
Your Home Component is the root of all your components so it needs to be pass to render function not your App which is a descendent of Home.
after that change you need to change this line in your Home Component:
<Route exact path="/" render={(props) => <App {...props} />} />
import React from "react";
import App from "./App";
import { render } from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
const Home = () => {
return (
<div>
<Router>
<Route exact path="/" render={(props) => <App {...props} />} />
<Route path="/:roomCode" component={App} />
</Router>
</div>
);
};
export default Home;
here is how to render it:
const appDiv = document.getElementById("app");
render(<Home />, appDiv);
now you can get the props
const App = (props) => {
console.log(props.match.params);
};
export default App;
You could try using the React hooks provided by the React Router framework. There are several different hooks used to interact with the router.
const App = () => {
const { roomCode } = useParams()
console.log(params);
};
More info here
I have a simple app that's using redux and react-router. I wrapped my app component in a provider tag so that it has access to the store. I connected (in App.js) the mapStateToProps and mapStateToDispatch in the App.js. I'm not sure how to pass the function I defined in App.js to a child component since I'm using route. I tried doing the render trick but it didn't work. If I can pass it to that CelebrityPage component, how would I receive it in the file? Any help would be appreciated.
This is my App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import './App.css';
import Clarifai from 'clarifai'
// import Particles from 'react-particles-js';
// import particlesOptions from './particleOptions'
import { Signin } from './components/signin/Signin';
import Register from './components/register/Register';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import { setSearchField } from './context/Actions'
import FacePage from './Containers/FacePage';
import CelebrityPage from './Containers/CelebrityPage';
import ControllerPage from './Containers/ControllerPage';
const mapStateToProps = state => {
return {
input: state.input
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSearchChange: (event) => dispatch(setSearchField(event.target.value))
}
}
...
render() {
return (<Router>
<Switch >
<Route path='/celebrity' exact render={props => <CelebrityPage{...props} handleSearchChange={this.handleSearchChange} />} />
<Route path='/' exact component={Register} />
<Route path='/signin' exact component={Signin} />
<Route path='/contoller' exact component={ControllerPage} />
<Route path='/face-detection' exact component={FacePage} />
</Switch>
</Router>)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
If you are going to pass store actions and states into the child components, it means you are refusing to use the advantages of redux. The best approach should be connect any of your component that needs to access to the actions or state to the store. Doing connection at the root component level and passing the props to the child components is not a good solution.
I think what robert is saying is what you'd probably want to do. Don't try to pass your props inside of your <Route>. Instead do your connect mapDispatchToProps and your mapStateToProps inside your CelebrityPage Component.
Once you do the wrapping inside of the Celebrity Page component you should have access to the props and functions that you have defined.
...
// keep all the previous imports from your App.Js
render() {
// have your router like this
return (<Router>
<Switch >
<Route path='/celebrity' exact component ={CelebrityPage} />
<Route path='/' exact component={Register} />
<Route path='/signin' exact component={Signin} />
<Route path='/contoller' exact component={ControllerPage} />
<Route path='/face-detection' exact component={FacePage} />
</Switch>
</Router>)
}
}
export default App
Example Celebrity page
import React from 'react'
import { connect } from 'react-redux'
class CelebrityPage extends React.Component {
// put your mapStateToProps and mapDispatch function heres instead of app.js
mapStateToProps() {
}
mapDispatchToProps {
// bind your handlesearch function to props here
}
render() {
return (
<div>
<input />
<button onClick={this.props.handleSearchChange}/>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CelebrityPage)
I am developing an application in which I check if the user is not loggedIn. I have to display the login form, else dispatch an action that would change the route and load other component. Here is my code:
render() {
if (isLoggedIn) {
// dispatch an action to change the route
}
// return login component
<Login />
}
How can I achieve this as I cannot change states inside the render function.
Considering you are using react-router v4
Use your component with withRouter and use history.push from props to change the route. You need to make use of withRouter only when your component is not receiving the Router props, this may happen in cases when your component is a nested child of a component rendered by the Router and you haven't passed the Router props to it or when the component is not linked to the Router at all and is rendered as a separate component from the Routes.
import {withRouter} from 'react-router';
class App extends React.Component {
...
componenDidMount() {
// get isLoggedIn from localStorage or API call
if (isLoggedIn) {
// dispatch an action to change the route
this.props.history.push('/home');
}
}
render() {
// return login component
return <Login />
}
}
export default withRouter(App);
Important Note
If you are using withRouter to prevent updates from being blocked by
shouldComponentUpdate, it is important that withRouter wraps the
component that implements shouldComponentUpdate. For example, when
using Redux:
// This gets around shouldComponentUpdate
withRouter(connect(...)(MyComponent))
// This does not
connect(...)(withRouter(MyComponent))
or you could use Redirect
import {withRouter} from 'react-router';
class App extends React.Component {
...
render() {
if(isLoggedIn) {
return <Redirect to="/home"/>
}
// return login component
return <Login />
}
}
With react-router v2 or react-router v3, you can make use of context to dynamically change the route like
class App extends React.Component {
...
render() {
if (isLoggedIn) {
// dispatch an action to change the route
this.context.router.push('/home');
}
// return login component
return <Login />
}
}
App.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default App;
or use
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
In react-router version 4:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
const Example = () => (
if (isLoggedIn) {
<OtherComponent />
} else {
<Router>
<Redirect push to="/login" />
<Route path="/login" component={Login}/>
</Router>
}
)
const Login = () => (
<h1>Form Components</h1>
...
)
export default Example;
Another alternative is to handle this using Thunk-style asynchronous actions (which are safe/allowed to have side-effects).
If you use Thunk, you can inject the same history object into both your <Router> component and Thunk actions using thunk.withExtraArgument, like this:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
import { createBrowserHistory } from "history"
import { applyMiddleware, createStore } from "redux"
import thunk from "redux-thunk"
const history = createBrowserHistory()
const middlewares = applyMiddleware(thunk.withExtraArgument({history}))
const store = createStore(appReducer, middlewares)
render(
<Provider store={store}
<Router history={history}>
<Route path="*" component={CatchAll} />
</Router
</Provider>,
appDiv)
Then in your action-creators, you will have a history instance that is safe to use with ReactRouter, so you can just trigger a regular Redux event if you're not logged in:
// meanwhile... in action-creators.js
export const notLoggedIn = () => {
return (dispatch, getState, {history}) => {
history.push(`/login`)
}
}
Another advantage of this is that the url is easier to handle, now, so we can put redirect info on the query string, etc.
You can try still doing this check in your Render methods, but if it causes problems, you might consider doing it in componentDidMount, or elsewhere in the lifecycle (although also I understand the desire to stick with Stateless Functional Compeonents!)
You can still use Redux and mapDispatchToProps to inject the action creator into your comptonent, so your component is still only loosely connected to Redux.
This is my handle loggedIn. react-router v4
PrivateRoute is allow enter path if user is loggedIn and save the token to localStorge
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => (localStorage.token) ? <Component {...props} /> : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
}
Define all paths in your app in here
export default (
<main>
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route exact path="/signup" component={SignUp} />
<PrivateRoute path="/" component={Home} />
</Switch>
</main>
);
Those who are facing issues in implementing this on react-router v4. Here is a working solution for navigating through the react app programmatically.
history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
App.js OR Route.jsx. Pass history as a prop to your Router.
import { Router, Route } from 'react-router-dom'
import history from './history'
...
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
You can use push() to navigate.
import history from './history'
...
render() {
if (isLoggedIn) {
history.push('/test') // this should change the url and re-render Test component
}
// return login component
<Login />
}
All thanks to this comment: https://github.com/ReactTraining/react-router/issues/3498#issuecomment-301057248
render(){
return (
<div>
{ this.props.redirect ? <Redirect to="/" /> :'' }
<div>
add here component codes
</div>
</div>
);
}
I would suggest you to use connected-react-router https://github.com/supasate/connected-react-router
which helps to perform navigation even from reducers/actions if you want.
it is well documented and easy to configure
I was able to use history within stateless functional component, using withRouter following way (needed to ignore typescript warning):
import { withRouter } from 'react-router-dom';
...
type Props = { myProp: boolean };
// #ts-ignore
export const MyComponent: FC<Props> = withRouter(({ myProp, history }) => {
...
})
import { useNavigate } from "react-router-dom"; //with v6
export default function Component() {
const navigate = useNavigate();
navigate.push('/path');
}
I had this issue and just solved it with the new useNavigate hook in version 6 of react-router-dom