Warning react : setState(...): Can only update a mounted or mounting component - javascript

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
This is a react application, where a banner is fixed on the screen and passing random images. The way it was written is generating the warning in question.
import React from "react";
import Lightbox from "react-image-lightbox";
import logo from "./logo.png";
class Banner extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [],
currentImage: logo,
isOpen: false,
sidebarOpen: true
};
}
async componentWillMount() {
await this.getBanners();
this.setState({ currentImage: this.state.images[0].url });
setInterval(async () => {
await this.getBanners();
}, 300000);
let i = 0;
setInterval(
() => {
this.setState({ currentImage: this.state.images[i].url });
if (i >= this.state.images.length - 1) {
i = 0;
} else {
i++;
}
},
10000,
i
);
}
async getBanners() {
const data = await (await fetch("/api/banners/active")).json();
if (data.true) {
this.setState({ images: data.true });
}
}
render() {
const { isOpen } = this.state;
return (
<div>
{isOpen && (
<Lightbox
mainSrc={this.state.currentImage}
onCloseRequest={() => this.setState({ isOpen: false })}
/>
)}
<footer>
<a>
<img
width={270}
height="200"
src={this.state.currentImage}
onClick={() => this.setState({ isOpen: true })}
alt="idk"
/>
</a>
</footer>
</div>
);
}
}
export default Banner;
Could anyone help improve this code?

You can put the numbers returned from setInterval on your instance and stop the intervals with clearInterval in componentWillUnmount so that they won't continue to run after the component has been unmounted.
class Banner extends React.Component {
constructor(props) {
super(props);
this.bannerInterval = null;
this.currentImageInterval = null;
this.state = {
images: [],
currentImage: logo,
isOpen: false,
sidebarOpen: true
};
}
async componentDidMount() {
await this.getBanners();
this.setState({ currentImage: this.state.images[0].url });
this.bannerInterval = setInterval(async () => {
await this.getBanners();
}, 300000);
let i = 0;
this.currentImageInterval = setInterval(
() => {
this.setState({ currentImage: this.state.images[i].url });
if (i >= this.state.images.length - 1) {
i = 0;
} else {
i++;
}
},
10000,
i
);
}
componentWillUnmount() {
clearInterval(this.bannerInterval);
clearInterval(this.currentImageInterval);
}
// ...
}

Use this template for any class-based component that has a state:
forgot about setState(), and use setComponentState declared down:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// other fields...
isUnmounted: false,
};
}
componentWillUnmount() {
this.setState({ isUnmounted: true });
}
setComponentState = (values) => {
if (!this.state.isUnmounted) this.setState(values);
};
}

Related

why I am getting warning-Functions are not valid as a React child. .......?

