I'm using MobX to render some orders with React and React-router. When I'm using Link the URL changes, but the view doesn't get rendered. When I refresh the page, the view is as it should be. I searched for a while and found here that MobX observer blocks rerendering because it uses shouldComponentUpdate. I tried using the location method, because that prop changes and should trigger a re-render, but that doesn't seem to help.
I also tried to use withRouter, but I didn't manage to implement that as a decorator.
Update
In this thread it's said that the decorators are breaking react-router? Using withRouter fixes it for them, but still I do not know how I should implement this into my code...
index.js
ReactDOM.render((
<Provider {...stores}>
<BrowserRouter>
<App location={location} />
</BrowserRouter>
</Provider>
),
document.getElementById('root')
);
App.js
import React from 'react';
import {Switch, Route, withRouter} from 'react-router-dom';
import {Order} from './components';
import {AllOrders} from './containers';
import {inject, observer} from 'mobx-react';
import {isEmpty} from 'lodash';
const App = ({orders, location}) => {
console.log(location);
return(
<Switch>
{
!isEmpty(orders) && <Route exact path="/orders" render={(props) => (<AllOrders {...props} orders={orders} />)} />
}
{
!isEmpty(orders) && <Route path="/orders/:id" render={(props) => (<Order {...props} orders={orders} />)} />
}
</Switch>
)
}
export default inject(
({orderStore}) => ({
orders: orderStore.orders
})
)(
observer(App)
);
AllOrders.js
class AllOrders extends Component{
render(){
const {orders} = this.props;
return(
<div>
{
!isEmpty(orders) &&
orders.map((props, key) => {
return(
<Link to={`/orders/${props.id}`} key={key}>{props.id}<br /></Link>
)
})
}
</div>
)
}
}
export default AllOrders;
Inside my BrowserRouter I added the component App, instead it should've been a Route which is passed the component App. This made the routing work again. The solution was changing my index.js to
ReactDOM.render((
<Provider {...stores}>
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
</Provider>
),
document.getElementById('root')
);
Related
im using react router v6 and i every time i use initializing for authentication in my main file it shows this error. i cant find a solution in the internet for it. i want to render some routes only when there is a user but now it doesnt render anything.
AuthNavigator
import React, { useState, useEffect } from 'react';
import app from './firebase';
import { Router, Routes, Route } from 'react-router-dom';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
import StaticStack from './stacks/StaticStack';
function AuthNavigator() {
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState(() => app.auth().currentUser);
useEffect(() => {
const unsubscribe = app.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user);
} else {
setUser(null);
}
if (initializing) {
setInitializing(false);
}
});
// cleanup subscription
return unsubscribe;
}, []);
if (initializing) return 'Loading....';
return (
<Router>
<Routes>
<Route path="*" element={<StaticStack />} />
<Route path="auth/*" element={<AuthStack user={user} />} />
<Route path="app/*" element={<AppStack user={user} />} />
</Routes>
</Router>
);
}
export default AuthNavigator;
App.js
import React from 'react';
import './App.css';
import AuthNavigator from './AuthNavigator';
import { Router } from 'react-router-dom';
function App() {
return (
<Router>
<AuthNavigator />
</Router>
);
}
export default App;
I had the same issue. My issue is because of the following reason.
I had a Header component which consists of NavLink which is a router component. I placed this Header component inside the App component. My App component was like this:
function App() {
return(
<Header/>
<Router>
<Routes>
<Route path="/" element={<Homepage/>}/>
<Route path="/shop" element={<Shop/>}/>
<Route path="/signin" element={<Signin/>}/>
</Routes>
</Router>
)
}
In the above App component, I have placed Header component outside of Router. Since in the Header component I have used NavLink which is a Router component caused this error. Then I moved Header component into the Router component then it worked fine. Finally my code looked like this:
function App() {
return(
<Router>
<Header/>
<Routes>
<Route path="/" element={<Homepage/>}/>
<Route path="/shop" element={<Shop/>}/>
<Route path="/signin" element={<Signin/>}/>
</Routes>
</Router>
)
}
Already wrapped in Router?
if your component is already wrapped in a Router. Make sure you are importing useLocation from react-router-dom instead of react-router. this worked for me.
Make sure that your App component in index.js is wrapped with BrowserRouter like this
const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
In case you run into this problem when running a test, don't forget to wrap your import of App in Router as well. My crude example is below.
import { render, screen } from '#testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { App } from '../App';
test('renders Box', () => {
render(
<BrowserRouter>
<App />
</BrowserRouter>
);
const boxElement = screen.getByLabelText('box-outline');
expect(boxElement).toBeInTheDocument();
});
I had the same error coming up from inside a test. The component I was testing contained a NavLink component, and I wasn't rendering a Router in the test.
This error disappeared after wrapping my component in question with BrowserRouter.
I had this problem when using {useLocation} from 'react-router-dom'
function App() {
const { pathname, hash, key } = useLocation();
//function using pathname hash and key
return(
<Router>
<Header/>
<Routes>
<Route path="/" element={<Homepage/>}/>
<Route path="/shop" element={<Shop/>}/>
<Route path="/signin" element={<Signin/>}/>
</Routes>
</Router>
)
}
throws the same error even with the in the correct place
I fixed it by explicitly wrapping the useLocation() within the router
function App() {
return(
<Router>
<Header/>
<Inner/>
</Router>
)
}
function Inner() {
const { pathname, hash, key } = useLocation();
//function using pathname hash and key
return(
<Routes>
<Route path="/" element={<Homepage/>}/>
<Route path="/shop" element={<Shop/>}/>
<Route path="/signin" element={<Signin/>}/>
</Routes>
)
}
I had this error because Vite was bundling two copies of the same version of react-router-dom... check the outputted bundle for * React Router DOM and see how many copies there are.
If that's the case, the solution will differ. In my scenario I think it's because I was referencing other local npm packages using file:... once I switched to a npm workspace that fixed it.
So i fixed like this structure
index.js
import { BrowserRouter } from 'react-router-dom'
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
App.js
import { Route, Routes, Navigate } from 'react-router-dom'
<Layout>
<Routes>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Home />} />
{...}
<Route path="*" element={<NotFound />} />
</Routes>
</Layout>
Layout.js
<Fragment>
<Header></Header>
<main className="container">{props.children}</main>
</Fragment>
Header.js
import { Link, NavLink } from 'react-router-dom'
<header className={classes.header}>
<nav>
<NavLink
className={(navData) => (navData.isActive ? classes.active : '')}
to="/search"
>
Search
</NavLink>
</nav>
</header>
App.test.js
import { BrowserRouter } from 'react-router-dom';
it('renders learn react link', async () => {
render(<BrowserRouter><App /></BrowserRouter>)
const linkElement = await screen.findByText(/home/i)
expect(linkElement).toBeInTheDocument()
})
in my case, I faced this error when I used the HOC and context provider and placed my Routing component as the context.provider's child, and export my Provider component by HOC like this:
class Provider extends Component {
render() {
return (
<Context.Provider
value={{
something: this.state.something
}}
>
<Routing />
</Context.Provider>
);
}
}
export default HOC(Provider)
im my case, the problem was putting BrowserRouter inside App.js file, solved it by moving this into index.js file and wrapping it around App.js
Former Index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<React.StrictMode>
<PersistGate persistor={persistor} >
<App/>
</PersistGate>
</React.StrictMode>
</Provider>
);
reportWebVitals();
Former App.js
import { BrowserRouter, Routes, Route, useLocation} from "react-router-dom";
const App = () => {
const {pathname} = useLocation()
return (
<div className='app'>
<BrowserRouter>
<Routes>
<Route path='/*' element={<LandingPage />} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
New Index.js
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<React.StrictMode>
<PersistGate persistor={persistor} >
<BrowserRouter >
<App/>
</BrowserRouter>
</PersistGate>
</React.StrictMode>
</Provider>
);
reportWebVitals();
New App.js
import { Routes, Route, useLocation} from "react-router-dom";
const App = () => {
const {pathname} = useLocation()
return (
<div className='app'>
<Routes>
<Route path='/*' element={<LandingPage />} />
<Route path='/registeration' element={<Registration />} />
</Routes>
</div>
);
};
export default App;
Try this:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
I am trying to redirect from my context following a failed update of the state from the a cookie.
import React, { createContext, Component } from 'react';
import { withRouter } from "react-router-dom";
import Cookies from 'universal-cookie';
export const MyContext = createContext();
const cookies = new Cookies();
class MyProvider extends Component {
componentDidMount() {
this.setStateFromCookie();
}
setStateFromCookie = () => {
try {
this.setState({ data: cookies.get('my-cookie')['data'] });
} catch(error) {
console.log(error);
this.props.history.push('/');
}
return
};
render() {
return (
<MyContext.Provider value={{...this.state}}>
{this.props.children}
</MyContext.Provider>
);
}
}
export default withRouter(MyProvider);
I am using a withRouter hook to this.props.history.push('/'), becuase the context is wrapping the router
class MyApp extends Component {
render() {
return (
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Router>
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
</MyProvider>
</BrowserRouter>
);
}
}
export default MyApp;
The problem is that the redirect to the home page following the error, but the home page isn't rendering.. I still see the dashboard page.
Any idea what is going on and how to fix this
The issue is that you have a nested Router wrapping your Routes. You need to remove that and then everything will work fine
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</div>
</MyProvider>
</BrowserRouter>
When you use a nested Router, and try to navigate from Provider, the history used by Provider is being provided by BrowserRouter and hence it isn't able to communicate to the Routes whcih are dependent on the inner <Router> component for history.
Using a single router wrapping your components solves this issue
I am trying to access my store from every component page I have, so I followed the following tutorial in order to connect React Router & MobX.
http://frontendinsights.com/connect-mobx-react-router/
However, I am facing a problem at The MobX way – Provider component.
This is the code exmaple:
import { Provider } from 'mobx-react';
import usersStore from './stores/usersStore';
import itemsStore from './stores/itemsStore';
const stores = { usersStore, itemsStore };
ReactDOM.render(
<Provider {...stores}>
<Router history={history}>
<Route path="/" component={App}>
</Route>
</Router>
</Provider>,
document.getElementById('app')
);
I tried to do the same in index.js
import React from 'react'
import { render } from 'react-dom'
import { Router, hashHistory, Route, IndexRedirect } from 'react-router'
import App from './webapp/App'
import Home from './components/pages/Home'
import Dogs from './components/pages/Dogs'
import Cats from './components/pages/Cats'
import Provider from 'mobx-react'
import RootStore from './webapp/stores'
const store = RootStore
render((
<Provider rootStore={store}>
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRedirect to="/home" />
<Route path="/home" component={Home}/>
<Route path="/dogs" component={Dogs}/>
<Route path="/cats" component={Cats}/>
</Route>
</Router>
</Provider>
), document.getElementById('app'))
However, because of <Provider/>, I am getting an error:
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components).
Why am I getting that? It should work doesn't it?
Thanks for any help !
If it's a web app, react-router-dom should be used.
Here is the correct way to inject store with Provider https://github.com/mobxjs/mobx-react#observer
I wrote a solution for your code without using decorator, so it supports create-react-app:
import React, { Component } from 'react';
import { Provider, Observer } from 'mobx-react';
import { observable } from 'mobx';
import { BrowserRouter, Switch, Route, Link, Redirect } from 'react-router-dom';
const myStore = observable({
home: 'Home',
cat: 'Cat',
});
const Home = () => (
<Observer
inject={stores => ({ myStore : stores.myStore })}
render={props => (<section><h1>{props.myStore.home}</h1></section>)}
/>
);
const Cat = () => (
<Observer
inject={stores => ({ myStore : stores.myStore })}
render={props => (<section><h1>{props.myStore.cat}</h1></section>)}
/>
);
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider myStore={myStore}>
<div className="App">
<header className="App-header">
<nav>
<ul>
<li><Link to="/home">HOME</Link></li>
<li><Link to="/cat">CAT</Link></li>
</ul>
</nav>
</header>
<Switch>
<Route path='/home' exact component={Home} />
<Route path='/cat' exact component={Cat} />
<Redirect from="/" to="/home" />
</Switch>
</div>
</Provider>
</BrowserRouter>
);
}
}
export default App;
All the components are in App.js file. There is no change in default index.js from create-react-app.
Note:
The another way to doing this, it's to simply create a singleton class for the store and use export default new Store() to make it available to all components. The class itself doesn't have to be observable, but its properties do.
It seems my application will not render the component passed to <Route /> unless I refresh the page. What could I be doing wrong?
components/App/index.jsx
// dependencies
import React from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom'
// components
import Header from '../Header';
// containers
import SidebarContainer from '../../containers/SidebarContainer';
import MainContainer from '../../containers/MainContainer';
const App = ({store}) => (
<Provider store={store}>
<Router>
<div className="wrapper">
<Header />
<div className="container-fluid container-fluid--fullscreen">
<div className="row row--fullscreen">
<SidebarContainer />
<MainContainer />
</div>
</div>
</div>
</Router>
</Provider>
);
App.propTypes = {
store: PropTypes.object.isRequired,
};
export default App;
containers/MainContainer.jsx
// dependencies
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route } from 'react-router-dom'
// components
import Dashboard from '../components/Dashboard';
import List from '../components/List';
// containers
import LoginContainer from './LoginContainer.jsx'
class Main extends Component {
render() {
console.log(this.props)
return(
<div className="wrapper">
<Route exact path="/" component={Dashboard} />
<Route path="/login" component={LoginContainer} />
<Route path="/users" component={List} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.authentication.token,
};
};
const MainContainer = connect(mapStateToProps, null)(Main);
export default MainContainer;
So it seems when I click on a <Link to="/users" /> component my path changes to http://localhost:3000/users but the component does not change from Dashboard to List
I'm also noticing that when I console.log this.props from MainContainer I do not see anything related to router such as this.props.location.pathname --perhaps I'm not structuring my application correctly?
After poking around the react-router issues page on github I found this thread: https://github.com/ReactTraining/react-router/issues/4671
It appears as though the redux connect method blocks context which is required by react-router package.
That being said, the fix for this is to wrap all redux connected components that have router components inside with withRouter() like so:
containers/MainContainer.jsx
// dependencies
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom' // IMPORT withRouter
// components
import Dashboard from '../components/Dashboard';
import List from '../components/List';
// containers
import LoginContainer from './LoginContainer.jsx'
class Main extends Component {
render() {
console.log(this.props)
console.log(this.context)
return(
<div className="wrapper">
<Route exact path="/" component={Dashboard} />
<Route path="/login" component={LoginContainer} />
<Route path="/users" component={List} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.authentication.token,
};
};
// WRAP CONNECT METHOD
const MainContainer = withRouter(connect(mapStateToProps, null)(Main));
export default MainContainer;
I think you have to do little more tweak in your code to make it work. Assuming you use react-router v4, the following should solve your problem.
import { BrowserRouter, Route, Switch } from 'react-router-dom';
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<SidebarContainer />
<MainContainer />
</Switch>
</div>
</BrowserRouter>
</Provider>
I'm working on server-side rendering, so far the initial page can be rendered with react-router successfully according to the path entered to the browser e.g. /, /customers
Code
Routes.jsx
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from './Home'
import CustomerList from './CustomerList'
const PageNotFound = () => <div className="box">
<h3 className="title">Page Not Found</h3>
</div>
export default () => {
return <div>
<Switch>
<Route exact path="/" component={ Home } />
<Route path="/customers" component={ CustomerList } />
<Route component={ PageNotFound }></Route>
</Switch>
</div>
}
NavBar.jsx
import React from 'react'
import { Link } from 'react-router-dom'
export default () => <nav className="nav">
<div className="nav-right">
<Link to="/" className="nav-item is-tab">Home</Link>
<Link to="/customers" className="nav-item is-tab">Customer</Link>
</div>
</nav>
Problem
I'm only successful with server-side rendering, but I cannot get re-render with the client version, when clicking on any of the links there is no re-rendering happens (only the URL gets changed on the browser)
So I have tried with below coding
Routes.jsx
const renderCustomerList = () => {
console.log('OK')
return <CustomerList />
}
...
<Route path="/customers" render={ renderCustomerList } />
I still cannot see the log on the client version
I believe I have done with the same coding as the client-side only version (no problems on the client-side only version), so please guide how to solve the problem
Thanks all
I encounter problems with the same symptom, but may not be the same cause since your index.js is not provided here. I hope you have already solved your problem so I just put my findings here for others references.
Go check your root render function in your index.js for both client-side and server-side rendering, mine is as follows with redux:
// React 16 global polyfill for IE < 11
import 'core-js/es6/map'
import 'core-js/es6/set'
import * as React from 'react'
import ReactDom from 'react-dom'
import { Provider, connect } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom'
import store from '~/store'
import App from './App'
const mapStateToProps = (state) => ({...state})
const mapDispatchToProps = {}
const render = App => {
const Connect = connect(mapStateToProps, mapDispatchToProps)(App)
ReactDom.hydrate(
<Provider store={store}>
<Router>
<Connect />
</Router>
</Provider>,
document.getElementById('root')
)
}
render(App)
My solution is simply to use <App /> instead of <Connect />, connect store inside children Components.
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>