React Router V4 - Route between components and not entire page [closed] - javascript

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I'm learning React with React Router V4. I have an image here that might explain what I would like to do:
Click "Next" button
Send click Event to Component A ("button got clicked")
Clicking "Next" would also swap "Component B" with "Component C" while keeping everything the same on the page.
Be able to navigate to a completely different page with different components after all this.
I'm having a really hard time figuring out the routing and structure to be able to handle this.
Thanks for the help.
Does what I'm trying to accomplish here is normal or is this weird and not recommended?
What I tried:
I tried leaving Component A Inside the router on top of everything - this works until you go to page 3 because then I can't remove it.
I've been trying to implement something similar to React Router v4 example on sub-topics. Basically creating a parent component that holds Component A and underneath it have two < Match />, one for Component B and another for Component C. This hasn't worked either and I'm probably doing something very wrong here.
https://react-router.now.sh/basic
I've also been poking around reading many tutorials but they're all different versions of React and React Router. I thought this was simple but been banging my head for a week now without any progress.

Ok I finally figured this out - maybe is not the right/perfect way but it seems to be working. I'll write the code here on how I structured the routing and my components. Remember that this is using react router v4 and things are a bit different that previous versions.
//Router file - index.js
//import React, BrowserRouter, React Dom, Header and Components A, B and C parent
//Create Root component, add BrowserRouter and add a subpath for browserRouter - needed for the nested routes
//Root component renders inside HTML element with an ID of main
//Have a Match for '/' (it will actually be to "/subpath")- then it renders Parent of ABC
import React from 'react';
import { render } from 'react-dom';//instead of importing ALL react dom we just import the render
//React Router V4 uses Match and Miss - if something Matches a path it will render the assigned component and you use Miss for a 404/not found page
import { BrowserRouter, Match, Miss } from 'react-router';
import Header from './components/Header';
import ComponentABCParent from './components/ComponentABCParent';
const Root = () => {
return (
<BrowserRouter basename="/subpathHere">
<div>
<div id="header">
<Header />
</div>
<div className="app-content">
<Match pattern="/" component={ComponentABCParent}/>
<Miss component={NotFound} />
</div>
</div>
</BrowserRouter>
)
}
render(<Root/>, document.getElementById('main'));
//Parent of A, B and C with nested route
//Create parent component - place inside ComponentA and two Match's
//One Match for Component B which is rendered immediately
//Second Match for ComponentC
import React from 'react';
import {Link, Match, Miss} from 'react-router';
import OracleCircle from './ComponentA';
import Intro from './ComponentB';
import StepOne from './ComponentC';
const ComponentABCParent = ({ pathname }) => {
return (
<div>
<ComponentA />
<Match exactly pattern={`${pathname}`} component={ComponentB} />
<Match pattern={`${pathname}component-c`} component={ComponentC}/>
</div>
);
}
export default ComponentABCParent;
//Component B - ComponentB.js
//Inside ComponentB I have a Link that points to ComponentC
import React from 'react';
import {Link} from 'react-router';
const ComponentB = ({pathname}) => {
return (
<div>
<h1>"Stack Overflow is not a tutorial website"</h1>
<Link to={`${pathname}component-c`}>Go to C</Link>
</div>
);
}
export default ComponentB;
//Component C - ComponentC.js
//Render ComponentC with funny answer
import React from 'react';
import {Link} from 'react-router';
const ComponentA = ({pathname}) => {
return (
<div>
<h1>"Your Momma is a tutorial website"</h1>
</div>
);
}
export default ComponentA;
Hope this helps

Maybe something like this would help you accomplish your task.
...
const ComponentA = ({children}) => {
return (
<div>
<div>COMPONENT A</div>
{children}
</div>
)
}
<Route path={/} component={SomeWrapper}>
<Route path={/doubled} component={ComponentA}>
<Route path={/b} component={ComponentB} />
<Route path={/c} component={ComponentC} />
</Route>
<Route path={/single} component={ComponentZ} />
</Route>