I was learning about various lifecycle components, so I have a counter in my code, which can be increased or decreased using buttons and I have a seed generator which should be called on button click and it should change the value of the counter to seed, I have added function for the seed to be set to Number.parseInt(Math.random() * 100)
when I run the code, it gives warning in chrome,
also when I click on increment , the counter is set to () => this.setState({ seed: Number.parseInt(Math.random() * 100) })1 , and when I press decrement(click) the counter is set to NaN.
There was a similar question related to this warning but that code was not related to mine.
APP COMPONENT
import React from "react";
import Counter from "./Counter";
import "./App.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mount: true,
ignoreProp: 0,
seed: 40
};
this.mountCounter = () => this.setState({ mount: true });
this.unmountCounter = () => this.setState({ mount: false });
this.ignoreProp = () => this.setState({ ignoreProp: Math.random() });
this.seedGenerator = () =>
this.setState({ seed: Number.parseInt(Math.random() * 100) });
}
render() {
let counter = null;
if (this.state.mount) {
counter = (
<Counter ignoreProp={this.state.ignoreProp} seed={this.seedGenerator} />
);
}
return (
<div className="App">
<button onClick={this.mountCounter} disabled={this.state.mount}>
Mount Counter
</button>
<button onClick={this.unmountCounter} disabled={!this.state.mount}>
Unmount Counter
</button>
<button onClick={this.ignoreProp}>Ignore Prop</button>
<button onClick={this.seedGenerator}>Generate seed</button>
{counter}
</div>
);
}
}
export default App;
COUNTER COMPONENT
import React from "react";
class Counter extends React.Component {
constructor(props) {
console.log("Constructor");
super(props);
this.state = {
counter: 0,
seed: 0
};
this.increment = () => this.setState({ counter: this.state.counter + 1 });
this.decrement = () => this.setState({ counter: this.state.counter - 1 });
}
static getDerivedStateFromProps(props, state) {
if (props.seed && state.seed !== props.seed) {
return {
seed: props.seed,
counter: props.seed
};
}
return null;
}
componentDidMount() {
console.log("Component Did Mount");
console.log("-------------------");
}
shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.ignoreProp &&
this.props.ignoreProp !== nextProps.ignoreProp
) {
console.log("Should Component Update- DO NOT RENDER");
return false;
}
console.log("Should Component Update- RENDER");
return true;
}
render() {
console.log("Render");
return (
<div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
<div className="counter">Counter: {this.state.counter}</div>
</div>
);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("Component Did Update");
console.log("--------------------");
}
componentWillUnmount() {
console.log("Component Will Unmount");
console.log("----------------------");
}
}
export default Counter;
You pass seedGenerator, a function, as the seed prop down to Counter, and since you have
return {
seed: props.seed,
counter: props.seed
}
in getDerivedStateFromProps (likely a copy-paste typo?), the
Counter: {this.state.counter}
render fragment tries to render seedGenerator, a function.

React: calling a function in the onClick returns undefined

I'm trying to write a code that shows a list of images, etc. based on this answer:
https://stackoverflow.com/a/57635373/9478434
My code is:
class ImageGallery extends Component {
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
}
getImages() {
axios
.get(IMAGE_LIST_URL, {})
.then((response) => {
const data = response.data;
console.log(data);
this.setState({ imageList: response.data });
})
.catch((error) => {
setTimeout(() => {
console.log(error.response.data.message);
}, 200);
});
}
componentDidMount() {
this.getImages();
}
changePhotoIndex(imgIndex) {
this.setState({ photoIndex: imgIndex, isOpen: true });
}
render() {
const { photoIndex, isOpen, imageList } = this.state;
const singleImage = imageList.map(function (img, imgIndex) {
const imagePath = `http://localhost:8000/media/${img.filePath}`;
console.log(imagePath);
return (
<figure className="col-xl-3 col-sm-6">
<img
src={imagePath}
alt="Gallery"
className="img-thumbnail"
onClick={() => this.changePhotoIndex(imgIndex)}
/>
</figure>
);
});
return <div>{singleImage}</div>;
}
}
However while clicking on the image, I get a type error (t is undefined) in the console regarding to the line onClick={() => this.changePhotoIndex(imgIndex) and the state of app does not change.
The changePhoneIndex function considers itself and not the component as this.
You can bind it to the component itself and be able to access setState by adding this to the constructor:
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
this.changePhotoIndex.bind(this);
}
Or you can call set state directly:
onClick={() => this.setState({ photoIndex: imgIndex, isOpen: true })}
You forgot to bind your function. Add the following to your code:
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
this.changePhotoIndex = this.changePhotoIndex.bind(this); // missing
}

How to replace componentWillReceiveProps with getDerivedStateFromProps

