React: Problem in dynamic nested routing. How to solve this error? - javascript

I am getting the following error while trying to implement dynamic routing in React JS.
The required files are:
Assignment.js
import React, { Component } from 'react';
import Users from './containers/Users/Users';
import Courses from './containers/Courses/Courses';
import {Route, Link, Switch, Redirect} from 'react-router-dom';
import Course from './containers/Course/Course';
class Assignment extends Component{
render(){
return(
<div>
<ul>
<li><Link to ="/Users"> Users </Link></li>
<li><Link to ="/Courses"> Courses </Link></li>
</ul>
<Switch>
<Route path ="/Users" component = {Users}/>
<Route path ="/Courses" exact component = {Courses}/>
</Switch>
</div>
)
}
};
export default Assignment;
Courses.js
import React, { Component } from 'react';
import {Link, Route} from 'react-router-dom';
import './Courses.css';
import Course from '../Course/Course';
class Courses extends Component {
state = {
courses: [
{ id: 1, title: 'Angular - The Complete Guide' },
{ id: 2, title: 'Vue - The Complete Guide' },
{ id: 3, title: 'PWA - The Complete Guide' }
]
}
render () {
return (
<div>
<h1>Amazing Udemy Courses</h1>
<section className="Courses">
{
this.state.courses.map( course => {
return (<Link key ={course.id} to = {this.props.match.url + '/' + course.id + '/' + course.title}>
<Course className = "Course" name = {course.title} no = {course.id} />
</Link>);
} )
}
<Route path = "/Courses/:id/:name" exact component = {Course} />
</section>
</div>
);
}
}
export default Courses;
Course.js
import React, { Component } from 'react';
class Course extends Component {
render () {
return (
<div>
<h1>{this.props.match.params.name}</h1>
<p>{this.props.match.params.id}_</p>
</div>
);
}
}
export default Course;
Why am I getting this error? Can anyone fix this? I am also finding it difficult to following dynamic routing.
PS. I am getting the error at /Courses url only not at the base url.

Have you tried withRouter?
import { withRouter } from 'react-router-dom';
console.log(props.match.params);
then export the component like:
export default withRouter(TestComponent);

Problems
The props such as params are only passed down to the top-level component which is rendered by a Route. When you are rendering the list of individual Course components inside your Course component, the Courses gets this.props.params but each Course does not. You can pass them down manually:
<Course {...this.props} className="Course" name={course.title} no={course.id} />
The above passes all props, while the below passes just the params prop.
<Course params={this.props.params} className="Course" name={course.title} no={course.id} />
This resolves your error, but it is not at all doing what you want it to be doing. The match is for the current URL, so this.props.match.params.name and this.props.match.params.id are both empty values when we are on the /Courses page. Meanwhile, the props className, name, and no which you set on the Course are all unused.
Additionally, the Route to "/Courses/:id/:name" which you have put inside of Courses should really be on the top level of the app alongside the main "/Courses" Route. Ideally it should be listed before the courses homepage route because you want to match to more specific paths before broader ones, but it won't present any conflicts with exact either way.
There is a lot wrong here and I recommend that you read up on the fundamentals of react-router and writing reusable components.
Rewrites
You are trying to use the same component to render a course for both your Route "/Courses/:id/:name" and as a list item on the Courses page, but one needs to have its data passed directly as props while the other gets its data from this.props.match.params. In order to solve this, we will make a component that handles just the rendering of the course. It gets its information from props, and is agnostic to where those props come from. This means we can use this component on any page of your app as long as we pass it a name and no. I used a function component, but it doesn't matter.
const CourseListItem = ({ name, no }) => {
return (
<div className="course">
<h1>{name}</h1>
<p>Course #{no}</p>
</div>
);
};
We can't send our Route directly to this component, because it wouldn't know the name and no. So we use an intermediate component that is responsible for setting the props of CourseListItem based on the router props (this.props.match.params). You could of course render other HTML or components and not just CourseListItem. I used a class for consistency with what you had before, but again it doesn't matter.
class SingleCoursePage extends Component {
render() {
return (
<CourseListItem
name={this.props.match.params.name}
no={this.props.match.params.id}
/>
);
}
}
In our Courses component, we loop through the courses from this.state and for each course we render the CourseListItem, setting the props name and no from the course object. See how we can use same component in different ways? If you wanted, you could make className be a prop of CourseListItem so that you could style it differently in different places.
class Courses extends Component {
state = {
courses: [
{ id: 1, title: "Angular - The Complete Guide" },
{ id: 2, title: "Vue - The Complete Guide" },
{ id: 3, title: "PWA - The Complete Guide" }
]
};
render() {
return (
<div>
<h1>Amazing Udemy Courses</h1>
<section className="Courses">
{this.state.courses.map((course) => {
return (
<Link
key={course.id}
to={"/Courses/" + course.id + "/" + course.title}
>
<CourseListItem name={course.title} no={course.id} />
</Link>
);
})}
</section>
</div>
);
}
}
As I explained, we are moving that Route for the single course page up to the top-level component, alongside the other routes.
class Assignment extends Component {
render() {
return (
<BrowserRouter>
<div>
<ul>
<li><Link to="/Users">Users</Link></li>
<li><Link to="/Courses">Courses</Link></li>
</ul>
<Switch>
<Route path="/Users" component={Users} />
<Route path="/Courses/:id/:name" component={SingleCoursePage} />
<Route path="/Courses" exact component={Courses} />
</Switch>
</div>
</BrowserRouter>
);
}
}
Code Sandbox Link - There's no CSS styling but all of the routing works!