Related

dupe rendering in console react [duplicate]

This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
React Hooks render twice
(5 answers)
Closed 5 months ago.
Why is this duplicating in the console? I noticed this while working on another projects and noticed the amount of HTML elements I was adding using jQuery was twice as much as expected (building a notification framework). I tried recreating the problem in a new project and the behavior persisted
DupeMountTest.js:
import React, {Component, useEffect} from "react";
const DupeMountTest = () => {
useEffect(() => {
console.log("useEffect")
}, [])
return (
<div>
<p>Test</p>
</div>
)
}
export default DupeMountTest
App.js:
import {
BrowserRouter as Router,
Route, Routes,
} from "react-router-dom";
import DupeMountTest from "./DupeMountTest";
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path={"/"} exact element={<DupeMountTest/>}/>
</Routes>
</div>
</Router>
);
}
export default App
index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Console:
I also attempted this using a class component but "Mounted" also logged twice.
DupeMountTest using class component:
import React, {Component, useEffect} from "react";
class DupeMountTest extends Component {
componentDidMount() {
console.log("Mounted")
}
render() {
return (
<div>
<p>Test</p>
</div>
)
}
}
export default DupeMountTest
Answer provided by evolutionxbox inside the comments section. Problem solved by removing <React.StrictMode> in index.js:
Updated index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
//
// </React.StrictMode>
<App />
);
React 18 introduces a new development-only check to Strict Mode. This
new check will automatically unmount and remount every component,
whenever a component mounts for the first time, restoring the previous
state on the second mount.
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors
So, your useEffect is running twice on each mount.
This was put in place to lay the groundwork for future features, so it's not exactly a bad thing.
In the future, we’d like to add a feature that allows React to add and
remove sections of the UI while preserving state. For example, when a
user tabs away from a screen and back, React should be able to
immediately show the previous screen. To do this, React would unmount
and remount trees using the same component state as before.
This feature will give React apps better performance out-of-the-box,
but requires components to be resilient to effects being mounted and
destroyed multiple times. Most effects will work without any changes,
but some effects assume they are only mounted or destroyed once.
It's also discussed in this post (thanks #evolutionxbox):
React Hooks render twice
I really liked a comment there that basically summed up the answer to what you're wondering about:
"it's a feature, not a bug."

Differences in creating/using components in React