I read and tried so hard, but still could not find a way to successfully replace componentWillReceiveProps with getDerivedStateFromProps.
Here is my code, componentWillReceiveProps one is working fine.
but the getDerivedStateFromProps which I tried is not working(after componentDidMount() running OK, state.movies eventually become [] empty).
Thanks for your kind help!
class OK_MovieListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
title:''
};
this.discoverMovieDB = this.discoverMovieDB.bind(this);
this.searchMovieDBAPI = this.searchMovieDBAPI.bind(this);
}
componentDidMount() {
this.discoverMovieDB();
}
UNSAFE_componentWillReceiveProps(nextProps, prevState) {
if (nextProps.match.params.title && prevState.title !== nextProps.match.params.title) {
this.searchMovieDBAPI(nextProps.match.params.title);
this.setState({ title: nextProps.match.params.title });
}
}
async discoverMovieDB() {
.....
}
async searchMovieDBAPI(title) {
const promisedData = await MovieDBAPI.search(title);
if (promisedData) {
this.setState({ movies: promisedData[0] });
}
}
}
render() {
return (
<div className="App">
<MovieList movies={this.state.movies} />
</div>
);
}
}
export default OK_MovieListContainer;
the following is what I tried with getDerivedStateFromProps , but not working(after componentDidMount() running OK, state.movies eventually become [] empty). ....T.T
class MovieListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
title:''
};
this.discoverMovieDB = this.discoverMovieDB.bind(this);
this.searchMovieDBAPI = this.searchMovieDBAPI.bind(this);
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.match.params.title && nextProps.match.params.title !== prevState.title) {
return {
//this state changes then componentDidUpdate is called
title: nextProps.match.params.title
};
}
//indicate state update not required
return null;
}
componentDidMount() {
this.discoverMovieDB();
}
componentDidUpdate(nextProps, prevState) {
if (nextProps.match.params.title !== prevState.title) {
this.searchMovieDBAPI(nextProps.match.params.title);
this.setState({ title: nextProps.match.params.title });
}
}
async discoverMovieDB() {
.....
}
async searchMovieDBAPI(title) {
const promisedData = await MovieDBAPI.search(title);
if (promisedData) {
this.setState({ movies: promisedData[0] });
}
}
}
render() {
return (
<div className="App">
<MovieList movies={this.state.movies} />
</div>
);
}
}
export default MovieListContainer;
Thank you so much!

How to set timeout on event onChange

