Effecting different component's state in React - reactjs

I have two different components in React "Header" and "Main".
Header:
import React, { Component } from 'react'
import Logo from './HeaderComps/Logo'
import UserHeader from './HeaderComps/UserHeader'
export default class Header extends Component {
render() {
return (
<header>
<Logo />
<UserHeader name ="Boris"/>
</header>
)
}
}
Main:
export default class Main extends Component {
state ={isLogged : false}
handleClientFirstImpact = () =>{
if(this.state.isLogged === false){
return <Switch>
<Route exact path ='/registration' component={Register} />
<Route exact path ='/login' component={Login} />
</Switch>
}
}
render() {
return (
<Router>
<div className="Main">
{this.handleClientFirstImpact()}
</div>
</Router>
)
}
}
In "Main" I have two components "Register" and "Login".
How do I make Login page effect the Header's state? is there a React technology or a way to do that?
I want the "UserHeader" to show the User name but I don't know how to effect it's parent's state.

There might be some common component where you will be calling the Main as well as the Header Component. Suppose that component is App Component.
So inside App you have
render() {
return
<div>
<Header />
<Main />
</div>
}
Now what you can do is keep the userName in this App Component's state and this App Component will pass userName to the Component as :-
<Header userName={userName} />
Also pass a function as a prop to the Main Component which will enable the component to set the State of the Parent Component.
<Main setUserName={(newUserName) => this.setState(newUserName)} />
now this setUserName prop should be passed on to the components which are called via Route inside the Main Component. Keeping your example in mind (use render prop instead of component for Route):
export default class Main extends Component {
state ={isLogged : false}
handleClientFirstImpact = () =>{
const { setUserName } =this.props;
if(this.state.isLogged === false){
return
<Switch>
<Route exact path ='/registration'
render={(props) => <Register {...props} setUserName={setUserName} />}
/>
<Route exact path ='/login'
render={(props) => <Login {...props} setUserName={setUserName} />}
/>
</Switch>
}
}
render() {
return (
<Router>
<div className="Main">
{this.handleClientFirstImpact()}
</div>
</Router>
)
}
}
Now you have passed setUserName as a prop to both login and register and you can use this method to set App component's state which will in turn reflect the changes on the Header component.
Although the solution might work for you. I would advise you to simplify the Application layout. Keep the routing functionality in the main app Component. Use a separate layout component to render similar page layouts. It would avoid confusion in the long run.

Related

How to Pass Props to a Component in React