I've been learning React following a couple different tutorials and I noticed some differences in the creation of components.
In one App.js file a component is made as follows:
import React, { Component } from "react";
import { BrowserRouter as Router, Route } from 'react-router-dom';
import ListEmployees from "./components/listEmployees";
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Route exact path="/" component={ListEmployees} />
</div>
</Router>
)
}
}
export default App;
And in another project the component is instead created like so:
import React, { Fragment, Component } from "react";
import { BrowserRouter as Router, Route } from 'react-router-dom';
import './App.css';
import ListEmployees from "./components/listEmployees";
import displayNavbar from "./components/navbar";
const App = () => {
return (
<Fragment>
<div className="container">
<ListEmployees />
</div>
</Fragment>
)
}
export default App;
What is the difference between these two components and are there advantages to using one way over the other?
Your first example is a class component. This used to be the only way to build React components (pre v16.8). In comparison to functional components, which is what your second example is, they can be more confusing. Developers, and Facebook, wanted easier ways to create React components.
Enter functional components, which utilize React Hooks (https://reactjs.org/docs/hooks-intro.html).
In my experience, functional components are easier to write, debug, and maintain. There are a few more complex problems that Hooks solve, which you can read about in the link I previously posted.
It's largely a matter of preference, but the vast majority of people I see use functional components.
The first example you gave is a class component. The second is a functional component.
Class components are the original way to make components. According to Facebook, functional components are the future.
Official Docs:
https://reactjs.org/docs/components-and-props.html

React-router v4 this.props.history.push(...) not working

I'm trying to route programatically using this.props.history.push(..) but it doesn't seem to work.
Here's the router:
import {
BrowserRouter as Router,
Route
} from 'react-router-dom';
<Router>
<Route path="/customers/" exact component={CustomersList} />
<Route path="/customers/:id" exact component="{Customer} />
</Router>
In CustomerList, a list of customers is rendered. Clicking on a customer (li) should make the application route to Customer:
import { withRouter } from 'react-router'
class Customers extends Component {
static propTypes = {
history: PropTypes.object.isRequired
}
handleCustomerClick(customer) {
this.props.history.push(`/customers/${customer.id}`);
}
render() {
return(
<ul>
{ this.props.customers.map((c) =>
<li onClick={() => this.handleCustomerClick(c)} key={c.id}>
{c.name}
</li>
</ul>
)
}
}
//connect to redux to get customers
CustomersList = withRouter(CustomersList);
export default CustomersList;
The code is partial but illustrates perfectly the situation.
What happens is that the browser's address bar changes accordingly to history.push(..), but the view does not update, Customer component is not rendered and CustomersList is still there. Any ideas?
So I came to this question hoping for an answer but to no avail. I have used
const { history } = this.props;
history.push("/thePath")
In the same project and it worked as expected.
Upon further experimentation and some comparing and contrasting, I realized that this code will not run if it is called within the nested component. Therefore only the rendered page component can call this function for it to work properly.
Find Working Sandbox here
history: v4.7.2
react: v16.0.0
react-dom: v16.0.0
react-router-dom:
v4.2.2
It seems things have changed around a bit in the latest version of react router. You can now access history via the context. this.context.history.push('/path')
Also see the replies to the this github issue: https://github.com/ReactTraining/react-router/issues/4059
You can try to load the child component with history. to do so, pass 'history' through props. Something like that:
return (
<div>
<Login history={this.props.history} />
<br/>
<Register/>
</div>
)
For me (react-router v4, react v16) the problem was that I had the navigation component all right:
import { Link, withRouter } from 'react-router-dom'
class MainMenu extends Component {
render() {
return (
...
<NavLink to="/contact">Contact</NavLink>
...
);
}
}
export default withRouter(MainMenu);
Both using either
to="/contact"
or
OnClick={() => this.props.history.push('/contact')};
The behavior was still the same - the URL in browser changed but wrong components were rendered, the router was called with the same old URL.
The culprit was in the router definition. I had to move the MainMenu component as a child of the Router component!
// wrong placement of the component that calls the router
<MainMenu history={this.props.history} />
<Router>
<div>
// this is the right place for the component!
<MainMenu history={this.props.history} />
<Route path="/" exact component={MainPage} />
<Route path="/contact/" component={MainPage} />
</div>
</Router>
You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
import React, { Component } from 'react'
import { withRouter } from 'react-router';
// you can also import "withRouter" from 'react-router-dom';
class Example extends Component {
render() {
const { match, location, history } = this.props
return (
<div>
<div>You are now at {location.pathname}</div>
<button onClick={() => history.push('/')}>{'Home'}</button>
</div>
)
}
}
export default withRouter(Example)
Seems like an old question but still relevant.
I think it is a blocked update issue.
The main problem is the new URL (route) is supposed to be rendered by the same component(Costumers) as you are currently in (current URL).
So solution is rather simple, make the window url as a prop, so react has a chance to detect the prop change (therefore the url change), and act accordingly.
A nice usecase described in the official react blog called Recommendation: Fully uncontrolled component with a key.
So the solution is to change from
render() {
return(
<ul>
to
render() {
return(
<ul key={this.props.location.pathname}>
So whenever the location changed by react-router, the component got scrapped (by react) and a new one gets initiated with the right values (by react).
Oh, and pass the location as prop to the component(Costumers) where the redirect will happen if it is not passed already.
Hope it helps someone.
I had similar symptoms, but my problem was that I was nesting BrowserRouter
Do not nest BrowserRouter, because the history object will refer to the nearest BrowserRouter parent. So when you do a history.push(targeturl) and that targeturl it's not in that particular BrowserRouter it won't match any of it's route, so it will not load any sub-component.
Solution
Nest the Switch without wrapping it with a BrowserRouter
Example
Let's consider this App.js file
<BrowserRouter>
<Switch>
<Route exact path="/nestedrouter" component={NestedRouter} />
<Route exact path="/target" component={Target} />
</Switch>
</BrowserRouter>
Instead of doing this in the NestedRouter.js file
<BrowserRouter>
<Switch>
<Route exact path="/nestedrouter/" component={NestedRouter} />
<Route exact path="/nestedrouter/subroute" component={SubRoute} />
</Switch>
</BrowserRouter>
Simply remove the BrowserRouter from NestedRouter.js file
<Switch>
<Route exact path="/nestedrouter/" component={NestedRouter} />
<Route exact path="/nestedrouter/subroute" component={SubRoute} />
</Switch>
Let's consider this scenario. You have App.jsx as the root file for you ReactJS SPA. In it your render() looks similar to this:
<Switch>
<Route path="/comp" component={MyComponent} />
</Switch>
then, you should be able to use this.props.history inside MyComponent without a problem. Let's say you are rendering MySecondComponent inside MyComponent, in that case you need to call it in such manner:
<MySecondComponent {...props} />
which will pass the props from MyComponent down to MySecondComponent, thus making this.props.history available in MySecondComponent
You need to export the Customers Component not the CustomerList.
CustomersList = withRouter(Customers);
export default CustomersList;
I see that you are using a class component but in case you decide to switch to functional component or encountered the same issue with a functional component in your application, you can fix this issue by using the "useHistory" hook API by react-router-dom.
Example of usage:
import { useHistory } from "react-router-dom";
const Customers = ({customer}) => {
let history = useHistory();
const handleCustomerClick = (customer) => {
history.push(`/customers/${customer.id}`);
}
return (
//some JSX here
);
};
You may find the official documentation here: https://reactrouter.com/web/api/Hooks/usehistory
Beginner's mistake when working with routing is the importance of using withRouter directly with the component and not put any other high order component in between (or at least one that doest not know to push the props.history to its children:
Wrong: export default withRouter(withErrorHandler(Foo));
Correct: export default withErrorHandler(withRouter(Foo));
`const navigate=useNavigate();
navigate(/customers/${customer.id}); `
Don't use with Router.
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err,values)=>{
if(!err){
this.setState({
visible:false
});
this.props.form.resetFields();
console.log(values.username);
const path = '/list/';
this.props.history.push(path);
}
})
}
It works well.
You need to bind handleCustomerClick:
class Customers extends Component {
constructor() {
super();
this.handleCustomerClick = this.handleCustomerClick(this)
}
this.props.history.push(`/customers/${customer.id}`, null);

React routes not automatically updating when nested under 'smart' Redux container components

I'm trying to create an Electron app using React, React-router and Redux. What I'm finding is that my routing logic works absolutely fine when I'm nesting the switch/route logic under a purely presentational component (Page), but that I'm forced to refresh the page to see navigational changes if nested under a 'smart' container component.
Near the top of my React component hierarchy (right beneath HashRouter) I have a Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
Here, DetailPane and SideBar are both container components wrapped around presentational components of the same name.
At startup (and during hot reloads), I create my React hierarchy using this function:
export default () => (
<Router>
<Page>
<Switch>
<Route exact path='/txDefinitions/:definitionName/:fieldName' component={FieldPage}/>
<Route exact path='/txDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/rxDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/'/>
<Route component={Route404}/>
</Switch>
</Page>
</Router>
This means that <Switch>...</Switch> gets nested underneath <DetailPane>.
If I try to navigate around my app (clicking links in the side bar), I won't actually see the detail pane render the new component until I force-reload the Electron app.
However, I find that routing works as expected if I omit DetailPane from Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
{props.children}
</div>
);
}
Here is my React hierarchy without DetailPane (works fine):
Here is my React hierarchy with DetailPane (does not work right):
(Apologies for using images but I'm not sure if there's a way to copy from React devtools into clipboard - appears larger if opened in a new tab).
As I was writing this question, I realised this wouldn't be a huge issue for me because earlier refactoring had made the 'smart' version of DetailPane apparently obsolete. Using the purely presentational version of DetailPane
instead resolves this issue:
import * as React from 'react';
//import {DetailPane} from '../../containers'; // Smart/Redux
import {DetailPane} from '../../components'; // Dumb/presentational
import {SideBar} from '../../containers/';
const styles = require('./Page.scss');
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
However, I'm still curious why this doesn't work for the container component version. For reference, this is the container component version of DetailPane:
import {connect} from 'react-redux';
import {DetailPane} from '../../components';
// TODO: delete this container?
function mapStateToProps (state): {} {
return {};
}
function mapDispatchToProps (dispatch) {
// TODO.
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(DetailPane);
The connect HOC implements shouldComponentUpdate logic so if the props don't change, the component doesn't update.
To prevent this from occurring, and have the component always render, you can override the pure option in the connect call.
export default connect(mapStateToProps, mapDispatchToProps, undefined, { pure: false })(DetailPane);
See the react-redux API docs for more details.

Using React Router v4 is it possible to create multi level routes in child components?

So I'm fairly new to the React framework, and I have decided to use Router v4, even though it is in beta, at the time of writing this, it seems to be the best way to go about things.
I have a router that shows one of two components
import React from 'react';
import { HashRouter as Router, Route } from 'react-router-dom';
import Category from './category/Category';
import Hist from './hist/Hist';
const Controls = React.createClass({
render: function() {
return (
<Router>
<div>
<Route path='/category/:categoryId' component={Category}} />
<Route path='/Hist' component={Hist} />
</div>
</Router>
);
}
});
export default Controls;
This works like a charm, of course, what I want to do now is to have some more routing inside of the Category component. I have tried something like this:
import React from 'react';
import { Route, Link } from 'react-router-dom';
const Category = React.createClass({
render: function() {
return (
<div>
<ul>
<li><Link to='/category/1'>Category 1</Link></li>
<li><Link to='/category/2'>Category 2</Link></li>
</ul>
<div className='category-display'>
<Route path='/category/1'>This is Category 1</Route>
<Route path='/category/2'>This is Category 2</Route>
</div>
</div>
);
}
});
But this is giving me a weird error saying something like
Uncaught Error: React.Children.only expected to receive a single React element child.
Is it even possible to pull off a stunt like this or does anyone have any other good suggestions for me?
Or is the best solution to rethink and implement some sort of conditional rendering depending on the route I get to the Category component as props from the first router.
Update:
I realised that <Route> does not seem to accept contents, so I added a component called Display and imported it like so:
import React from 'react';
import { Route, Link } from 'react-router-dom';
import Display
const Category = React.createClass({
render: function() {
return (
<div>
<ul>
<li><Link to='/category/1'>Category 1</Link></li>
<li><Link to='/category/2'>Category 2</Link></li>
</ul>
<div className='category-display'>
<Route path='/category/1' component={Display} />
<Route path='/category/2' component={Display} />
</div>
</div>
);
}
});
However Display is not rendered. Any ideas as to why this is would be greatly appreciated.
So it actually turns out that my update (see question above) did the trick. The Display component was at fault here, not the router.
So to recap if anyone else stumbles upon this:
<Route> does not accept any contents, one could have used the render function of <Route> to do some quick testing. Everything is neatly summarised in the React Router API documentation.
Cheers!

Categories

Resources