I have a gallery that show images, and i have a search textbox
Im Trying to use Timeout on Input event to prevent the api call on every letter im typing :
I try to handle the event with doSearch function onChange: but now I cant write anything on the textbox and it cause many errors
Attached to this session the app and gallery components
Thanks in advance
class App extends React.Component {
static propTypes = {
};
constructor() {
super();
this.timeout = 0;
this.state = {
tag: 'art'
};
}
doSearch(event){
var searchText = event.target.value; // this is the search text
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){this.setState({tag: event.target.value})} , 500);
}
render() {
return (
<div className="app-root">
<div className="app-header">
<h2>Gallery</h2>
<input className="input" onChange={event => this.doSearch(event)} value={this.state.tag}/>
</div>
<Gallery tag={this.state.tag}/>
</div>
);
}
}
export default App;
This is the Gallery class:
import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import Image from '../Image';
import './Gallery.scss';
class Gallery extends React.Component {
static propTypes = {
tag: PropTypes.string
};
constructor(props) {
super(props);
this.state = {
images: [],
galleryWidth: this.getGalleryWidth()
};
}
getGalleryWidth(){
try {
return document.body.clientWidth;
} catch (e) {
return 1000;
}
}
getImages(tag) {
const getImagesUrl = `services/rest/?method=flickr.photos.search&api_key=522c1f9009ca3609bcbaf08545f067ad&tags=${tag}&tag_mode=any&per_page=100&format=json&safe_search=1&nojsoncallback=1`;
const baseUrl = 'https://api.flickr.com/';
axios({
url: getImagesUrl,
baseURL: baseUrl,
method: 'GET'
})
.then(res => res.data)
.then(res => {
if (
res &&
res.photos &&
res.photos.photo &&
res.photos.photo.length > 0
) {
this.setState({images: res.photos.photo});
}
});
}
componentDidMount() {
this.getImages(this.props.tag);
this.setState({
galleryWidth: document.body.clientWidth
});
}
componentWillReceiveProps(props) {
this.getImages(props.tag);
}
render() {
return (
<div className="gallery-root">
{this.state.images.map((dto , i) => {
return <Image key={'image-' + dto.id+ i.toString()} dto={dto} galleryWidth={this.state.galleryWidth}/>;
})}
</div>
);
}
}
First of all why do you need to use setTimeout to set value that is entered by user. I don't see any use using setTimeout in doSearch function.
The reason your doSearch function won't work because you are not binding it.
You can directly set value to tag using setState in doSearch function in following ways.
ES5 way
constructor(props){
super(props);
this.doSearch = this.doSearch.bind(this);
}
doSearch(event){
this.setState({
tag: event.target.value
});
}
ES6 way
doSearch = (event) => {
this.setState({
tag: event.target.value
});
}
Doing setState inside setTimeout in doSearch function won't work because
input tag has value assigned.
ES5 way
constructor(props){
super(props);
this.doSearch = this.doSearch.bind(this);
}
doSearch(event){
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){
this.setState({
tag: event.target.value
})
}.bind(this),500);
}
setTimeout in ES6 way
doSearch = (event) => {
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.setState({
tag: event.target.value
})
},500);
}
Gallery component:
Check current props changes with previous change in componentWillRecieveProps to avoid extra renderings.
Try with below updated code
import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import Image from '../Image';
import './Gallery.scss';
class Gallery extends React.Component {
static propTypes = {
tag: PropTypes.string
};
constructor(props) {
super(props);
this.state = {
images: [],
galleryWidth: this.getGalleryWidth()
};
}
getGalleryWidth(){
try {
return document.body.clientWidth;
} catch (e) {
return 1000;
}
}
getImages(tag) {
const getImagesUrl = `services/rest/?method=flickr.photos.search&api_key=522c1f9009ca3609bcbaf08545f067ad&tags=${tag}&tag_mode=any&per_page=100&format=json&safe_search=1&nojsoncallback=1`;
const baseUrl = 'https://api.flickr.com/';
axios({
url: getImagesUrl,
baseURL: baseUrl,
method: 'GET'
})
.then(res => res.data)
.then(res => {
if (
res &&
res.photos &&
res.photos.photo &&
res.photos.photo.length > 0
) {
this.setState({images: res.photos.photo});
}
});
}
componentDidMount() {
this.getImages(this.props.tag);
this.setState({
galleryWidth: document.body.clientWidth
});
}
componentWillReceiveProps(nextProps) {
if(nextProps.tag != this.props.tag){
this.getImages(props.tag);
}
}
shouldComponentUpdate(nextProps, nextState) {
if(this.props.tag == nextProps.tag){
return false;
}else{
return true;
}
}
render() {
return (
<div className="gallery-root">
{this.state.images.map((dto , i) => {
return <Image key={'image-' + dto.id+ i.toString()} dto={dto} galleryWidth={this.state.galleryWidth}/>;
})}
</div>
);
}
}
I am keeping tag initial value to empty as you are not doing anything with value art.
Please try with below code
class App extends React.Component {
static propTypes = {
};
constructor() {
super();
this.timeout = 0;
this.state = {
tag: '',
callGallery: false
};
}
doSearch = (event) => {
this.setState({tag: event.target.value, callGallery: false});
}
handleSearch = () => {
this.setState({
callGallery: true
});
}
render() {
return (
<div className="app-root">
<div className="app-header">
<h2>Gallery</h2>
<input className="input" onChange={event => this.doSearch(event)} value={this.state.tag}/>
<input type="button" value="Search" onClick={this.handleSearch} />
</div>
{this.state.callGallery && <Gallery tag={this.state.tag}/>}
</div>
);
}
}
export default App;
This is because you haven't bound this to your method.
Add the following to your constructor:
this.doSearch = this.doSearch.bind(this);
Also, you don't need the fat arrow notation for onChange. Just do:
onChange={this.doSearch}
onChange handler is just fine but you need to bind the setTimeout to render context.Currently,it is referring to window context.And the code as follows
doSearch(event){
var searchText = event.target.value; // this is the search text
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){
this.setState({
tag: event.target.value
})
}.bind(this),500);
}

