I have the previous code that updates the interval data in the locale and in the browser without issue.
class Main extends Component {
constructor(props) {
super(props);
this.state = {data: []}
}
componentWillMount() {
fetch('file.json')
.then(response => response.json())
.then(result =>
this.setState({
data: result.data}));
}
componentDidMount() {
this.timer = setInterval(() => componentWillMount(), 5000);
}
componentWillUnmount() {
this.timer = null;
}
Due to the fact that componentWillMount is now deprecated for use, I decided to rewrite the code. As a result, the data is updated in the locale, but not in the browser. Why? Please help me.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
lang: 'ru'
}
}
componentDidMount() {
this.loadingData = fetch('file.json')
.then(response => response.json())
.then(result =>
this.setState({
data: result.data}));
this.timer = setInterval(() => this.loadingData, 5000);
}
componentWillUnmount() {
this.timer = null;
}
As requested, here's an answer based on my comment. Also see how I clear the interval when the component will be unmounted.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
lang: 'ru'
}
}
loadData() {
fetch('file.json')
.then(response => response.json())
.then(result =>
this.setState({
data: result.data
})
);
}
componentDidMount() {
this.loadData();
this.timer = setInterval(() => this.loadData(), 5000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
You may consider to use setTimeout and reinitiate this from within your fetch so that you can account for slow network etc.
I am not sure if this is the "right" way to do it but you can just force the browser to refresh the page: window.location.reload()
Related
I have an api which i want to filter the data and place the filterd into a state
export default class ModifyPage_Single extends React.Component {
constructor(props) {
super(props)
this.state = {data:[],idd:""}
}
componentWillMount() {
fetch("removed api")
.then(response => response.json())
.then((data) =>{
this.setState({data:data})
})
}
render() {
const test = this.state.data.map((e) => { if(e.ID === this.props.location.productdetailProps.productdetail) {this.setState({idd:e.PP})} })
But i keep getting this error Unhandled Rejection (Error): Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
How can i solve so that the fitered out api goes into the state?
Thanks in advance
you should update in componentMount, not in render():
export default class ModifyPage_Single extends React.Component {
constructor(props) {
super(props);
this.state = { data: [], idd: "" };
}
componentWillMount() {
fetch("removed api")
.then((response) => response.json())
.then((data) => {
this.setState({ data: data });
data.forEach((e) => {
if (e.ID === this.props.location.productdetailProps.productdetail) {
this.setState({ idd: e.PP });
}
});
});
}
render() {
return null;
}
}
You can update the state in lifecycle methods, updating it in render is anti pattern
componentDidMount() {
fetch("removed api")
.then(response => response.json())
.then((data) =>{
this.setState({data:data})
const iddObj = data.find((el) => el.ID === this.props.location.productdetailProps.productdetail)
if(iddObj ){
this.setState({idd:iddObj.PP})
}
})
}
I'm making a call to a getTime function which returns the datetime, but for some reason the state I specify is not being updated, am I misunderstanding how this works? Do I need to await the result?
import * as React from 'react';
import {getTime} from '../utilities/time-helper'
export default class Landing extends React.Component {
constructor(props) {
super(props);
this.state = {
london: null,
paris: null
};
}
componentDidMount() {
this.setState({ london: getTime("Europe/London") });
this.setState({ paris: getTime("Europe/Paris") });
}
render() {
return (
<div>
<h1>London Time: {this.state.london}</h1>
<h1>Paris Time: {this.state.paris}</h1>
</div>
);
}
}
// time-helper.js
export function getTime(timezone) {
let url = 'http://worldtimeapi.org/api/timezone/' + timezone;
fetch(url)
.then(res => res.json())
.then((out) => {
return out.datetime
})
.catch(err => { throw err });
}
Yes, exactly, it's a fetch, so you gotta wait for the result and set the state only then, so you could do smth like:
componentDidMount() {
getTime('Europe/London')
.then((response) => this.setState({ london: response });
}
I have a React.Component inside a ReactModal.
class Course extends React.Component {
constructor(props) {
super(props)
this.state = {
isModalOpen: false,
}
}
handleModalOpen = event => {
this.setState({ isModalOpen: true })
}
handleModalClose = event => {
this.setState({ isModalOpen: false })
}
render() {
<ReactModal
isOpen={this.state.isModalOpen}
onRequestClose={this.handleModalClose}
contentLabel="Purchase a Course"
style={customStyles}>
<CheckoutComponent handleClose={this.handleModalClose}/>
</ReactModal>
class CheckoutForm extends React.Component {
constructor(props) {
super(props);
}
handleSubmit = (ev) => {
axios.post(`${process.env.API_URL}purchase`, charge)
.then(function (response) {
this.props.handleClose();
}
}
I would like to close the react modal upon successful post of the http request.
However, this is undefined.
How can I do it?
The problem is in here
axios.post(`${process.env.API_URL}purchase`, charge)
// here
.then(function (response) {
this.props.handleClose();
})
You need to use arrow functions. With normal functions, this will the the annonimous function's this. Using arrow functions solve this problem and you get the component's this.
axios.post(`${process.env.API_URL}purchase`, charge)
// arrow function
.then(response => {
this.props.handleClose();
})
This answer explain it well.
In a lot of my components I need to do something like this:
handleSubmit() {
this.setState({loading: true})
someAsyncFunc()
.then(() => {
return this.props.onSuccess()
})
.finally(() => this.setState({loading: false}))
}
The onSuccess function
may or may not be a promise (if it is, loading should stay true until it is resolved)
may or may not unmount the component (it may close the modal this component is in or even navigate to different page)
If the function unmounts the component, this.setState({loading: false}) obviously triggers a warning Can't call setState (or forceUpdate) on an unmounted component.
My 2 questions:
Is there a simple way to avoid the issue ? I don't want to set some _isMounted variable in componentDidMount and componentWillUnmount and then check it when needed in most of my components, plus I may forget to do it next time writing something like this ...
Is it really a problem ? I know that, according to the warning, it indicates a memory leak in my application, but it is not a memory leak in this case, is it ? Maybe ignoring the warning would be ok ...
EDIT: The second question is a little bit more important for me than the first. If this really is a problem and I just can't call setState on unmounted component, I'd probably find some workaround myself. But I am curious if I can't just ignore it.
Live example of the problem:
const someAsyncFunc = () => new Promise(resolve => {
setTimeout(() => {
console.log("someAsyncFunc resolving");
resolve("done");
}, 2000);
});
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {loading: false};
}
componentDidMount() {
setTimeout(() => this.handleSubmit(), 100);
}
handleSubmit() {
this.setState({loading: true})
someAsyncFunc()
/*
.then(() => {
return this.props.onSuccess()
})
*/
.finally(() => this.setState({loading: false}))
}
render() {
return <div>{String(this.state.loading)}</div>;
}
}
class Wrapper extends React.Component {
constructor(props, ...rest) {
super(props, ...rest);
this.state = {
children: props.children
};
}
componentDidMount() {
setTimeout(() => {
console.log("removing");
this.setState({children: []});
}, 1500)
}
render() {
return <div>{this.state.children}</div>;
}
}
ReactDOM.render(
<Wrapper>
<Example />
</Wrapper>,
document.getElementById("root")
);
.as-console-wrapper {
max-height: 100% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
Unfortunately you have to keep track of "isMounted" yourself.
To simplify you control flow you could use async/await:
handleSubmit() {
this.setState({loading: true})
try {
await someAsyncFunction()
await this.props.onSuccess()
} finally {
if (this._isMounted) {
this.setState({loading: false})
}
}
}
This is actually mentioned in the react docs, which points to this solution: https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6
If your someAsyncFunction supports cancelation, you should do so in componentWillUnmount, as encouraged by this article. But then - of course - check the return value and eventually not call this.props.onSuccess.
class myClass extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this._isMounted = true;
this._getData();
}
componentWillUnmount() {
this._isMounted = false;
}
_getData() {
axios.get('example.com').then(data => {
if (this._isMounted) {
this.setState({ data })
}
});
}
render() {
...
}
}
You should be able to use this._isMounted to check if the component is actually mounted.
handleSubmit() {
this.setState({loading: true})
someAsyncFunc()
.then(() => {
return this.props.onSuccess()
})
.finally(() => {
if (this && this._isMounted) { // check if component is still mounted
this.setState({loading: false})
}
})
}
But be aware that this approach is considered to be an anitpattern. https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
What about
componentWillUnmount() {
// Assign this.setState to empty function to avoid console warning
// when this.setState is called on an unmounted component
this.setState = () => undefined;
}
I have problem with automatically re-rendering view, when state is changed.
State has been changed, but render() is not called. But when I call this.forceUpdate(), everything is ok, but I think that's not the best solution.
Can someone help me with that ?
class TODOItems extends React.Component {
constructor() {
super();
this.loadItems();
}
loadItems() {
this.state = {
todos: Store.getItems()
};
}
componentDidMount(){
//this loads new items to this.state.todos, but render() is not called
Store.addChangeListener(() => { this.loadItems(); this.forceUpdate(); });
}
componentWillUnmount(){
Store.removeChangeListener(() => { this.loadItems(); });
}
render() {
console.log("data changed, re-render");
//...
}}
You should be using this.state = {}; (like in your loadItems() method) from the constructor when you are declaring the initial state. When you want to update the items, use this.setState({}). For example:
constructor() {
super();
this.state = {
todos: Store.getItems()
};
}
reloadItems() {
this.setState({
todos: Store.getItems()
});
}
and update your componentDidMount:
Store.addChangeListener(() => { this.reloadItems(); });
You sholdn't mutate this.state directly. You should use this.setState method.
Change loadItems:
loadItems() {
this.setState({
todos: Store.getItems()
});
}
More in react docs
In your component, whenever you directly manipulate state you need to use the following:
this.setState({});
Complete code:
class TODOItems extends React.Component {
constructor() {
super();
this.loadItems();
}
loadItems() {
let newState = Store.getItems();
this.setState = {
todos: newState
};
}
componentDidMount(){
//this loads new items to this.state.todos, but render() is not called
Store.addChangeListener(() => { this.loadItems(); this.forceUpdate(); });
}
componentWillUnmount(){
Store.removeChangeListener(() => { this.loadItems(); });
}
render() {
console.log("data changed, re-render");
//...
}}