React Unmounted Component State Update Error - javascript

I have the following components in react:
PublicProfile.js:
import React, { Component } from 'react';
import axios from 'axios'
import Post from '../posts/Post'
import Navbar from '../Navbar'
import FollowButton from './FollowButton'
import { Avatar, Button, CircularProgress } from '#material-ui/core'
class PublicProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: {},
followers: undefined,
following: undefined,
posts: [],
showFollowers: false,
showFollows: false,
curr_id: null
}
this.handleFollowerClick = this.handleFollowerClick.bind(this)
this.handleFollowClick = this.handleFollowClick.bind(this)
}
componentDidMount() {
const { user_id } = this.props.match.params
axios.get(`http://127.0.0.1:8000/users/${user_id}`)
.then(res =>
this.setState({
user: res.data,
followers: res.data.followers.length,
following: res.data.following.length
}))
.catch(err => console.log(err))
axios.get(`http://127.0.0.1:8000/posts/user/${user_id}`)
.then(res => {
this.setState({ posts: res.data })
})
.catch(err => console.log(err))
axios.get('http://127.0.0.1:8000/users/self')
.then(res => this.setState({curr_id: res.data.id}))
.catch(err => console.log(err))
}
handleFollowerClick(e) {
e.preventDefault()
if (this.state.showFollowers === true) {
this.setState({showFollowers: false})
} else {
this.setState({showFollowers: true})
}
}
handleFollowClick(e) {
e.preventDefault()
if (this.state.showFollows === true) {
this.setState({showFollows: false})
} else {
this.setState({showFollows: true})
}
}
render() {
const showFollowers = this.state.showFollowers
const showFollows = this.state.showFollows
let followers
let follows
let edit
let fbutton
if (showFollowers === true) {
followers = (
<div>
<p>Followed by:</p>
<ul>
{this.state.user.followers.map(follower => (
<li key={follower.id}><a href={`/users/${follower.user.id}`}>{follower.user.username}</a></li>
))}
</ul>
</div>
)
}
if (showFollows === true) {
follows = (
<div>
<p>Follows:</p>
<ul>
{this.state.user.following.map(follow => (
<li key={follow.id}><a href={`/users/${follow.user.id}`}>{follow.user.username}</a></li>
))}
</ul>
</div>
)
}
if (this.state.user.id === this.state.curr_id) {
edit = <Button href='/profile'>Edit My Profile</Button>
}
if (this.state.user.id !== this.state.curr_id) {
fbutton = <FollowButton user={this.state.user} followers_num={this.state.followers} setParentState={state => this.setState(state)} />
}
if (this.state.user.id !== undefined) {
return (
<div style={{background: '#f7f4e9'}}>
<Navbar />
<div style={{height: '70px'}}></div>
<div>
<Avatar style={{width: 75, height: 75}} variant='rounded' src={this.state.user.pp} alt={this.state.user.username} />
<h1>{this.state.user.username}</h1>
<h3>#{this.state.user.username}</h3>
<h4>{this.state.posts.length} Post(s)</h4>
<p>{this.state.user.bio}</p>
<Button style={{marginLeft: '10px'}} disabled={!this.state.following} onClick={this.handleFollowClick}>{this.state.following} Follows</Button>
<Button disabled={!this.state.followers} onClick={this.handleFollowerClick}>{this.state.followers} Followers</Button>
{followers}
{follows}
</div>
{edit}
{fbutton}
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column'
}}>
{this.state.posts.map(post => (
<Post key={post.id} post={post} />
))}
</div>
<div style={{height: '15px'}}></div>
</div>
)
} else {
return (
<div>
<Navbar />
<CircularProgress />
</div>
)
}
}
}
export default PublicProfile
FollowButton.js:
import React, { Component } from 'react';
import axios from 'axios'
import { Button } from '#material-ui/core'
class FollowButton extends Component {
constructor(props) {
super(props);
this.state = {
followsUser: null
}
this.unfollowClick = this.unfollowClick.bind(this)
this.followClick = this.followClick.bind(this)
}
componentDidMount() {
if (this.props.user.id !== undefined) {
axios.get(`http://127.0.0.1:8000/users/check/${this.props.user.id}`)
.then(res => {
this.setState({ followsUser: res.data.follows })
})
.catch(err => console.log(err))
}
}
unfollowClick() {
axios.delete(`http://127.0.0.1:8000/users/${this.props.user.id}/unfollow/`)
.then(() => {
this.setState({ followsUser: false })
this.props.setParentState({followers: this.props.followers_num - 1})
})
.catch(err => console.log(err))
}
followClick() {
axios.post(`http://127.0.0.1:8000/users/${this.props.user.id}/follow/`)
.then(res => {
this.setState({ followsUser: true })
this.props.setParentState({followers: this.props.followers_num + 1})
})
.catch(err => console.log(err))
}
// user: {
// followers: [...this.props.user.followers, res.data.user]
// }
render() {
let button
if (this.state.followsUser) {
button = <Button style={{background: 'blue'}} onClick={this.unfollowClick}>Following</Button>
} else {
button = <Button onClick={this.followClick}>Follow</Button>
}
return (
<div style={{marginTop: '20px'}}>
{button}
</div>
);
}
}
But I get the following error:
index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in FollowButton (at PublicProfile.js:93)
I have found that this error is largely due to unresolved process when unmounting the component, but I am not even rendering the component in this case due to the conditional, but I still seem to get the error. Can someone please help me fix it.

