React Routing : toggle switch onclick dynamic routing - javascript

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');
});
}

Related

Navigate to welcome page after login page using React Router Dom v6

I'm a beginner learning React and using React v 17.0.2, react-router-dom v 6.0.2. I'm following a course made for react-router-dom v4. I'm not able to get page navigation working if I try to navigate from a successful login to append a welcome message to the url. In v4 this is achieved by a {this.props.history.push("/welcome") method. I'm not able to something equivalent in V6. Specifically, I would like to know how to handle the loginClicked method.
Based on the helpful guidance from Himanshu Singh, I tried the following:
import { computeHeadingLevel } from '#testing-library/react'
import React, { Component } from 'react'
import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'
class TodoApp extends Component {
render() {
return (
<div className="TodoApp">
<Router>
<Routes>
<Route path="/" exact element={<LoginComponent />} />
<Route path="/enlite" element={<LoginComponent />} />
<Route path="/welcome" element={<WelcomeComponent />} />
</Routes>
</Router>
{/* <LoginComponent /> */}
</div>
)
}
}
class WelcomeComponent extends Component {
render() {
return <div>Welcome to Enlite</div>
}
}
class LoginComponent extends Component {
constructor(props) {
super(props)
this.state = {
username: 'testuser',
password: '',
hasLoginFailed: false,
showSuccessMessage: false
}
this.handleChange = this.handleChange.bind(this)
this.loginClicked = this.loginClicked.bind(this)
}
handleChange(event) {
this.setState(
{
[event.target.name]
: event.target.value
})
}
**loginClicked() {
if (this.state.username === 'testuser' &&
this.state.password === 'dummy') {
function HandlePageNav() {
let navigate = useNavigate()
navigate('/welcome')
}
**HandlePageNav();**
}
else {
this.setState({ showSuccessMessage: false })
this.setState({ hasLoginFailed: true })
}
}**
render() {
return (
<div>
{this.state.hasLoginFailed && <div>Invalid Credentials</div>}
{this.state.showSuccessMessage && <div>Welcome to Enlite</div>}
User Name: <input type="text" name="username" value={this.state.username} onChange={this.handleChange} />
Password: <input type="password" name="password" value={this.state.password} onChange={this.handleChange} />
<button onClick={this.loginClicked}>Login</button>
</div>
)
}
}
export default TodoApp
This gives the following error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
Basically calling hooks in class components is not supported. I also tried to completely do away with the function like this:
loginClicked() {
if (this.state.username === 'testuser' &&
this.state.password === 'dummy') {
let navigate = useNavigate()
navigate('/welcome')
}
else {
this.setState({ showSuccessMessage: false })
this.setState({ hasLoginFailed: true })
}
}
This gives a compile error:
Line 85:32: React Hook "useNavigate" cannot be called in a class
component. React Hooks must be called in a React function component or
a custom React Hook function react-hooks/rules-of-hooks Line 89:13:
'HandlePageNav' is not defined
no-undef
The above makes me wonder if I need to refactor my entire code into a function component or if there's a way to achieve this page navigation but keeping the class component. Other than that I would appreciate any help or insights on this problem. Thanks in advance.
UseNavigate Hook will not work here because hooks are meant to be used in functional components not class components.
What you can do for now is, since no proper doc is provided for class component
Try to use Functional Components : the most easiest way
Use a HOC component around the class component and pass history and other necessary props to it through that component.
Note: Here I tried second approach. You can follow this: https://codesandbox.io/s/snowy-moon-30br5?file=/src/App.js

React Router: How to redirect to a different component when a link/button is clicked?

