I have an issue with react router 4. And I'm not sure if its solvable:
This is my application layout:
Which basically works. But the problem is that whenever I hit /items/:id/ via the link on the left side it also matches /items. Which causes the link list in the sidebar to rerender. The solution would be to nest the routes. But this is not possible due to the interface/DOM. The left sidebar needs be independent of the item detail. And I need to split those up like:
<div>
<div className={styles.sidebar}>
<HeaderContainer />
<Switch location={location}>
<Route exact path="/" component={Homepage} />
<Route path="/items" component={Items} />
</Switch>
</div>
<div className={styles.content}>
<Route path="/items/:id" component={ItemDetail} />
</div>
</div>
Thanks a lot for your help in advance!
I had a similar layout and I used something like this
//App.jsx
<Router path="/" component={Page}>
//Page.jsx
<Layout>
<MasterView>
<DetailView>
</Layout>
//MasterView.jsx
componentDidMount() {
const { dispatch } = this.props
const data = await api.getData();
dispatch(updateDetail(data));
}
connect()(MasterView)
// DetailView.jsx
import { connect } from 'react-redux';
render() {
return <ul>{this.props.list.map((item) => <li>{item}</li>)}</ul>;
}
// map the props you need to redux state
const mapStateToProps = (state) => ({ list: state.data.list });
connect(mapStateToProps)(DetailView)
Related
I have a React project that has a HeaderComponent that exists for all routes in project like this:
function App() {
return (
<Fragment>
<Router>
<HeaderComponent />
<Routes>
<Route path="/login" element={<Login />}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/" element={<LandingPage />}></Route>
</Routes>
<FooterComponent />
</Router>
</Fragment>
);
}
And my problem is that the <HeaderComponent> is rendered when the website first loads but when the user logs in, the <HeaderComponent> is not aware of the changes because the component has already mounted.
So in my <HeaderComponent>, the componentDidMount function looks like this:
componentDidMount() {
AuthService.authorizeUser()
.then((r) => {
this.setState({ loggedIn: true });
})
.catch((error) => {
this.setState({ loggedIn: false });
});
}
This only works if I refresh the page.
Basically, if a user successfully logs in (from the <Login> component), what is the proper way of making my HeaderComponent aware of this?
You can use Context API to make AuthContext to share global state within your app:
// AuthContext.js
export const AuthContext = React.createContext({});
export const AuthProvider = ({
children,
}) => {
// your context logic
return (
<AuthContext.Provider value={yourAuthValue}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => React.useContext(AuthContext);
// Layout.js
import { Outlet } from 'react-router-dom'
// Using `Outlet` to render the view within layout
export const Layout = () => {
return (
<>
<HeaderComponent />
<Outlet />
<FooterComponent />
</>
)
}
// HeaderComponent.js
import { useAuth } from './AuthContext'
export const HeaderComponent = () => {
// get state from auth context
const { isLoggedIn } = useAuth()
return // rest of your code
}
// App.js
function App() {
return (
<Fragment>
<-- Wrap your app with AuthContext let other components within your app can access auth state !-->
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<LandingPage />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
</Fragment>
);
}
There are a couple of ways to do so.
When you're facing a situation where you need to share the same state between multiple components, lifting the state up should be the first thing to try Check this codesandbox.
And some great blogposts to read, KCD - Prop Drilling, KCD - State Management with React
Such approach may cause "prop drilling" when you need the same state in deeply nested components and that's where the context API comes in handy.
codesandbox
I'm trying to separate my links by using one link with parameter for multiple links.
For example:
instead of.
User/login
User/Profile
User/home
I'm trying to use.
User/:id
When someone click on the User/login link the login component will be selected.
If someone click on the User/Profile link the Profile component will be selected.
I got it to work, but react router seem to be firing twice for each click.
Router
const AppRouter = (props) => (
<BrowserRouter >
<div className={`site-grid-container ${props.siteBgBodyColor}`}>
<div className='site-grid-nav-body'>
<NavigationBar />
<Switch>
<Route path="/" component={Home} exact={true} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route path="/user/:id" component={User} />
<Route component={PageNotFound} />
</Switch>
</div>
</div>
</BrowserRouter>
);
export default AppRouter;
Select User Component Function
const UserViews= ({ match }) => {
console.log('------- called twice -----')
const id = match.params.id;
const SelectedView = getSelectedView(id);
return (
<SelectedView />
);
}
export default UserViews;
Get Selected View
const getSelectedView= (id) => {
switch (id) {
case "login":
return Login;
case "profile":
return Profile;
case "home":
return Home;
default:
return PageNotFound;
}
}
The code above works, but react router seem to be firing twice for each click.
Is there a correct way to do what i'm trying to do?
Thanks,
i want redirect to "/user". i write but this not work.
how to correctly redirect to the right page
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
<Redirect to="/user"/>
});
}
My router list. Among them there is a router with the path "/ user"
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
UPADATE
App.js
The button I click on is in the component <SearchForm/>
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
There are two ways to programmatically navigate with React Router - <Redirect /> and history.push. Which you use is mostly up to you and your specific use case.
<Redirect /> should be used in user event -> state change -> re-render order.
The downsides to this approach is that you need to create a new property on the component’s state in order to know when to render the Redirect. That’s valid, but again, that’s pretty much the whole point of React - state changes update the UI.
The real work horse of React Router is the History library. Under the hood it’s what’s keeping track of session history for React Router. When a component is rendered by React Router, that component is passed three different props: location, match, and history. This history prop comes from the History library and has a ton of fancy properties on it related to routing. In this case, the one we’re interested is history.push. What it does is it pushes a new entry onto the history stack - aka redirecting the user to another route.
You need to use this.props.history to manually redirect:
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
this.props.history.push('/user');
});
}
You should be getting history as a prop from your <Router> component.
EDIT:
Okay thank you for the code update. The SearchForm component is not nested under your BrowserRouter, so it is not getting the history prop. Either move that component inside the BrowserRouter or use the withRouter HOC in SearchForm reacttraining.com/react-router/web/api/withRouter
Option 1: Move SearchForm inside the BrowserRouter
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
Option 2: use the withRouter HOC to inject the history prop into SearchForm manually:
import { withRouter } from 'react-router-dom';
class SearchForm extends React.Component { ... }
export default withRouter(SearchForm)
New to React:
I have a <Header /> Component that I want to hide only when the user visit a specific page.
The way I designed my app so far is that the <Header /> Component is not re-rendered when navigating, only the page content is, so it gives a really smooth experience.
I tried to re-render the header for every route, that would make it easy to hide, but I get that ugly re-rendering glitch each time I navigate.
So basically, is there a way to re-render a component only when going in and out of a specific route ?
If not, what would be the best practice to achieve this goal ?
App.js:
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Frame>
<Canvas />
<Header />
<Main />
<NavBar />
</Frame>
</div>
</BrowserRouter>
);
}
}
Main.js:
const Main = () => (
<Switch>
<Route exact activeClassName="active" path="/" component={Home} />
<Route exact activeClassName="active" path="/art" component={Art} />
<Route exact activeClassName="active" path="/about" component={About} />
<Route exact activeClassName="active" path="/contact" component={Contact} />
</Switch>
);
I'm new to React too, but came across this problem. A react-router based alternative to the accepted answer would be to use withRouter, which wraps the component you want to hide and provides it with location prop (amongst others).
import { withRouter } from 'react-router-dom';
const ComponentToHide = (props) => {
const { location } = props;
if (location.pathname.match(/routeOnWhichToHideIt/)){
return null;
}
return (
<ComponentToHideContent/>
)
}
const ComponentThatHides = withRouter(ComponentToHide);
Note though this caveat from the docs:
withRouter does not subscribe to location changes like React Redux’s
connect does for state changes. Instead, re-renders after location
changes propagate out from the component. This means that
withRouter does not re-render on route transitions unless its parent
component re-renders.
This caveat not withstanding, this approach seems to work for me for a very similar use case to the OP's.
Since React Router 5.1 there is the hook useLocation, which lets you easily access the current location.
import { useLocation } from 'react-router-dom'
function HeaderView() {
let location = useLocation();
console.log(location.pathname);
return <span>Path : {location.pathname}</span>
}
You could add it to all routes (by declaring a non exact path) and hide it in your specific path:
<Route path='/' component={Header} /> // note, no exact={true}
then in Header render method:
render() {
const {match: {url}} = this.props;
if(url.startWith('/your-no-header-path') {
return null;
} else {
// your existing render login
}
}
You can rely on state to do the re-rendering.
If you navigate from route shouldHide then this.setState({ hide: true })
You can wrap your <Header> in the render with a conditional:
{
!this.state.hide &&
<Header>
}
Or you can use a function:
_header = () => {
const { hide } = this.state
if (hide) return null
return (
<Header />
)
}
And in the render method:
{this._header()}
I haven't tried react-router, but something like this might work:
class App extends Component {
constructor(props) {
super(props)
this.state = {
hide: false
}
}
toggleHeader = () => {
const { hide } = this.state
this.setState({ hide: !hide })
}
render() {
const Main = () => (
<Switch>
<Route exact activeClassName="active" path="/" component={Home} />
<Route
exact
activeClassName="active"
path="/art"
render={(props) => <Art toggleHeader={this.toggleHeader} />}
/>
<Route exact activeClassName="active" path="/about" component={About} />
<Route exact activeClassName="active" path="/contact" component={Contact} />
</Switch>
);
return (
<BrowserRouter>
<div className="App">
<Frame>
<Canvas />
<Header />
<Main />
<NavBar />
</Frame>
</div>
</BrowserRouter>
);
}
}
And you need to manually call the function inside Art:
this.props.hideHeader()
{location.pathname !== '/page-you-dont-want' && <YourComponent />}
This will check the path name if it is NOT page that you DO NOT want the component to appear, it will NOT display it, otherwise is WILL display it.
I am somewhat new to React. so please bear with me. I have the following base structure:
<App>
this.props.children
</App>
...and in children, one component is a header that has what I want to be an optional search component:
<Header>
...some other children...
<Search /> <- STUCK HERE SHOULD BE OPTIONAL!!!
</Header>
...children from other components...
What I am trying to do is say when I go to route A, the search component should not be included (or at least not shown), but when I go to route B, it should. I have scoured for days and so far been unable to find a solution that meets this need. If it matter, I am using ES6/7 (babel via webpack).
I can set state in the APP and toggle it literally to adjust the passed down props on the Search and show or not show it, but cannot figure out how to do that dynamically based on the route.
The core issue is how to tell App (and indirectly Header) to show the search component on inside the Header on some routes, but not on others. I 'think' maybe I need some sort of abstraction/wrapper component in the middle, but am not really sure. Any though or ideas are welcome.
TIA!
First setup your routes.
<Router path="/" component={App}>
<Route path="foo" component={Header} showSearch={true} />
<Route path="bar" component={Header} showSearch={false} />
</Router>
The route will be passed down as a property, then you can access the showSearch property, which determines whether the search component is rendered.
// Header
render() {
const { showSearch } = this.props.route;
return (
<div className='header'>
// ... other components
{ showSearch ? <Search /> : null }
</div>
);
}
Maybe you don't want your header to be the top level component though. In that case define an intermediary wrapper component that forwards the route props down to the header.
<Router path="/" component={App}>
<Route path="foo" component={Shell} showSearch={true} />
<Route path="bar" component={Shell} showSearch={false} />
</Router>
// Shell
render() {
const { route } = this.props;
return (
<div className='shell'>
<Header {...route} />
</div>
);
}
Alternatively, you could do a quick and dirty check from inside your Header component.
// Header
render() {
const { hash } = window.location,
showSearch = /\/foo/.test(hash);
return (
<div className='header'>
// ... other components
{ showSearch ? <Search /> : null }
</div>
);
}
If you want to use functional components, React Router has specifically created an API for this called render for this purpose.
Example:
<Route
path='/search'
render={(props) => (
<Header {...props} showSearch={true} />
)}
/>
Then just simply use the props as normal in your component:
interface HeaderProps {
showSearch: boolean
}
export const Header: React.FC<HeaderProps> = ({ showSearch }) => {
return (
<React.Fragment>
{ showSearch ? <Search /> : null }
</React.Fragment>
)
}
See the excellent article written by Tyler McGinnis regarding this implementation:
https://ui.dev/react-router-v4-pass-props-to-components/