You are not cancelling axios request when component unmounted. Axios accepts cancelToken as a parameter, you should create a CancelTokenSource which provides a method cancel and then cancel the Source when component unmounts which cancels all pending request.

Here's the syntax of how to use async/await:
const unfollowClick = async() => {
try{
const res = await axios.delete(`http://127.0.0.1:8000/users/${this.props.user.id}/unfollow/`);
this.setState({ followsUser: false });
this.props.setParentState({followers: this.props.followers_num - 1});
}
catch(err) { console.log(err)}
}

Related

Why is one of my child components able to get handleClick() but the others are not?

PROBLEM
I am swapping components based on state in Dashboard(parent) component and passing props to all of them. When I log in using wholesaler the app is running without problems but when i log in with retailer account the app return
TypeError: this.props.handleClick is not a function
when i click on a button-handleClick() -> switch components through handleClick which changes state
My components are almost identical and i have no idea where this is coming from.
Thank you in advance! :)
Files:
Dashboard.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { Retailers } from './_components/Retailers'
import { Locations } from './_components/Locations'
import { Products } from './_components/Products'
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
chosenRetailerId: null,
chosenLocationId: null,
mountComponent: ''
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
switch (this.props.user.type) {
case 'wholesaler':
this.setState({ mountComponent: "retailers" })
break;
case 'retailer':
this.setState({ mountComponent: "locations" })
break;
case 'location':
this.setState({ mountComponent: "products" })
break;
default:
break;
}
}
handleClick(id, shouldMountComponent) {
switch (shouldMountComponent) {
case 'locations':
this.setState({
mountComponent: shouldMountComponent,
chosenRetailerId: id
})
break;
case 'products':
this.setState({
mountComponent: shouldMountComponent,
chosenLocationId: id
})
break;
default:
break;
}
}
render() {
const { user } = this.props
const { chosenLocationId, chosenRetailerId, mountComponent } = this.state
return (
<div className="dashboard">
{user.type === 'wholesaler' &&
<div className="wholesaler">
<h1>Wholesaler</h1>
<h3>{user._id}</h3>
{this.state.mountComponent === 'retailers' &&
<Retailers mountComponent={mountComponent} handleClick={this.handleClick} />
}
{this.state.mountComponent === 'locations' &&
<Locations retailerId={chosenRetailerId} locationId={chosenLocationId} mountComponent={mountComponent} handleClick={this.handleClick} />
}
{this.state.mountedComponent === 'products' &&
<Products locationId={chosenLocationId} mountComponent={mountComponent}/>
}
</div>
}
{user.type === 'retailer' &&
<div className="retailers">
<h1>Retailer {user._id}</h1>
<Locations locationId={chosenLocationId}/>
</div>
}
{user.type === 'location' &&
<div className="locations">
<h1>Location {user._id}</h1>
<Products />
</div>
}
<p>You're logged in with React & JWT!!</p>
<p>
<Link to="/login">Logout</Link>
</p>
</div>
)
}
}
function mapStateToProps(state) {
const { authentication } = state
const { user } = authentication
return {
user
}
}
const connectedDashboard = connect(mapStateToProps)(Dashboard)
export { connectedDashboard as Dashboard }
Retailers.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Retailers extends Component {
state = {
retailers: []
}
componentDidMount() {
const { user } = this.props
const requestOptions = {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + user.token }
}
fetch(`http://localhost:4000/retailers/by-wholesaler/${user._id}`, requestOptions)
.then(result => result.json())
.then(result => {
this.setState({
retailers: result
})
})
}
render() {
const { retailers } = this.state
return (
<div className="retailers">
{retailers.map((retailer, index) =>
<button key={index} onClick={() => this.props.handleClick(retailer._id, 'locations')}>{retailer.name}</button>
)}
</div>
)
}
}
function mapStateToProps(state) {
const { authentication } = state
const { user } = authentication
return { user }
}
const connectedRetailers = connect(mapStateToProps)(Retailers)
export { connectedRetailers as Retailers }
Locations.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Locations extends Component {
state = {
locations: []
}
componentDidMount() {
const { user } = this.props
const requestOptions = {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + user.token }
}
const retailerId = (user.type === 'retailer') ? user._id : this.props.retailerId
console.log(retailerId)
fetch(`http://localhost:4000/locations/by-retailer/${retailerId}`, requestOptions)
.then(result => result.json())
.then(result => {
this.setState({
locations: result
})
})
}
render() {
const { locations } = this.state
return (
<div className="locations">
{locations.map((location, index) =>
<button key={index} onClick={() => this.props.handleClick(location._id, 'products')}>{location.name}</button>
)}
</div>
)
}
}
function mapStateToProps(state) {
const { authentication } = state
const { user } = authentication
return { user }
}
const connectedLocations = connect(mapStateToProps)(Locations)
export { connectedLocations as Locations }
You have to pass handleCLick to location as a prop. You do that in you wholesaler case (passing it to the retailer component), but not when using the Locations component
You didn't pass handleClick as a prop :)
<Retailers handleClick={this.handleClick} />
<Locations handleClick={this.handleClick} />
So prop is undefined and you can't call it as a function.
Check the locations.map function in your Locations.js.
Your are passing a so called thisArg to the map function, so it is no longer using the right context.
This should work:
<div className="locations">
{locations.map((location, index) =>
<button key={index} onClick={() =>
this.props.handleClick(location._id, 'products')}>
{location.name}
</button>
)}
</div>
Also, think about using the uuid package for your iteration keys. Now you are using an index and that will not be unique if you do so in another iteration too (not the case yet so).

