Dealing with bloated components in React - reactjs

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;

Related

react : why is axios api call not using proxy in package.json?

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

Extract Data from API and show in another page

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;

Not getting props in react component

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>
)

Why am I getting a blank screen while following React JS?

I am following the current tutorial:
Youtube tutorial at 12:51 mins.
I expect to see bunch of posts on my screen but my screen remains blank.
It appears I have followed everything told in the tutorial.
import React, { Component } from 'react';
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentWillMount() {
fetch('https://jsonplaceholder.typicode.posts')
.then(res => res.json())
.then(data => this.setState({posts: data}))
}
render() {
const postItems = this.state.posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return (
<div>
<h1>Posts</h1>
{ postItems }
</div>
);
}
}
export default Posts;
and
import React, { Component } from 'react';
import './App.css';
import Posts from './components/Posts'
class App extends Component {
render() {
return (
<div className="App">
<Posts />
</div>
);
}
}
export default App;
My browser screen remains blank and I do not see any errors on console.
What am I missing ?
Don't know about the tutorial but it looks outdated...
Here is your App.js (parent component):
import React, { Component } from 'react';
import Posts from './Posts';
export default class App extends Component {
state = { posts: [] };
//fetch the posts and store them in the state
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(posts => this.setState({ posts }))
.catch(error => console.log(error));
}
render() {
return (
<div>
{/* pass the state (posts) as props to Posts */}
<Posts posts={this.state.posts} />
</div>
);
}
}
Here is your Posts.js:
import React from 'react';
// No need for a class based comp
// destructure the props and you have all your data
const Posts = ({ posts }) => (
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<hr />
</div>
))}
</div>
);
export default Posts;
Live Demo: https://jsfiddle.net/ctszdue9/7/
Try putting side effect/ api call inside
componentDidMount() { }
Change URL
https://jsonplaceholder.typicode.posts/
TO
https://jsonplaceholder.typicode.com/posts

When component renders I get 'Cannot read property 'params'

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;

Resources