Related

How to pass props (or state) in the Link to the class component? [duplicate]

This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
How to pass params into link using React router v6?
(5 answers)
Closed 4 months ago.
I have a BoardHome.tsx component and I can't pass props (or state) to the Board.tsx component in the Link. I only get an empty object or null.
BoardHome.tsx
export default function BoardHome(props: { id: number; title: string }) {
//props are displayed correctly
console.log(props)
return (
<div className="board-home">
<Link
className="board-link"
to={{ pathname: '/board/' + props.id }}
state={{ id: props.id }}
>
<div className="board-fade">
<h2 className="board-title">{props.title}</h2>
</div>
</Link>
<Routes>
<Route path="/board/:id" element={<Board />} />
</Routes>
</div>
)
}
Board.tsx
import React from 'react'
import './board.scss'
import '../../index.css'
interface BoardState{
id: number
}
export default class Board extends React.Component<{ id?: number }, BoardState> {
render() {
//empty object
console.log(this.props)
//null
console.log(this.state)
return (
<div className="board-header">
<h1 className="board-h1">{this.state.id}</h1>
</div>
)
}
}
I don't know if it matters, but both components should be class components, not function components.
I tried different ways, but it still failed to pass the props.
Was reading:
How to pass data from a page to another page using react router
How to pass props within components with react-router v6
how to pass props to react router v6 link
How to pass props using react router link v6?
React: how to pass props through a Link from react-router-dom?
How to pass data from a page to another page using react router
all these answers are for functional components
You are not passing any prop to the Board. Pass the prop as you would always do in React.
<Routes>
<Route path="/board/:id" element={<Board id={props.id} />} />
</Routes>
You forgot adding your constructor
export default class Board extends React.Component<{ id?: number }, BoardState> {
constructor(props) {
super (props);
this.state = {
id: this.props.id, // or make this optional in the interface
}
}
render() {
//empty object
console.log(this.props?.id)
//null
console.log(this.state?.id)
return (
<div className="board-header">
<h1 className="board-h1">{this.state.id}</h1>
</div>
)
}
}

Push to new route without any further actions on the component