React Expected an assignment or function call and instead saw an expression

I'm trying to render the data from my database get this instead Failed to compile.
./src/components/list-pets.component.js
Line 38:5: Expected an assignment or function call and instead saw an expression no-unused-expressions
Search for the keywords to learn more about each error.enter code here
Here is my code from the trouble component
import React, { Component } from 'react';
import axios from 'axios';
export default class ListPets extends Component {
constructor(props) {
super(props);
this.state = {
pets: []
};
}
componentDidMount = () => {
this.getPets();
};
getPets = () => {
axios.get('http://localhost:5000/pets')
.then((response) => {
const data = response.data;
this.setState({ pets: data });
console.log('Data has been received!');
})
.catch((err) => {
console.log(err);
});
}
displayPet = (pets) => {
if (!pets.length) return null;
return pets.map((pet, index) => {
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
});
};
render() {
console.log('State: ', this.state);
return (
<div className='adopt'>
{this.displayPet(this.state.pets)}
</div>
)
}
}
You need to return a value at each pets.map iteration, currently you’re returning undefined.
return pets.map((pet, index) => {
return (
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
)
});
You have to wait until fetching data is completed.
You should have to define the loading bar while fetching.
class App extends Component {
constructor() {
super();
this.state = {
pageData: {},
loading: true
}
this.getData();
}
async getData(){
const res = await fetch('/pageData.json');
const data = await res.json();
return this.setState({
pageData: data,
loading: false
});
}
componentDidMount() {
this.getData();
}
render() {
const { loading, pageData } = this.state;
if (loading){
return <LoadingBar />
}
return (
<div className="App">
<Navbar />
</div>
);
}
}

React Infinite scrolling function by online API

