In the constructor of my app I call an async function that does
this.setState({ loading: true })
This is the stack trace I get
util.js:9 Uncaught (in promise) TypeError: Cannot set property 'loading' of undefined
at s (util.js:9)
at App.d.setState (component.js:40)
at App.somethingAsync (app.js:47)
at new App (app.js:44)
at T (index.js:67)
at children.js:102
at b (children.js:236)
at b (children.js:231)
at _ (children.js:60)
at T (index.js:180))
In the the setState of component.js#L29 the this._nextState is undefined and s is also undefined.
I am using Preact with htm without any compilation, direct import in the browser with the script tag type module.
import { Component, h } from 'https://unpkg.com/[email protected]/dist/preact.module.js'
import htm from 'https://unpkg.com/htm?module'
const html = htm.bind(h)
export default class App extends Component {
constructor (props) {
super(props)
this.somethingAsync = this.somethingAsync.bind(this)
this.state = {
loading: false
}
this.somethingAsync()
}
async somethingAsync () {
this.setState({ loading: true })
setTimeout(() => this.setState({ loading: false }), 100)
}
}
This is the index.js
import { h, render } from 'https://unpkg.com/[email protected]/dist/preact.module.js'
import htm from 'https://unpkg.com/htm?module'
import App from './components/app.js'
const html = htm.bind(h)
render(
html`<${App} />`,
document.getElementById('app')
)
What should I do?
Hey,
Usually we dispatch async stuff from componentDidMount since this is a bit safer, this because in constructor we are still creating everything while an async setState could happen during the construction of this part of the tree. In ComponentDidMount we've already established the semantics of this and are committing it to the DOM
However it shouldn't error out and I'll certainly look at it.
Ok thanks for the answer I will use the componentDidMount to make it work meanwhile.
I have here a fully working example to test it easily.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<div id='app' />
<script type='module' >
import { Component, h, render } from 'https://unpkg.com/[email protected]/dist/preact.module.js'
import htm from 'https://unpkg.com/htm?module'
const html = htm.bind(h)
class App extends Component {
constructor (props) {
super(props)
this.somethingAsync = this.somethingAsync.bind(this)
this.state = {
loading: false
}
this.somethingAsync()
}
async somethingAsync () {
this.setState({ loading: true })
setTimeout(() => this.setState({ loading: false }), 100)
}
}
render(
html`<${App} />`,
document.getElementById('app')
)
</script>
</body>
</html>
Firing async code during class instantiation is unusual as plain instantiation of objects should be as few as possible and not trigger side effects. It may work, but the intended way to do it is via componentDidMount like Jovi said 馃憤
It's advised, even for React to always start your async work on componentDidMount
https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/#componentdidmount
Closing as calling effects inside constructor is unsupported behaviour. Use componentDidMount or an useEffect call if you're using hooks instead. Calling effects in constructor is unsafe because we are still in the process of creating the tree and the component instance as @JoviDeCroock explained.
Most helpful comment
Hey,
Usually we dispatch async stuff from
componentDidMountsince this is a bit safer, this because in constructor we are still creating everything while an async setState could happen during the construction of this part of the tree. In ComponentDidMount we've already established the semantics of this and are committing it to the DOMHowever it shouldn't error out and I'll certainly look at it.