I'm not getting props in my Nav component. Odd thing is, 'this.props.history.push' is working in my other components.
The same function is working in my other components, but when I try to call the push function, I'm getting 'err in logout TypeError: Cannot read property 'push' of undefined'. The 'this.props' object is logging as '{}'.
Any help is appreciated, thank you.
import React from 'react'
import logo from 'logo.png'
import css from './Nav.module.scss'
import { Link } from 'react-router-dom'
import Cookies from 'js-cookie'
import axios from 'axios'
class Nav extends React.Component {
constructor(props) {
super(props)
this.state = {
loggedIn: false
}
console.log(this.props)
}
_handleLogout = () => {
// const self = this
console.log(this.props)
axios.get('http://localhost:8080/logout', {
withCredentials: true
})
.then(res => {
console.log(res)
console.log('logout')
if (Cookies.get('sid') === undefined) {
this.props.history.push('/')
}
console.log(this.props)
})
.catch(err => {
console.log('err in logout', err)
})
}
render() {
return (
<div className={css.nav}>
<div className={css.leftPart}>
<Link to="/">
<div className={css.brandicon}>
<img src={logo} alt="Logo" />
</div>
<div className={css.brandname}>
somebrand
</div>
</Link>
</div>
<div className={css.rightPart}>
{
Cookies.get('sid') === undefined ?
<Link to="/login">
<div className={css.loginButton}>
Login
</div>
</Link>
:
<div className={css.logoutButton} onClick={this._handleLogout}>
Logout
</div>
}
</div>
</div>
)
}
}
export default Nav
My Nav component is only referenced once in my Layout component:
import React from 'react'
import Nav from 'components/Nav/Nav'
import css from './BasicLayout.module.scss'
class Basic extends React.Component {
render() {
return (
<div className={css.page}>
<Nav />
<div className={css.content}>
{this.props.children}
</div>
</div>
)
}
}
export default Basic
history and location are special props injected by React Router's HOC withRouter
import { withRouter } from 'react-router-dom'
class Nav extends React.Component{
render(){
const { history, location } = this.props
return <div>{`I'm at ${location.pathname}`}</div>
}
}
export default withRouter(Nav)
It works for functional components as well
export const Component = withRouter(({ history, location })) =>(
<div>{`I'm at ${location.pathname}`}</div>
)
Related
I have a react app that i've made and it is working great in storybook with a mock for data retrieving.
When I switch to yarn start to check the app without mock, the page is loading some components but not the main component (PostPageCardContainer) which is only displaying "loading" (see the code below).
The component which load properly make api calls like this in ComponentDidMount :
axios.get("/api/blog/categories/").then((res) => {
const categories = res.data.results;
this.setState({
categories,
loading: false });
and
axios.get("/api/blog/tags/").then((res) => {
const tags = res.data.results;
this.setState({
tags,
loading: false });
}); }
The components that dont load make an api call like this in COmponentDidMount:
PostPageCard.js:
const pk = this.props.match.params.id;
axios.get(`/api/cms/pages/${pk}/`).then((res) => {
const post = res.data;
this.setState({
post,
loading: false });
}) }
PostDetail.js
axios.get(`/api/cms/pages/${this.props.postPk}/`).then((res) => {
this.setState({
data: res.data,
loading: false
}); });
In the browser console, when i try to load the page i get :
printWarnings # webpackHotDevClient.js:138
:3000/api/cms/pages/6/:1
Failed to load resource: the server responded with a status of 404 (Not Found)
And when i hover the mouse on the link i get http://localhost:3000/api/cms/pages/6.
In fact the react page is being served on localhost:3000 but I have put "proxy": "http://172.20.128.2:8000" in packages.json so my api call go on this adress.
How come some api calls go on the good adress and others dont?
The issue is similar to this : How to set proxy when using axios to send requests? and this Axios not using proxy setting with https and this axios request ignores my proxy and even when hardcoded I can't fetch any data but there is not really a solution except using fetch or restarting the machine
I ve tried to hardcode the proxy in the api call like axios.get(http://172.20.128.2:8000/api/cms/pages/${this.props.postPk}` and removed the proxy line from package.json but then nothing is loading properly...
Here is some sample of the code:
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./components/App";
import 'bootstrap/dist/css/bootstrap.css';
import { MemoryRouter } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<MemoryRouter initialEntries={["/"]}>
<App/>
</MemoryRouter>
</React.StrictMode>,
document.getElementById("root")
);
App.js
import React from "react";
import { Route, Switch } from "react-router";
import { Container, Row } from "react-bootstrap";
import { BlogPage } from "./BlogPage";
import { PostPage } from "./PostPage";
function App() { return (
<Switch>
<Route path="/post/:id([\d]+)" component={PostPage}/>
<Route path="/tag/:tag/:page([\d]+)?" component={BlogPage}/>
<Route path="/:page([\d]+)?" component={BlogPage}/>
<Route
path="*"
component={() => (
<Container> <Row>
<h1>404</h1> </Row>
</Container> )}
/> </Switch>
); }
export default App;
BlogPage.js
import React from "react";
import { Container, Row } from "react-bootstrap";
import { TopNav } from "./TopNav";
import { Footer } from "./Footer";
import { PostPageCardContainer } from "./PostPageCardContainer";
import { SideBar } from "./SideBar";
class BlogPage extends React.Component { render() {
return (
<div>
<TopNav />
<Container>
<Row>
<PostPageCardContainer {...this.props} />
<SideBar />
</Row>
</Container>
<Footer />
</div> );
} }
export { BlogPage };
Postpagecardcontainer.js
import React from "react";
import axios from "axios";
import { Col } from "react-bootstrap";
import { Link } from "react-router-dom";
import { generatePath } from "react-router";
import _ from 'lodash';
import { PostPageCard } from "./PostPageCard";
class PostPageCardContainer extends React.Component {
constructor(props) {
super(props); this.state = {
posts: [],
pageCount: 0,
pageStep: 2,
};
this.getPosts = this.getPosts.bind(this);
}
componentDidMount() {
this.getPosts();
}
componentDidUpdate(prevProps) {
if (prevProps.location !== this.props.location) {
this.getPosts(); }
}
getCurPage() {
// return the page number from the url
const page = this.props.match.params.page;
return page === undefined ? 1 : parseInt(page);
}
getPrePageUrl() {
const target = _.clone(this.props.match.params);
target.page = this.getCurPage() - 1;
return generatePath(this.props.match.path, target);
}
getNextPageUrl() {
const target = _.clone(this.props.match.params);
target.page = this.getCurPage() + 1;
return generatePath(this.props.match.path, target);
}
getPosts() {
let category = this.props.match.params.category === undefined ? "*" : this.props.match.params.category;
let tag = this.props.match.params.tag === undefined ? "*" : this.props.match.params.tag;
let offset = (this.getCurPage() - 1) * this.state.pageStep;
const url = `/api/blog/posts/?limit=${this.state.pageStep}&offset=${offset}&category=${category}&tag=${tag}`;
axios.get( url).then((res) => {
const posts = res.data.results;
this.setState({
posts,
pageCount: Math.ceil(parseInt(res.data.count) / this.state.pageStep),
});
});
}
render() {
return (
<Col md={8}> {this.state.posts.map((post) => (
<PostPageCard postPk={post.id} key={post.id} /> ))}
<nav aria-label="Page navigation example">
<ul className="pagination">
<li className={
this.getCurPage() <= 1 ? "page-item disabled" : "page-item" }>
<Link to={this.getPrePageUrl()}
className="page-link" >
Previous
</Link>
</li>
<li className={this.getCurPage() >= this.state.pageCount ? "page-item disabled" : "page-item" }>
<Link to={this.getNextPageUrl()}
className="page-link" >
Next
</Link>
</li>
</ul>
</nav>
</Col>
);
}
}
export { PostPageCardContainer };
PostPage.js
import React from "react";
import { Container, Row } from "react-bootstrap";
import { TopNav } from "./TopNav";
import { Footer } from "./Footer";
import { SideBar } from "./SideBar";
import { PostDetail } from "./PostDetail";
class PostPage extends React.Component { render() {
return ( <div>
<TopNav/> <Container>
<Row>
<PostDetail {...this.props} /> <SideBar/>
</Row> </Container> <Footer/>
</div> );
} }
export { PostPage };
PostDetail.js
import React from "react";
import axios from "axios";
import { StreamField } from "./StreamField/StreamField";
class PostDetail extends React.Component {
constructor(props) {
super(props); this.state = {
post: [],
loading: true, };
}
componentDidMount() {
const pk = this.props.match.params.id;
axios.get(`/api/cms/pages/${pk}/`).then((res) => {
const post = res.data;
this.setState({
post,
loading: false });
}) }
render() {
if (!this.state.loading) {
const post = this.state.post;
return (
<div className="col-md-8">
<img src={post.header_image_url.url} className="img-fluid rounded" alt=""/>
<hr />
<h1>{post.title}</h1>
<hr />
<StreamField value={post.body} />
</div> );
}
else {
return <div className="col-md-8">Loading...</div>;
}
}
}
export { PostDetail };
PostPageCard.js
import React from "react";
import { Link } from "react-router-dom";
import axios from "axios";
class PostPageCard extends React.Component {
constructor(props) {
super(props); this.state = {
data: null,
loading: true,
};
}
componentDidMount() {
axios.get(`/api/cms/pages/${this.props.postPk}/`).then((res) => {
this.setState({
data: res.data,
loading: false
}); });
}
renderPost(data) {
const dateStr = new Date(data.pub_date).toLocaleString();
return (
<div className="card mb-4">
<Link to={`/post/${data.id}`}> <img src={data.header_image_url.url} className="card-img-top" alt=""/> </Link>
<div className="card-body">
<h2 className="card-title">
<Link to={`/post/${data.id}`}>{data.title}</Link>
</h2>
<p className="card-text">{data.excerpt}</p>
<Link to={`/post/${data.id}`} className="btn btn-primary">Read More → </Link>
</div>
<div className="card-footer text-muted">Posted on {dateStr}
</div>
</div>
); }
render() {
if (this.state.loading) {
return 'Loading...'; }
else{
return this.renderPost(this.state.data); }
} }
export { PostPageCard };
I have a component where a list of pictures is rendered and it works perfectly fine :
import { Component} from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
class Photos1930 extends Component {
render() {
return (
<div>
<Header />
<h2 className='titre bloc'>Photos 1930</h2>
<div className='bloc bloc__photo'>
{PhotoItems.map((val, key) => {
let id = val.id
let url = val.url
let lienImage = "/galerie/:" + (val.id)
return <div key={id}>
<a href={lienImage}>
<img className='photo' alt='Photo Charles-Quint' src={url}></img>
</a>
</div>
})}
</div>
<Footer />
</div>
)
}
}
export default Photos1930
I want to create an other component where i can load a specific picture when user click on a picture from the precedent list. I use the same logic but for some reason the picture doesn't load. I don't have any errors in my console but on my page i just have the standard icon for image with my alt.
All the pictures are on public folder.
I just don't understand why is it working on one component but not on the other one.
import { Component } from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
const url = window.location.pathname
const justId = parseInt((url.split(':')[1]))
function specificId(photo) {
return photo.id === (justId)
}
let justUrl = (PhotoItems.find(specificId).url)
console.log(justUrl)
class PickPhoto extends Component {
render() {
return (
<div>
<Header />
<div>
<h1>{justId}</h1>
<img className="bigPhoto" alt="Charles-Quint" src={justUrl}></img>
</div>
<Footer />
</div>
)
}
}
export default PickPhoto
EDIT1 : Here's my github repo : https://github.com/FranMori/CharlesQuint
and here's my netlify link : https://stoic-bohr-810e13.netlify.app/
You can click on "Galerie Photos" and then click on any picture to see the problem.
in your repo, this.justUrl is undefined. You need to add justUrl in the component's state and update it dynamically inside componentDidMount like below. I also added a / in src={/${this.state.justUrl}}
import { Component } from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
class PickPhoto extends Component {
constructor() {
super()
this.state = { justUrl: "" };
}
componentDidMount() {
const url = window.location.pathname
const justId = parseInt((url.split(':')[1]))
function specificId(photo) {
return photo.id === justId
}
let justUrl = (PhotoItems.find(specificId).url)
console.log(justUrl)
this.setState({justUrl})
}
render() {
return (
<div>
<Header />
<div>
<h1>{this.justId}</h1>
<img className="bigPhoto" alt="Charles-Quint" src={`/${this.state.justUrl}`}></img>
</div>
<Footer />
</div>
)
}
}
export default PickPhoto
This question may sound silly to some people, but I am really confused on how to do it
I have 3 file: App.js, HomePage.js and Profile.js
App.js :
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/HomePage";
import Profile from "./components/Profile"
function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/profile/:profileId" component= {Profile} />
</Switch>
</Router>
);
}
export default App;
From here, the default page it will go to is HomePage.js
HomePage.js:
import React, { Component } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
class HomePage extends Component {
constructor() {
super();
this.state = {
userData: [],
}
}
componentDidMount() {
axios.get("XXXXXXXX").then((response) => {
const userDataList = response.data.users;
this.setState({
userData: userDataList
})
})
}
render() {
const userGrid = this.state.userData.map((user, index) => {
return (
<div key={index}>
<Link to={`/profile/${user.id}`}>
<img src={user.profilepicture} />
<p>{user.name}</p>
</Link>
</div>
)
})
return (
<div className="App">
<div className="card">
<div className="card__top">
<span className="card__title">
<p>Select An Account</p>
</span>
</div>
<div className="card__bottom">
<div className="card__table">
{userGrid}
</div>
</div>
</div>
</div>
)
}
}
export default HomePage;
In HomePage.js, I am able to show the profile picture and name of the user from API.
In the next page which is Profile.js , I am able to print the ID of the user.
Profile.js:
import React, { Component } from "react";
class Profile extends Component{
componentDidMount(){
const uid = this.props.match.params.profileId;
}
render() {
console.log(this.props.match);
return(
<h1>{this.props.match.params.profileId}</h1>
)
}
}
export default Profile;
As you can see I am printing the ID of user.
Here I also want to show the Profile Picture of the user which I selected in HomePage.js
This I am not able to do it.
JSON file:
{ - users: [-{id:1, name:"abc", profilepicture: "xxxxx.jpeg"}, ]}
You need to store a global state in your applicattion, which you can access from every connected component. This is a more complex topic. redux is a good framework to handle your global state changes.
Here is a tutorial: https://appdividend.com/2018/06/14/how-to-connect-react-and-redux-with-example/
I found it pretty hard to learn redux, but in the end it takes away a lot of pain. Because this is a problem you gonna have in every app you build with react.
You need use Context API o redux
Example context API: https://ibaslogic.com/react-context-api/
Context's well to little projects, but Redux performs better.
App.js
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/HomePage";
import Profile from "./components/Profile"
import { UsersProvider } from "./UsersProvider.js";
function App() {
return (
<Router>
<UsersProvider>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/profile/:profileId" component= {Profile} />
</Switch>
</UsersProvider>
</Router>
);
}
export default App;
UsersContext.js
import React, { Component } from "react"
const UsersContext = React.createContext();
const UsersProvider = UsersContext.Provider;
const UsersConsumer = TodosContext.Consumer;
class MyContext extends Component {
state = {
value: null,
};
setValue = (value) => {
this.setState({ value });
};
render() {
return (
<UsersProvider value={{ setValue, value }}>{this.props.children}
</UsersProvider>
)
}
}
export { UsersContext, UsersProvider, UsersConsumer }
HomePage.js
import React, { Component } from "react";
import axios from 'axios';
class HomePage extends Component {
componentDidMount() {
axios.get("XXXXXXXX").then((response) => {
const userDataList = response.data.users;
// updating your context
this.props.context.setValue(userDataList);
})
}
render() {
const userGrid = this.props.context.value.map((user, index) => {
return (
<div key={index}>
<Link to={`/profile/${user.id}`}>
<img src={user.profilepicture} />
<p>{user.name}</p>
</Link>
</div>
)
})
return (
<div className="App">
<div className="card">
<div className="card__top">
<span className="card__title">
<p>Select An Account</p>
</span>
</div>
<div className="card__bottom">
<div className="card__table">
{userGrid}
</div>
</div>
</div>
</div>
)
}
}
export default HomePage;
Profile.js
import React, { Component } from "react";
import { UsersConsumer } from "./UsersContext.js";
class Profile extends Component{
render() {
return(
<UsersConsumer>
{users => (
<h1>{users.value.find(user => user.id === this.props.match.params.profileId)}</h1>
)}
</UsersConsumer>
)
}
}
export default Profile;
Can someone provide a clean example, or article dealing with bloated React code?
I have a HOC function on the main App.js file but I don't know how to "redeclare" the extended component to export. Then, the other components are not sharing the render. I already have Redux on one of the components called Counter. That is working.
App.js
import { connect } from 'react-redux'
import Counter from './components/Counter';
import Actions from './components/Actions';
function mapStateToProps(state) {
return {
countValue: state.count
};
}
//Action
var increaseAction = { type:"increase" }
var decreaseAction = { type:"decrease" }
// Map Redux actions to component props.
function mapDispachToProps(dispach) {
return {
// etc..
}
// The HOC
var connectedComponent = connect (
mapStateToProps,
mapDispachToProps
)(Counter);
export default connectedComponent;
Actions.js:
import React, {Component} from 'react';
import App from '../App';
import Counter from './Components/Counter';
import './App.css';
class Actions extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
componentDidMount() {
fetch(
"/posts"
)
.then(response => response.json())
.then(data => this.setState({ response: data }))
}
render() {
return (
<div className="App">
<header className="App-header">
{/* <img src={logo} className="App-logo" alt="logo" /> */}
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <div>
<ul>
<App>
`<Counter>`
<li>{resIndex.title} </li> // <- is this not bloated?
<li>{resIndex.author} </li>
</Counter>
</App>
</ul>
</div>
)}
</header>
</div>
)
}
}
export default Actions;
Im getting a big headache.. I dont know what Im doing wrong here. When my Podcast.js component renders, I get 'Cannot read property 'params' of undefined... '
Someone that can point me in the right direction?
This is the parent component of Podcast:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import NavLinks from './components/NavLinks';
import Home from './components/Home';
import Podcast from './components/Podcast';
import './App.css';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<NavLinks />
<Route exact path='/' component={Home} />
<Route path='/podcast/:podID' component={Podcast} />
</div>
</Router>
);
}
}
export default App;
This is my main Component (Podcast):
import React, { Component } from 'react';
import PodcastList from './PodcastList';
class Podcast extends Component {
constructor(props) {
super(props);
this.state = {
podcast: []
};
}
// Fetches podID from props.match
fetchPodcast () {
const podID = this.props.match.params.podID
fetch(`https://itunes.apple.com/search?term=podcast&country=${podID}&media=podcast&entity=podcast&limit=20`)
.then(response => response.json())
.then(data => this.setState({ podcast: data.results }));
}
componentDidMount () {
this.fetchPodcast()
}
// Check if new props is not the same as prevProps
componentDidUpdate (prevProps) {
// respond to parameter change
let oldId = prevProps.match.params.podID
let newId = this.props.match.params.podID
if (newId !== oldId)
this.fetchPodcast()
}
render() {
return (
<div>
<PodcastList />
</div>
)
}
}
export default Podcast;
This is the component thats list's all podcasts:
import React, { Component } from 'react';
class PodcastList extends Component {
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast =>
<li key={podcast.collectionId}>
<a
href={podcast.collectionId}>
{podcast.collectionName}</a>
</li>
)}
</ul>
</div>
)
}
}
export default PodcastList;
Where does the error comes from? Podcast or PodcastList ? Maybe because you're not passing the props down to PodcastList ?
Try:
<PodcastList {...this.props} {...this.state} />
Also, in the child component (PodcastList) use this.props and not this.state
I guess you are using react-router. To have match prop of the React Router you have to decorate it by withRouter decorator of the module
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class PodcastList extends Component {
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast =>
<li key={podcast.collectionId}>
<a
href={podcast.collectionId}>
{podcast.collectionName}</a>
</li>
)}
</ul>
</div>
)
}
}
export default withRouter(PodcastList);
UPDATE:
One of the ways how to handle podcast prop in the PodcastList. The solutions fits all React recommendations and best practices.
import React, { PureComponent } from 'react';
import PodcastItem from './PodcastItem';
class Podcast extends PureComponent { // PureComponent is preferred here instead of Component
constructor(props) {
super(props);
this.state = {
podcast: []
};
}
// Fetches podID from props.match
fetchPodcast () {
const podID = this.props.match.params.podID
fetch(`https://itunes.apple.com/search?term=podcast&country=${podID}&media=podcast&entity=podcast&limit=20`)
.then(response => response.json())
.then(data => this.setState({ podcast: data.results }));
}
componentDidMount () {
this.fetchPodcast()
}
// Check if new props is not the same as prevProps
componentDidUpdate (prevProps) {
// respond to parameter change
let oldId = prevProps.match.params.podID
let newId = this.props.match.params.podID
if (newId !== oldId)
this.fetchPodcast()
}
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast => (
<PodcastItem key={podcast.collectionId}> podcast={podcast} />
))}
</ul>
</div>
)
}
}
export default Podcast;
import React from 'react';
// Here Stateless function is enough
const PodcastItem = ({ podcast }) => (
<li key={podcast.collectionId}>
<a href={podcast.collectionId}>{podcast.collectionName}</a>
</li>
);
export default PodcastItem;