Just started learning web-dev recently so please excuse my perhaps naive question.
I've seen many examples of using <Link> and <Route> to update the page. However, most contain a fixed navigation bar where the links sit, and the components themselves have but static content.
It's not immediately clear to me as to how to correctly update the page if the links/buttons are inside the child component. For example, given a container:
<div id='container'><h1>components should be rendered in here, but only 1 at a time.</h1></div>
and 2 components, C1 and C2:
class C1 extends React.Component {
/*other functions*/
render() {
return(
<div>
<p>Other things</p>
<Button>Click me to Redirect to C2!</Button>
</ div>
)
}
}
class C2 extends React.Component {
/*other functions*/
render() {
return(
<div>
<p>Other things</p>
<a>Click me to Redirect to C1!</a>
<p>Other things</p>
</ div>
)
}
}
Assume these 2 components are to be rendered under the 'container' div, with C1 being the default component.
How should I set up react-router to navigate between C1 & C2, and theoretically for more than two components?
react-router and react-router-dom are two complementary libraries that help you use the Browser history api using Javascript History package under the hood.
To implement what you want you should use BrowserRouter, Switch and Route Components within react-router-dom. BrowserRouter acts as a provider to its children of some utilities and the current location the history is in (current hostname, pathname, queryParams, etc.) while Switch and Route work together by Route being the component that bind a path you want with a React component to render and Switch the component that checks between all its child Route's and Render the first that match the current pathname the history has, you can add a Route without a path prop so Switch falls back to it in case the current history location doesn't match any.
An example Would be something like this
import React from 'react'
import {BrowserRouter, Switch, Route} from 'react-router-dom'
import C1 from './C1'
import C2 from './C2'
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div id="container">
<BrowserRouter>
<Switch>
<Route path="/view1" component={C1}/>
<Route path="/view2" component={C2}/>
<Route component={C1} />
</Switch>
</BrowserRouter>
</div>;
}
}
Now, there's multiple ways to navigate through the declared pages. One is to directly use the Link component and pass the pathname you want to go using the 'to' prop.
class C2 extends React.Component {
/*other functions*/
render() {
return(
<div>
<p>Other things</p>
<Link to="/view1">Click me to Redirect to C1!</Link>
<p>Other things</p>
</ div>
)
}
}
Link Actually renders an anchor with the href you gave through the to prop.
Another way to do navigate is to use the history api which is injected as a prop when rendered from a Route. This allows you to add some more logic before actually setting the new location. Here's a simple example
class C2 extends React.Component {
/*other functions*/
redirectToC1(){
// do some extra logic you may want
this.props.history.push("/view1")
}
render() {
return(
<div>
<p>Other things</p>
<a onClick={() => this.redirectToC1()}>Click me to Redirect to C1!</a>
<p>Other things</p>
</ div>
)
}
}
Be sure to dive in on the documentation to see more.
You can call the components as a functions, example :
export default class App extends React.Component {
constructor() {
super();
this.state = {
isActive: true
};
}
C1 = () => {
return <h1>C1</h1>;
};
C2 = () => {
return <h1>C2</h1>;
};
render() {
return <div id="container">{this.state.isActive ? this.C1 : this.C2}</div>;
}
}
with the example above not using any routers. you can see the documentation how to install and use react navigation here: https://reactnavigation.org/docs/hello-react-navigation
You can use Scrollintoview
You can check out this small demo I made for reference demo
This stackoverflow will also be useful

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

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!

React router pass callback with current route to child component

I am using react with react router. Some of my components have sub-routes defined so I want to pass them a callback that enables to returning to a specific route/component. I want to avoid passing a string to a specific route (because of the dependency when routing changes happen in the code). So i prefer passing a callback and populating it with the value of match.url.
But this does not work: Instead of passing the value, match.url always refers to the current route.
Parent component (simplified):
export class ParentComponent extends React.Component {
render() {
const { history, match, contentId } = this.props;
return (
<div>
<div>
<div>Block 1</div>
<div>Block 2</div>
<div>Block 3</div>
</div>
{contentId && <MyChildComponent content={contentId} goBack={() => history.push(match.url)} />}
</div>
);
}
}
My child component (simplified):
export class MyChildComponent extends React.PureComponent {
render() {
return (
(
<React.Fragment>
<div role="dialog" onClick={this.props.goBack} />
</React.Fragment>),
);
}
}
My router:
const Routes = () => (
<Router history={createBrowserHistory()}>
<div>
<Route path="/result/:contentId?" component={ParentComponent} />
</div>
</Router>
);
So when I go to /result I see - as expected - all but the child component. When navigating to /result/someId I see the child component but the goBack only refers to the current page instead of the previous one.
constructor(props){
super(props);
this.goBack = this.goBack.bind(this);
}
goBack(){
this.props.history.goBack(); // You're not calling it from history
}
.....
<button onClick={this.goBack}>Go Back</button>
I think that you are using push to navigate to another route. So when you do history.push('/result/someId') you are adding another entry to history stack so goBack will navigate to the previous entry in the stack which is /result. It works as if you were a regular website and clicked a link - you could still go back even if what had changed was some dynamic parameter.
If you don't want to add up to history stack use - history.replace('/result/someId')
See navigating with history.
I figured out my core-problem was that I needed at least one part of the child routes in the parent component. This lead to changing path props also in the parent component when child-routes were changing.
My solution: Store the current location in the constructor of the parent component and pass this as prop to child components to refer back. It works but has the drawback that one can not directly access child component routes because they will not refer back to the right parent path. For my use case this is fine but improvements are welcome.
Parent component
export class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.location = this.props.location.pathname;
}
render() {
return (
{contentId && <MyChildComponent content={contentId} goBack={() =>
history.push(this.location)} />}
)
}

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