How to resolve undefined match props in React and React-router - reactjs

When I click to the <Link to={"/weather/" + loc.id} onClick={props.findCurrentWeather}> in SearchBar component. I got the error :
Cannot read property 'params' of undefined
My App.js class component :
class App extends Component {
state = {
locations: undefined,
current: undefined,
...
}
findCurrentWeather = async () => {
let id = this.props.match.params.id; //this is the concerned line. My applicatioon stops here
const data = await fetch(`https://api.weather.com/v1/current.json?key=${api_key}&q=${id}`);
const current_weather = await data.json();
console.log(current_weather);
}
render() {
return (
<BrowserRouter>
<Navbar />
<SearchBar locations={this.state.locations} findCurrentWeather={this.findCurrentWeather} />
<Breadcrumb />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/weather/:id" component={Home} /> //This is the route
<Route path="/about" component={About} />
</Switch>
</BrowserRouter>
)
} // render
} // class
SearchBar functionnal component :
const SearchBar = (props) => {
const searchResult = props.locations ? (
props.locations.map((loc) => {
return (
<li className="search-item" key={loc.id}>
<Link to={"/weather/" + loc.id} onClick={props.findCurrentWeather}> //I call the function from App.js here
<span id="city">{loc.name}</span>
</Link>
</li>
) // return
})
) :
(<li className="search-item">
<Link to={"#"}>
<span>No result</span>
</Link>
</li>);
return (
<form onSubmit={props.searchLocation}>
<input type="text" name="search" id="searchInput" className="search-input"/>
<div className="search-result">
<ul className="search-list" id="searchList">
{searchResult}
</ul>
</div>
</div>
</form>
)
} //fun
export default SearchBar;
I don't know why the props in this.props.match.params.id is undefined

Your component needs the match as a props which you need to pass it. The render prop of Route component takes a function which passes props as the first argumment. The match is one of the props that the Route gives you. For example -
const App = () => (
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/weather/:id" render={ ({match}) => <Home id={match.params.id}/> } /> //This is the route
<Route path="/about" component={About} />
In your example, the SearchBar needs to be inside the Home component since the id param is passed in the route to the Home component.
You may want to check this link for understanding better how the match param works in react-router.

So the thing is your page renders twice first time the id is null while second time it has the value.If you console.log(props.match) it will show you the object.
So to skip this error you can use ternery operator so your id will be equal to a value once it is loaded.Hopefully it helps.Inside findCurrentWeather function use this.
const id = props.match ? props.match.params.id : null
console.log(id);

You need to wrap your component with withRouter to be able to acceess the router from a component not rendered by a Route and move findCurrentWeather inside the SearchBar component.
// Searchbar.js
import { withRouter } from "react-router";
...
export default withRouter(SearchBar);

The problem here is the context of this in function findCurrentWeather. Since it is an arrow function it holds the instance of component where it is defined, not of the calling component. Read more about the difference here.
Now you have two options:
You shift the method definition to SearchBar component.
Make findCurrentWeather method as normal javascript function something like given below.
async function findCurrentWeather () {
let id = this.props.match.params.id; //this is the concerned line. My applicatioon stops here
const data = await fetch(`https://api.weather.com/v1/current.json?key=${api_key}&q=${id}`);
const current_weather = await data.json();
console.log(current_weather);
}

Related

Effecting different component's state in React

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.

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

React Router: Route defined in child component not working

I'm working on a React web application using React router.
The Route objects defined on the main wrapper component are working just fine, but if I try to define a Route on a child component, any link pointing to it won't be able to render the desired component.
Here is a code snippet trying to explain the situation:
class MainWrapper extends Component {
render() {
return (
<div className="App">
<Switch>
<Route exact path="/a" component= {A}/>
</Switch>
</div>
);
}
}
const A = () => {
return (
<div>
<Route exact path="/b" component={B}/>
<Link to="/b"/>
</div>
)
}
const B = () => {
return (<div>HELLO</div>)
}
In my application, the link pointing to "/b" is not rendering the B component, like the component prop weren't passed
Why this won't work?
You are specifying "exact path" in both Routes, so for rendering B your path should be exactly "/b", but when linking to "/b" component A will unmount because for rendering A you must be on exact path "/a". You should change your approach. One would be removing "exact" and including "/a" to your Link:
class MainWrapper extends Component {
render() {
return (
<div className="App">
<Switch>
<Route path="/a" component= {A}/>
</Switch>
</div>
);
}
}
const A = () => {
return (
<div>
<Route path="/b" component={B}/>
<Link to="/a/b"/>
</div>
)
}
const B = () => {
return (<div>HELLO</div>)
}
if B is a child of A, the url should be /a/b instead of /b, so you just need to update the A component with this code
const A = ({match}) => {
return (
<div>
<Route exact path={`${match.url}/b`} component={B}/>
<Link to=to={`${match.url}/b`}/>
</div>
)
};
See the documentation here
Do you have a Router somewhere? Also, you haven't closed your Link tag.
You need to wrap it in a Switch, and you should remove the exact prop from your /b route.
const A = ({match}) => {
return (
<div>
<Switch>
<Route path={`${match.url}/b`} component={B}/>
</Switch>
<Link to="a/b"/>
</div>
)
}

react.js redirect to view

i want redirect to "/user". i write but this not work.
how to correctly redirect to the right page
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
<Redirect to="/user"/>
});
}
My router list. Among them there is a router with the path "/ user"
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
UPADATE
App.js
The button I click on is in the component <SearchForm/>
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
There are two ways to programmatically navigate with React Router - <Redirect /> and history.push. Which you use is mostly up to you and your specific use case.
<Redirect /> should be used in user event -> state change -> re-render order.
The downsides to this approach is that you need to create a new property on the component’s state in order to know when to render the Redirect. That’s valid, but again, that’s pretty much the whole point of React - state changes update the UI.
The real work horse of React Router is the History library. Under the hood it’s what’s keeping track of session history for React Router. When a component is rendered by React Router, that component is passed three different props: location, match, and history. This history prop comes from the History library and has a ton of fancy properties on it related to routing. In this case, the one we’re interested is history.push. What it does is it pushes a new entry onto the history stack - aka redirecting the user to another route.
You need to use this.props.history to manually redirect:
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
this.props.history.push('/user');
});
}
You should be getting history as a prop from your <Router> component.
EDIT:
Okay thank you for the code update. The SearchForm component is not nested under your BrowserRouter, so it is not getting the history prop. Either move that component inside the BrowserRouter or use the withRouter HOC in SearchForm reacttraining.com/react-router/web/api/withRouter
Option 1: Move SearchForm inside the BrowserRouter
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
Option 2: use the withRouter HOC to inject the history prop into SearchForm manually:
import { withRouter } from 'react-router-dom';
class SearchForm extends React.Component { ... }
export default withRouter(SearchForm)