I'm using YTS API and I would like to make Infinite scrolling function.
There is a page parameter and limit parameter. It seems it can work with them but I have no idea of how to use it. I'm a beginner user of React. Could you guys help me? Thanks in advance.
fetch('https://yts.am/api/v2/list_movies.json?sort_by=download_count&limit=20')
fetch('https://yts.am/api/v2/list_movies.json?sort_by=download_count&page=2')
This is the link of YTS API https://yts.am/api#list_movies
I would try using React-Waypoint and dispatch an action to fetch the data every time it enters the screen.
The best way IMO is using redux but here's an example without:
state = { currentPage: 0, data: [] };
getNextPage = () => {
fetch(`https://yts.am/api/v2/list_movies.json?sort_by=download_count&page=${this.state.currentPage}`).
then((res) => this.setState((prevState) => ({currentPage: prevState.currentPage + 1, data: res.body}));
}
render(){
<div>
{
this.state.data.map((currentData) => <div>{currentData}</div>)
}
<Waypoint onEnter={this.getNextPage}/>
</div>
}
I would like to show {this._renderList() } infinitely
import React, {Component} from 'react';
import L_MovieList from './L_MovieList';
import L_Ranking from './L_Ranking';
import './L_Right_List.css';
import Waypoint from 'react-waypoint';
class L_BoxOffice extends Component {
state = {
currentPage: 0,
data : []
};
constructor(props) {
super(props);
this.state = {
movies: []
}
this._renderRankings = this._renderRankings.bind(this);
this._renderList = this._renderList.bind(this);
}
componentWillMount() {
this._getMovies();
}
_renderRankings = () => {
const movies = this.state.movies.map((movie, i) => {
console.log(movie)
return <L_Ranking title={movie.title_english} key={movie.id} genres={movie.genres} index={i}/>
})
return movies
}
_renderList = () => {
fetch(`https://yts.am/api/v2/list_movies.json?sort_by=download_count&page=${this.state.currentPage}`)
.then((res) => this.setState((prevState) => ({currentPage: prevState.currentPage + 1, data: res.body}));
const movies = this.state.movies.map((movie) => {
console.log(movie)
return <L_MovieList title={movie.title_english} poster={movie.medium_cover_image} key={movie.id} genres={movie.genres} language={movie.language} runtime={movie.runtime} year={movie.year} rating={movie.rating} likes={movie.likes} trcode={movie.yt_trailer_code}/>
})
return movies
}
_getMovies = async () => {
const movies = await this._callApi()
this.setState({
movies
})
}
_callApi = () => {
return fetch('https://yts.am/api/v2/list_movies.json?sort_by=download_count&limit=10').then(potato => potato.json())
.then(json => json.data.movies)
.catch(err => console.log(err))
}
getNextPage = () => {
fetch(`https://yts.am/api/v2/list_movies.json?sort_by=download_count&page=${this.state.currentPage}`).
then((res) => this.setState((prevState) => ({currentPage: prevState.currentPage + 1, data: res.body}));
}
render() {
const {movies} = this.state;
let sub_title;
let right_information;
if (this.props.page == 'main') {
sub_title = <div>Today Box Office</div>;
right_information = <div>
aaa</div>;
} else if (this.props.page == 'box_office') {
right_information = <div className={movies
? "L_Right_List"
: "L_Right_List--loading"}>
{this._renderList()}
{
this.state.data.map((currentData) => <div>{this._renderList()}</div>)
}
<Waypoint onEnter={this.getNextPage}/>
</div>;
}
return (<div style={{
backgroundColor: '#E5E5E5',
paddingTop: '20px',
paddingLeft: '20px'
}}>
{sub_title}
<div className={movies
? "L_Left_Ranking"
: "L_Left_Ranking--loading"}>
<div className="L_Left_Ranking_title">영화랭킹</div>
{this._renderRankings()}
</div>
{right_information}
</div>);
}
}
export default L_BoxOffice;

how to setState in componentDidMount in promise without getting setState error

I'm calling an api and setting the state with the response.
I'm not able to call setState without seeing this error:
The error does not occur if setState happens outside the promise.
How do you set an API response to state without seeing this error?
Can't call setState (or forceUpdate) on an unmounted component
componentDidMount() {
const url = 'https://localhost:8000/api/items'
let items;
fetch(url, {mode: 'cors'})
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
Entire component for reference:
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import GridList from '#material-ui/core/GridList';
import GridListTile from '#material-ui/core/GridListTile';
import GridListTileBar from '#material-ui/core/GridListTileBar';
import ListSubheader from '#material-ui/core/ListSubheader';
import IconButton from '#material-ui/core/IconButton';
import InfoIcon from '#material-ui/icons/Info';
const styles = theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
backgroundColor: theme.palette.background.paper,
},
icon: {
color: 'rgba(255, 255, 255, 0.54)',
},
});
class ItemList extends Component {
constructor(props) {
super(props)
this.state = {
items: [],
isLoaded: false,
}
this.handleItemClick = this.handleItemClick.bind(this);
}
componentDidMount() {
const url = 'https://localhost:8000/api/items'
let items;
fetch(url, {mode: 'cors'})
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
handleItemClick(id) {
}
handleRenderItems() {
const { classes } = this.props
const { items } = this.state;
return this.state.items.map((item, idx) => {
const id = item.id;
return (
<GridListTile onClick={() => this.handleItemClick(id)} key={idx}>
<img src={item.key_image} alt={item.title} />
<GridListTileBar
title={item.title}
subtitle={<span>${item.rent_price_day}/day</span>}
actionIcon={
<IconButton className={classes.icon}>
<InfoIcon />
</IconButton>
}
/>
</GridListTile>
)
})
}
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<GridList cols={3} cellHeight={220} className={classes.gridList}>
<GridListTile key="Subheader" cols={3} style={{ height: 'auto' }}>
<ListSubheader component="div">Popular Items</ListSubheader>
</GridListTile>
{this.handleRenderItems()}
</GridList>
</div>
);
}
}
ItemList.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ItemList);
Instead of handling this in the component - I think it may be simpler to handle the call to the API in an action creator, and use redux to store the data - then just send it to the component when it's available. Better to keep these details out of the view anyway.
Also, wouldn't typically use a style object in the view either - but it's boilerplate with material-ui.
Add below inside your component,
componentDidMount() {
this.isMounted = true;
}
componentWillUnmount() {
this.isMounted = false;
}
Then setState only if this.isMounted is true. This should solve your problem, but this is not a recommended pattern. Please refer https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
Check the code of parent component which holds the <ItemList/>. If you are rendering <ItemList/> based on certain conditions, the problem might be in those conditions.
It simple. You just have to return the value of the second promise in a variable.
Like this :
let items;
let errorFetch;
fetch(url, {mode: 'cors'})
.then(res => res.json())
.then(
(result) => {
items = result;
},
(error) => {
errorFetch = error
}
);
this.setState(items ? { isLoaded : true, items } : { isLoaded : true, error: errorFetch });