We use an external componet which we don't control that takes in children which can be other components or
used for routing to another page. That component is called Modulation.
This is how we are currently calling that external Modulation component within our MyComponent.
import React, {Fragment} from 'react';
import { withRouter } from "react-router";
import { Modulation, Type } from "external-package";
const MyComponent = ({
router,
Modulation,
Type,
}) => {
// Need to call it this way, it's how we do modulation logics.
// So if there is match on typeA, nothing is done here.
// if there is match on typeB perform the re routing via router push
// match happens externally when we use this Modulation component.
const getModulation = () => {
return (
<Modulation>
<Type type="typeA"/> {/* do nothing */}
<Type type="typeB"> {/* redirect */}
{router.push('some.url.com')}
</Type>
</Modulation>
);
}
React.useEffect(() => {
getModulation();
}, [])
return <Fragment />;
};
export default withRouter(MyComponent);
This MyComponent is then called within MainComponent.
import React, { Fragment } from 'react';
import MyComponent from '../MyComponent';
import OtherComponent1 from '../OtherComponent1';
import OtherComponent2 from '../OtherComponent2';
const MainComponent = ({
// some props
}) => {
return (
<div>
<MyComponent /> {/* this is the above component */}
{/* We should only show/reach these components if router.push() didn't happen above */}
<OtherComponent1 />
<OtherComponent2 />
</div>
);
};
export default MainComponent;
So when we match typeB, we do perform the rerouting correctly.
But is not clean. OtherComponent1 and OtherComponent2 temporarily shows up (about 2 seconds) before it reroutes to new page.
Why? Is there a way to block it, ensure that if we are performing router.push('') we do not show these other components
and just redirect cleanly?
P.S: react-router version is 3.0.0

React Routing : toggle switch onclick dynamic routing

I am using a toggleswitch component in my react app. Within every event change of the toggle button, I want to change my routing, inside this component. Using react routing for the first time so I am pretty confused how can I handle it within the component.
If the state is true, I want to route it to "/", else i want it to "/videos". Here is my code :
import React, { Component } from "react";
class ToggleSwitch extends Component {
constructor() {
super();
this.state = {
toggleValue: false
};
this.changeToggleMenuValue = this.changeToggleMenuValue.bind(this);
}
changeToggleMenuValue(event) {
this.setState({
toggleValue: !this.state.toggleValue
});
}
render() {
return (
<div className="onoffswitch">
<input
type="checkbox"
name="onoffswitch"
class="onoffswitch-checkbox"
id="myonoffswitch"
onClick={e => this.changeToggleMenuValue(e)}
/>
<label class="onoffswitch-label" for="myonoffswitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
);
}
}
export default ToggleSwitch;
the base routing structure in react is like below:
1.Root Component:
basically you have a Root Component in your application, mainly the <App /> component
2.Inner Components:
inside of your <App /> component you render 2 type of components:
components that should render in your routes
components that are shared between your routes ( which means that they are visible in every route )
type 2 components would render on each route, because they are out of Switch, like code structure below:
function App(props) {
return (
<>
<Header />
<Switch>
...your routes
</Switch>
<SharedComponent_1 /> // may be a notif manager
<SharedComponent_2 /> // may be a footer
<SharedComponent_1 /> // other type of shared component
</>
)
}
if you want to navigate from a route to another route inside of your component logic, you should ask your self two questions about your component:
is my component directly rendered by a <Route ... />?
is my component is just a simple sub component that rendered by another component which rendered by a <Router ... />
if your component has criteria of condition 1, then you already have history, match, location in your props and you can use history.push('/targetRoute') to navigate your user to another route.
however, if your component has criteras described in condition 2, you should wrap your component by a withRouter to get history, match and location into your props and user push function of history to navigate user around.
This function should be work on your code:
changeToggleMenuValue() {
this.setState({
toggleValue:
this.state.toggleValue
? history.push('') // or history.push('/')
: history.push('/videos');
});
}

Problem with loading JSON file in React Router

