React - re-render components when linking between parameterized routes - reactjs

I recently reconfigured my React app, and it seems that I have broken the functionality of my parameterized routes. Rather than going too deep into how it used to work, I'll describe what I am currently stuck with and what I am aiming to achieve...
In a navigation panel on the side of my page, a user can view a list of links to resources - the urls for these links would be something like:
user/123
user/456
user/789
group/123
group/456
group/789
Clicking the first link will now render the User component in the main div on my page (rendering in {this.props.children} - see App.jsx below), and a call to componentDidMount() pulls data for the user with id 123 and populates the component. Ok, so far, so good.
However, if a user now clicks on the link for user/456, nothing happens. The url in the navbar changes, but the User component does not re-render, though clicking a link for a group will correctly clear out the User component and render the Group component instead... then, of course, I have the same problem with the groups, etc...
How can I force the re-rendering of a component when the pathname remains the same but the parameter has changed? If I've clicked the link for user/123 and then I click the link for user/456, I want that User component to re-render and pull the new data.
Other relevant code...
index.js
import { Router, Route, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/user/:id" component={User} />
<Route path="/group/:id" component={Group} />
</Route>
</Router>
App.jsx
<div className="App">
<div className="body_section_container">
<div className="body_section main">
<Nav />
{this.props.children}
</div>
<div className="body_section sidebar">
<Search searchModel={this.searchAll} user_list={this.state.user_list} group_list={this.state.group_list} organizations_list={this.state.organizations_list} />
</div>
</div>
</div>

Try plugging into componentWillReceiveProps, which should fire when the route params change. Then you can receive the new route params, submit a new query, and update state.

In the Route component, specify a key.
<Route path={YOURPATH} render={(props) => <YourComp {...props} keyProp={id} key={id}/>} />
when react see a different key, it will trigger rerender.

Related

Hide React Elements Based on Session Storage or Page

I have React application which has a structure similar to the following.
class App extends Component {
render() {
return (
<div className="App">
<NavBar />
<Router>
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/" exact component={DashboardPage} />
<Route path="/admin" exact component={AdminPage} />
// many other routes
<Route component={NotFound} />
</Switch>
</Router>
</div>
);
}
}
I do not want the login page to display the <NavBar /> element. I tried using the sessionStorage to get the userId and only display the navigation if the value is set. When I do this and go to the login page and the nav bar is not there. But when I log in, it's still not there. If I refresh however, it will appear.
I know one way to solve this is to make some sort of wrapper around the pages that do want the navigation, but I'd rather not have all of that code duplication, etc.
I feel this must be a common want, and I'm missing something dumb. Any help would be appreciated. I'm new to React so I don't follow everything that's going on here. Thanks.
I think your way of conditionally showing the NavBar is the right way. The question is how to trigger a state change so that the render method takes care of hiding and showing the NavBar, when you log in and out. I suggested maintaining a isLoggedIn state in your App component, and rendering the NavBar based on that, instead of directly accessing the SessionStorage. You could then use a custom event to update the state, when SessionStorage changes.
See this question for updating state based on Storage (in short, you fire and handle a custom event for storage changes): How to listen to localstorage in react.js
This might still be more code that you had hoped for, but it's more aligned with how React works, to derive the view (render) from component state.

How can I nest dynamic page routed components in React Routers?

I'm working on a dynamic webpage using React. I'm using a Switch component from React Routers to change from the home page to various other pages, as needed by the user. I want the following routing to create a new account:
Home page >> login page >> Create account (1) > (2) > (3) >> back to login or home page
In words: The user should be able to navigate from the home page to a login page, from the login page to a create account page with three steps (each a unique component), and from the third step back to the home page.
My trouble lies in getting from the third create-account page back to the home or login page. I can create links that bring me from Page 1 > 2, 2 > 1, 2 > 3, or 3 > 2. But I can't make page three render a link that will bring me back to Home.
I initially had a switch statement in my Create Account page rendering three separate components. If I set the route home inside of this switch statement, the div would render Home inside of it, as expected. If I simply omitted the route, the page would fail to render when I clicked on the link, but clearly had the right path because it would load properly upon refresh.
I've also tried:
Moving the Router to circumscribe the entire component, rather than just the Switch component.
Moving the Route to home before anything else in the document - as expected this just rendered the home page above the create account page upon activating the route.
Using a Redirect inside and outside of the Switch statement.
A really nasty method of rendering inside the div the value of a "component" key from inside an object, and manually updating the values in that object when the links are clicked, rather than using anything even vaguely associated with React Routers. This worked better than it should have, but not perfectly.
**As kind of a side note I've been told (though I have no personal experience) that Vue has a 'stepper' that could be used to navigate through pages 1, 2, and 3, to avoid using a nested router at all. Is there such a thing in React or React-Routers?
The App:
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/Login" component={Login} />
<Route path="/CreateAccount" component={CreateAccount} />
<Route path="/Account_Recovery" component={Recovery} />
<Route path="/RR_One" component={RR_One} />
</Switch>
</Router>
);
}
The Create Account Page as it exists now:
class CreateAccount extends React.Component {
render() {
return (
<Router>
<PageHeader pageTitle="Create a Free Account"/>
<div className="pageBody">
<div className="centerHalf">
<Route path="/CreateAccount/1" component={PageOne} />
<Route path="/CreateAccount/2" component={PageTwo} />
<Route path="/CreateAccount/3" component={PageThree} />
</div>
<p>Paragraph outside of the changing div.</p>
</div>
</Router>
)}};
An example component PageThree (the others are similar for now, just linking between each other):
class PageThree extends React.Component {
render() {
return (
<div>
<p>Page Three</p>
<Link to={{pathname: "/CreateAccount/2"}}>Go to page 2</Link> <br />
<Link to={{pathname: "/Login"}}>Complete</Link>
</div>
)}};
The solution was actually very simple: I had misunderstood the purpose of the Router component, and the nesting of these (not of the switch statement or the routes) was the real problem. By removing the inner Router component (in my case, the one in the CreateAccount component) the navigation was able to flow smoothly from page three back to an external page.
class CreateAccount extends React.Component {
render() {
return (
<div>
<PageHeader pageTitle="Create a Free Account"/>
<div className="pageBody">
<div className="centerHalf">
<Route path="/CreateAccount/1" component={PageOne} />
<Route path="/CreateAccount/2" component={PageTwo} />
<Route path="/CreateAccount/3" component={PageThree} />
</div>
<p>Paragraph outside of the changing div.</p>
</div>
</div>
)}};