I have 2 page components with their own routes. There is one header component that is to be used in both pages, and it is supposed to change its content depending on what page you're on.
How can I change the content in the header per page?
return (
<main className={theme.main}>
<Header
title={this.state.headerTitle}
boldTitle={this.state.boldTitle}
/>
<div className={theme.content}>
<Switch>
<Route path={'/page1'} component={Page1} />
<Route path={'/page2'} component={Page2} />
</Switch>
</div>
<Footer/>
</main>
);
I am trying to pass props to the header in my Routes page as in the code below.
<Route path={'/page2'} component={Page2} currentTitle={this.state.headerTitle} />
UPDATE
I got it fixed this using the
import { withRouter } from "react-router"; and
export default withRouter(Header);
in Header Component then he listen the routes and I can put the pathname for to do what I need.
Then, I don't need set up state, just listen the routes inside that MyComponent.
Usign: this.props.location
MOVE YOUR HEADER INSIDE Page1 AND Page2.
<Route path={'/page2'} component={() => <Page2 currentTitle={this.state.headerTitle}
/>} />
Then in header use currentTitle to change title.
Put Header into its own component and import it separately into each page component.
Then you can pass props into the header like this, when you call the Header in each page...
import Header from (wherever that component is)
class Page# extends React.component {
other code
render() {
return (
<div>
<Header currentTitle="this.state.headerTitle" />
<rest of code for this page />
</div>
}
or whatever props you need.
I assume that the code you show lies inside the render() method of some React component, so that you have actually a state.
Note that the component attribute in the Route tag expects a value that is a function without arguments. The component to be rendered is the result of the function. But nothing forbids you to make some work before returning a value. I would try something like this
<Route path={'/page1'} component={() => {
this.setState({headerTitle: ...what you want..., boldTitle: ...what you want...})
return <Page1 />
}}/>
If Page1 is just a function, change the "return" line with return Page1()
Hope it helps - Carlos
you can use the redux library as global state
something like this
class Main extends Component{
render(){
return (
<main className={theme.main}>
<Header
title={this.props.headerTitle}
boldTitle={this.state.boldTitle}
/>
<div className={theme.content}>
<Switch>
<Route path={'/page1'} component={Page1}/>
<Route path={'/page2'}component={Page2}
/>
</Switch>
</div>
<Footer/>
</main>
)
}
const mapStateToProps = state =>{
return { headerTitle: state.headerTitle }
}
export default connect(mapStateToProps)(Main)
Page1
class Page1 extends Component{
changeTitle =()=>{
this.props.actions.changeTitle("title from Page1")
}
render(){
return (
<div>
<button onClick={this.changeTitle}>changeTitle</button>
</div>
)
}
const mapDispatchToProps = (dispatch)=> {
return {
actions: bindActionCreators({ changeTitle }, dispatch)
}
}
export default connect(null, mapDispatchToProps)(Page1)
the code it's not functional but this is an Idea how you can use redux for this purpose

How to change state based on Route change in React-Router?

I'm trying to figure out how to change the state of my Dashboard when a new route is clicked. This new route change needs to update the TopMenu component.
This is the code for the Dashboard
class Dashboard extends Component {
constructor (props) {
super(props)
this.state = {
selectedMenuItem: 'Now'
}
}
render () {
return (
<Router>
<div id='dashboard-container'>
<LeftMenu/>
<div className='column'>
<TopMenu selectedMenuItem={this.state.selectedMenuItem} />
<div id='content-container'>
<div>
<Switch>
<Route exact path='/' component={Now} />
<Route exact path='/surveys' component={Surveys} />
<Route exact path='/my-questions' />
<Route exact path='/feedback' />
<Route exact path='/logout' />
<Route render={function () {
return <p>Not Found</p>
}} />
</Switch>
</div>
</div>
</div>
</div>
</Router>
)}}
This is the code for the TopMenu
class TopMenu extends Component {
render () {
return (
<h3>{this.props.selectedMenuItem}</h3>
)
}
}
How do I listen to a change in React Router such that I can change the 'selectedMenuItem' state variable in the Dashboard and pass that to TopMenu.
Thanks!
Assuming you are using version 4, check the usage of match in React Router 4: https://reacttraining.com/react-router/web/api/match
match will include all the path information you will need to handle route changes. You can access it by calling this.props.match in your top level page component.
I had the same problem and I solved it with withRouter which is build for integrate router with redux, the only thing you need to do is wrap your connect with withRouter. for more information read redux integration document

React Maximum update depth exceeded

I receive an error while running my application in React. There are lots of questions with this error but I do not how to solve it. When I press the link, it directs to Login component. 'http://localhost:3000/login'
This is the error which I got on the site:
'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.'
Here is my LoginPage:
class LoginPage extends React.Component {
constructor(props) {
super(props);
// reset login status
this.props.dispatch(userActions.logout());
this.state = {
username: '',
password: '',
submitted: false
};
}
render() {
return (
<div className="col-md-6 col-md-offset-3">
<h2>Login</h2>
</div>
);
}
}
function mapStateToProps(state) {
return { auth: state.auth };
}
const loginComp = connect(mapStateToProps)(LoginPage);
export { loginComp as LoginPage };
And this is the routing part.
render() {
const { alert } = this.props;
return (
<Router history={history} >
<Switch>
<PrivateRoute path="/" name="Home" component={DefaultLayout} />
<Route exact path="/login" component={LoginPage} />
</Switch>
</Router>
);
}
And that one is DefaultLayout:
import React, { Component } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { Container } from 'reactstrap';
import {
AppAside,
AppBreadcrumb,
AppFooter,
AppHeader,
AppSidebar,
AppSidebarFooter,
AppSidebarForm,
AppSidebarHeader,
AppSidebarMinimizer,
AppSidebarNav,
} from '#coreui/react';
// sidebar nav config
import navigation from '../../_nav';
// routes config
import routes from '../../routes';
import DefaultAside from './DefaultAside';
import DefaultFooter from './DefaultFooter';
import DefaultHeader from './DefaultHeader';
class DefaultLayout extends Component {
render() {
return (
<div className="app">
<AppHeader fixed>
<DefaultHeader />
</AppHeader>
<div className="app-body">
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<AppSidebarNav navConfig={navigation} {...this.props} />
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
<main className="main">
<AppBreadcrumb appRoutes={routes}/>
<Container fluid>
<Switch>
{routes.map((route, idx) => {
return route.component ? (<Route key={idx} path={route.path} exact={route.exact} name={route.name} render={props => (
<route.component {...props} />
)} />)
: (null);
},
)}
<Redirect from="/" to="/dashboard" />
</Switch>
</Container>
</main>
<AppAside fixed hidden>
<DefaultAside />
</AppAside>
</div>
<AppFooter>
<DefaultFooter />
</AppFooter>
</div>
);
}
}
export default DefaultLayout;
The problem could be somewhere else other than this jsx??
You are not going to believe me maybe but I solved the issue by changing the order of Route tags. First one is the tag with path "/login" and it works correctly.
I think its because you are calling this.props.dispatch(userActions.logout());
in your constructor and that is not a good place to update the application state, because that will make the component to re-render before it gets mounted and each time it wants to re-render you are again calling the function in its constructor and setting the state so it gets into a loop where it calls the setState again and again, its better to put this.props.dispatch(userActions.logout()); in a lifeCycle method like componentDidMount(){} so each time your component gets mounted, your logout action gets called and updated the application state.
From my experience, i understood that calling setState inside arrow functions (that is, function written in ES6 format) repeatedly causes this error. Therefore, as much as much possible i started using ES5 format functions only.

React Router Navigation in multi step form

I have a react multi step form with a flux store like this:
// MultiForm.js
....
import LoadFile from './components/LoadFile';
import LoadPeople from './components/LoadPeople';
import Confirmation from './components/Confirmation';
class MultiForm extends Component {
.
.
.
nextPage() {
// Update page number with a flux action
MultiStepActions.nextPage();
}
previousPage() {
// Update page number with a flux action
MultiStepActions.previousPage();
}
render() {
const {pagina} = this.state;
<div className="container">
{page === 1 && <LoadFile
handleChange={this.handleChange}
file={this.state.file}
nextPage={this.nextPage}
data1={this.state.data1}
data2={this.state.data2}
data3={this.state.data3}/>
}
{page === 2 && <LoadPeople
listPeople={this.state.listPeople}
nextPage={this.nextPage}
previousPage={this.previousPage}
handleChangePeople={this.handleChangePeople}/>
}
{page === 3 && <Confirmation
listData={this.state.listData}
nextPage={this.nextPage}
previousPage={this.previousPage}/>
}
</div>
}
}
// Index.js with react-router
import Menu from './components/Menu';
class Index extends Component {
class App extends Component {
render() {
return (
<div>
<Menu />
{this.props.children}
</div>
)
}
}
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/MultiForm" component={MultiForm}>
</Route>
</Route>
</Router>
), document.getElementById('app'));
}
It is a summary of the main component (MultiForm) and a basic react router scenario.
In the component I am using a flux store to set and get the actual number of page of the multi step form and then render the component according to the actual page.
In the components (LoadFile, LoadPeople, Confirmation) I have a button to navigate to the next and previos page (via nextPage and previousPage functions) and all wokr ok.
Now I want achieve the same result using the back and previous buttons of the browser and I suppose with react-router. So, how I must configure react router or what I need to do in order to getting the browser buttons to work equals to my next and previous buttons?
You cannot override a browser's navigation buttons. You can however, hook up the pages states defined in react-router, and as the user browses around using the navigation buttons, your application state remains synced to the browser state.
What i would suggest for you here is to create a parameter for your MultiForm route:
<Route path="/Multiform/:page" component={MultiForm} />
This would then allow you to get that page parameter in this.props.params.page
Even better, you can hook up each of the child components as routes in themselves:
<Route path="/Multiform/" component={MultiForm}>
<Route path="1" component={LoadFile} />
<Route path="2" component={LoadPeople} />
<Route path="3" component={Confirmation} />
</Route>
class Multiform extends Component {
//...
render() {
return (
<div className="container">
{ this.props.children }
</div>
)
}
}

React set state/props on route using react router

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/

Resources