React component not rendering without an event

I'm learning React and is doing a personal project where i'm creating a chatbot with api.ai agent. I'm using api.ai npm package in my project where the user can ask a question and my agent will reply to the answer based on the question.I'm getting the response correctly from the agent, but the response is not rendered in the page until there is keypress event.
Below is my code
import React, {
Component
} from 'react';
import ChatMessageComposer from
'../ChatMessageComposer/ChatMessageComposer';
import ChatHistory from '../ChatSection/ChatHistory/ChatHistory';
import apiai from 'apiai-promise';
class Chat extends Component {
state = {
messages: [], //[{from: 'bot', message: 'Hi'}]
inputValue: ''
}
atKeyPress = (event) => {
if (event.key !== 'Enter') {
return;
}
this.setState((prevState) => {
prevState.messages.push({
message: this.state.inputValue,
from: 'you'
})
})
let data = this.state.inputValue;
var app = apiai("");
app.textRequest(data, {
sessionId: ''
}).then((response) => {
console.log(response);
this.setState((prevState) => {
prevState.messages.push({
message: response.result.fulfillment.speech,
from: 'bot'
})
})
}).catch((error) => {
console.log(error);
})
this.setState({
inputValue: ''
});
}
render() {
console.log("here ", this.state.messages)
return (<
div >
<
ChatHistory messages={
this.state.messages
} > < /ChatHistory> <
ChatMessageComposer
changed={
(event) => this.setState({
inputValue: event.target.value
})
}
atKeyPress={
(event) => this.atKeyPress(event)
}
value={
this.state.inputValue
}
>
< /ChatMessageComposer> <
/div>
)
}
}
export default Chat;
This is chatmessagecomposer component,
export default Chat;
const chatMessageComposer = (props) => {
return (
<div className={classes.Chatinput}>
<input placeholder="Talk to me..." className={classes.Userinput} type="text" value={props.value} onChange={props.changed} onKeyPress= {props.atKeyPress}/>
</div>
)
}
const chatHistory = (props) => (
<div className={classes.ChatOutput}>
{props.messages.map((message, i)=>(
<ChatMessage key={i} message={message} />
))}
</div
Any help will be appreciated
You are not returning the mutated state in your setState method call. Try doing this
this.setState((prevState) => {
prevState.messages.push({
message: response.result.fulfillment.speech,
from: 'bot'
})
return prevState;
})

Categories

Resources