How to create shared-singleton components across all the platform?

I'm thinking on creating a React component called LoadingMask, where I can show or not (depending on the state) a loading mask from any component. The idea is showing it before an ajax call, and hiding it after I receive the data.
I don't want to display two masks at the same time, so if one component is making a request, and another one creates another request, I want to add 1 to my "MaskCounter", and substract one when the Request is finished. If the counter is 0, I need to hide the LoadingMask.
I order to do this, I think I need to create a "Singleton" component, that I can share through the whole platform, so there's only exist one LoadingMask. I also don't think it's nice to send the events to hide/show the mask to all components.
Any ideas?
To share data between components, you can :
Use a lib like Redux, and keep in shared store your mask loader status
Use the React context api from your root component, and share loader status to all childrens. See an example below :
class Application extends React.Component {
constructor() {
super();
this.state = {
nbTasks: 0
};
this.addTask = this.addTask.bind(this);
this.removeTask = this.removeTask.bind(this);
this.isLoading = this.isLoading.bind(this);
}
addTask() {
this.setState(prevState => ({
nbTasks: prevState.nbTasks + 1
}));
}
removeTask() {
this.setState(prevState => ({
nbTasks: prevState.nbTasks - 1
}));
}
isLoading() {
return this.state.nbTasks > 0;
}
getChildContext() {
return {
addTask: this.addTask,
removeTask: this.removeTask,
isLoading: this.isLoading
};
}
render() {
return (
<div>
<ComponentX />
<ComponentY />
<LoadingMask />
</div>
);
}
}
Application.childContextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func,
isLoading: PropTypes.func
};
const LoadingMask = (props, context) => (
context.isLoading()
? <div>LOADING ...</div>
: null
);
LoadingMask.contextTypes = {
isLoading: PropTypes.func
};
class ComponentX extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Processing ...'
};
}
componentDidMount() {
this.context.addTask();
setTimeout(() => {
this.setState({
message: 'ComponentX ready !'
});
this.context.removeTask();
}, 3500);
}
render() {
return (
<div>
<button disabled>{this.state.message}</button>
</div>
);
}
}
ComponentX.contextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func
};
class ComponentY extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Processing ...'
};
}
componentDidMount() {
this.context.addTask();
setTimeout(() => {
this.setState({
message: 'ComponentY ready !'
});
this.context.removeTask();
}, 6000);
}
render() {
return (
<div>
<button disabled>{this.state.message}</button>
</div>
);
}
}
ComponentY.contextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func
};
ReactDOM.render(
<Application />,
document.getElementById('app')
);
<script src="https://unpkg.com/prop-types/prop-types.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="app"></app>
I found this library use-between to be simple, powerful and useful. It removes complexity of redux for sharing data between within functional components.
import React, { useState, useCallback } from 'react';
import { useBetween } from 'use-between';
Context/Session.ts
export const useShareableState = () => {
const [count, setCount] = useState(0);
const inc = useCallback(() => setCount(c => c + 1), []);
const dec = useCallback(() => setCount(c => c - 1), []);
return {
count,
inc,
dec
};
};
App.tsx
import { useBetween } from 'use-between';
import { useShareableState } from './src/Context/Session'
const useSharedCounter = () => useBetween(useShareableState);
const Count = () => {
const { count } = useSharedCounter();
return <p>{count}</p>;
};
const Buttons = () => {
const { inc, dec } = useSharedCounter();
return (
<>
<button onClick={inc}>+</button>
<button onClick={dec}>-</button>
</>
);
};
const App = () => (
<>
<Count />
<Buttons />
<Count />
<Buttons />
</>
);
export default App;

Categories

Resources