React-router-dom and Redirect not being added to history?

Outline:
I'm currently trying to learn react/redux/router. As a practice project, I'm working on a basic contact/client management app. While using react-router-doms Redirect component. I can't get the back/forward navigation to work as expected.
Details:
I have a table with a list of entries located at /contacts/ and when you click on one of the table rows, I want to Redirect using a value set by the <tr>s onClick attribute.
The click on <tr key={d._id} onClick={()=>this.setState({ id: d._id })}> changes state.id to the clientIDs unique value. This results in a Redirect being true in the tables render method, triggering the Redirect.
render() { // render method of '/contacts/' {Contacts} component
return(
... // table head
{ this.state.id && <Redirect to={"/contacts/card/"+this.state.id}/> }
...// table content
)
}
This is the router which is located in the parent App.js
render() {
return (
<BrowserRouter basename="/" >
<div>
<NavBar/>
<main>
<Switch>
<Route exact path="/" component={Login}/>
<Route
exact path="/contacts" component={Contacts}/>
<Route
exact
path="/contacts/card/:clientID" component={ContactCard}/>
</Switch>
</main>
</div>
</BrowserRouter>
);
}
Question:
After the redirect, if you click the back button from /contacts/card/:clientID, the browser doesn't go back to /contacts/. instead, it cycles through any previous :clientID URLs that I've looked at.
I have tried wrapping the <tr> in a router <Link to={}> tag, but it destroyed styles.
Caveat: (this.state.id)
I know that I should be using Redux to hold the value of this.state.id for my redirect function. I haven't fully grasped how to mange multiple values with a single redux reducer yet. I will update question with the appropriate method once I do.
Found the answer, I needed to add push to my <Redirect/> component like so.
<Redirect push to={`/contacts/card/${this.state.id}`}/>
From the docs:
<Redirect/>
push: bool
When true, redirecting will push a new entry onto the history instead of replacing the current one.

React-router: components don't render unless refreshed

I have a simple App component with Links to a User index and a Cache index (for a geocaching app). My Links render fine, and when clicked they change the path in the address bar, but nothing changes in the DOM until I refresh the page, at which point the page looks the way it should. What's going on here, and what's the conventional way of dealing with it? I am using Redux as well, if that makes any difference. The following is all of the JSX returned by my App component:
<div>
<nav>
<Link to="/caches">Caches</Link>
<Link to="/users">Users</Link>
</nav>
<div>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/users" render={() => <div><UserList users={this.props.users}/></div>}/>
<Route path="/caches" component={CacheList}/>
</Switch>
</div>
</div>
Its a common issue with react-router-4.
use {pure: false} in react-redux connect or use withRouter HOC.
React router 4 does not update view on link, but does on refresh

Update property of React component in a separate component

I have a React MaterialUI AppBarcomponent with property title , that I am changing based on the value returned by window.location.pathname. So as the page/url changes, the title will change with it. Looks something like below:
<AppBar
title={this.renderTitle()}
/>
renderTitle() {
if (window.location.pathname === '/home'
return 'home';
} else if (window.location.pathname === '/login'
return 'login';
}
The issue I am running into is that renderTitle() does not get executed if a different component (so not the AppBar) causes the page/url change.
E.g. another separate React component on the page triggers the page to change, which I'd hoped with trigger renderTitle(), but it doesn't... thus the title property never updates. So if I am navigating from /home to /login, the following will happen:
pathname is /home
user presses a button which runs a function, submit(), which is used to change the page w/ react-router
renderTitle() is run at this point, but window.location.pathname is still returning the previous page
submit() changes the page to /login
window.location.pathname is now correctly set to /login, but it is too late as renderTitle() has already been run
any help is appreciated, thanks
The best way is to use react-document-title library.
From documentation:
react-document-title provides a declarative way to specify document.title in a single-page app.
This component can be used on server side as well.
If your component that renders AppBar is an actual route you can just read the pathname from the props that react router injects.
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
For example in App, About, Inbox and Message you have access to the router props. And you can also pass the props to their children.
render() {
return (
<AppBar
title={this.renderTitle(this.props.location.pathname)}
/>
);
}
And in your function just use the parameter to return the correct result. Now because you are using props your component will update automatically when they change.

Resources