How to hide header component in login page reactJS

Am new to ReactJS. I want to hide header component in Login page and show in inner pages. I have an App.js I have used ternary operator but not working.
class App extends Component {
render(){
let HideHeader = EmployeeLogin ? null : <HeaderNavContainer />
return (
<div>
<Router history={history}>
<div>
{HideHeader}
<Switch>
<Route path="/about" component={About} />
<Route path="/EmployeeLogin" component={EmployeeLogin} />
<Route path="/MyPreferences" component={MyPreferences} />
<Route component={PageNotFound} />
</Switch>
</div>
</Router>
</div>
);
}
}
If EmployeeLogin component is rendered I want to hide header navigation
<HeaderNavContainer /> if not I want to show <HeaderNavContainer />
In the render method of your HeaderNavContainer, you can do this:
render() {
if (window.location.pathname === '/EmployeeLogin') return null;
return <insert your header nav code>;
}
Since HeaderNavContainer is wrapped within <Router>, it'll re-render when window.location.pathname changes.
Alternatively, add HeaderNavContainer to your About, MyPreferences etc instead of putting in App.
In component you can check if the history.location.pathname is equal to /EmployeeLogin and then return null. You can use withReducer for getting history object as a prop.
render(){
if(this.props.history.location.pathname==='/EmployeeLogin'){
return null;
}
return (//your navigation component code.)
}
Instead of checking component exists or not try to check the URL is hit or not
In window.location.pathname you will get the current URL.
let HideHeader = window.location.pathname === 'your need string' ? null :
Create a HideHeader route that renders (conditionally) the Header component and an Outlet component for the nested route components.
import { Outlet, useLocation } from "react-router-dom";
import { Header } from "./Header";
const HideHeader = ({ hideHeaderPaths }) => {
const { pathName } = useLocation();
return (
<>
{!hideHeaderPaths.includes(pathName) && <Header />}
<Outlet />
</>
);
};
export default HideHeader;
In App.js
import your HideHeader.js
import HideHeader from "./common/HideHeader"
...
...
<Route element={<HideHeader hideHeaderPaths={["/login"]} />}></Route>
and import your header on those pages where you want to show
<Header />

Resources