I try to get only the name's region in this API link but it shows me errors in the properties of the setState. Syntax problem or something else?
By the way: Why I use this case, map function? For future purposes.
Thanks!
App Js :
import React from 'react';
import './App.css';
import Region from './region';
import {BrowserRouter,Switch,Route} from "react-router-dom";
function App() {
return (
<div className="App">
<BrowserRouter>
<Switch>
<Route exact path = "/" component={Region}/>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
the component:
import React, { Component } from "react";
class Region extends Component {
constructor (props) {
super (props)
this.state = {
items:[],
isLoad:false
}
}
ComponentDidMount () {
fetch("https://restcountries.eu/rest/v2/region/europe").then(res=>res.json()).then(JSON=> {
this.setState ({
isLoad = true,
items = JSON,
})
})
}
render () {
let {isLoad,items} = this.state;
if(!isLoad) {
return <div>Loading...</div>
}
else {
return <div>
<ul>
{items.map(items=>(
<li key={items.name}> </li>
))}
</ul>
</div>
}
}
}
export default Region;
json file: https://restcountries.eu/rest/v2/region/europe
The reason behind .map() is you get an opportunity to manipulate your array elements by accessing each elements and returning a different way, structure. In React case it helps to render proper JSX elements for render() method. Suggested read Lists and Keys from React's official documentation.
Read from Array.prototype.map() documentation:
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
First issue is wrongly passed properties to setState(), please note that I'm using : instead of =. Try to change as the following:
this.setState({
isLoad: true,
items: JSON,
});
Additionally you can try the following - you had the same name for current element as the array itself - it just better to have different name:
return <div>
<ul>
{items && items.map(e => <li key={e.name}> {e.name} </li>)}
</ul>
</div>
I hope that helps!

Communicating between React components

I'm new to react so this is something I don't know. In the app that I
'm working with it has a main component where other components are loaded.
Like this,
render() {
return (
<div className="index">
<HeaderComponent />
<MainHeroComponent />
<AboutComponent />
</div>
);
}
And I want when someone clicks a link in HeaderComponent to show the about component. And hide the MainHeroComponent. How can I do such communication between components in React? Is it possibe?
Thanks
Use React-Router and create routes for this scenario instead of direct communication between components. Sample app structure using react-router
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<HeaderComponent />
</div>
)
}
})
render((
<Router>
<Route path="/" component={App}>
<Route path="hero" component={MainHeroComponent} />
<Route path="about" component={AboutComponent} />
</Route>
</Router>
), document.body)
For more details on router refer: https://github.com/reactjs/react-router/blob/master/docs/guides/RouteConfiguration.md
Aditya's answer is probably a better solution, but if you really want to it your way, you can use state and callbacks.
class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
showHero: true
};
this.toggleShowHero = this.toggleShowHero.bind(this);
}
toggleShowHero() {
this.setState({
showHero: !this.state.showHero
});
}
render() {
return (
<div className="index">
<HeaderComponent onClick={toggleShowHero}/>
{
this.state.showHero ?
<MainHeroComponent /> :
<AboutComponent />
}
</div>
);
}
There are various ways you can achieve this, including React-routers and Redux, but since you're new to React, it'll be good if you get familiar with the basics first. For a start, you have to change the state of the main component to decide which child component to render.
In the main component code snippet you posted, initialize a state in the constructor as follows:
/* in the main component */
constructor(props) {
super(props);
this.state = {
showAbout: true
};
}
Then modify the render function as follows, to pass a reference to your main component, down to your header component:
/* in the main component */
<HeaderComponent mainComponent={this}/>
Then, in HeaderComponent, attach a click event handler to the link on which you want to perform the operation.
/* in HeaderComponent */
<a href="#" ....... onClick={this.showAbout.bind(this)}>Show About</a>
In the same component, define the showAbout function as follows:
/* in HeaderComponent */
showAbout () {
let mainComponent = this.props.mainComponent;
mainComponent.setState({
showAbout: true
)};
}
Finally, back in the render function of the main component:
/* in the main component */
render () {
let mainHeroComponent, aboutComponent;
if (this.state.showAbout) {
aboutComponent = (
<AboutComponent/>
);
} else {
mainHeroComponent = (
<MainHeroComponent/>
);
}
return (
<div className="index">
<HeaderComponent mainComponent={this}/>
{mainHeroComponent}
{aboutComponent}
</div>
);
}
And you're done! Basically, a component gets re-rendered every time its state is changed. So each time you click on the link, the main component's state is changed with a new value of showAbout. This will cause the main component to re-render itself, and, based on the value of showAbout, it will decide whether to render MainHeroComponent or AboutComponent.
But you should make sure you have a similar logic to display MainHeroComponent as well, instead of AboutComponent, just to switch the views.

Categories

Resources