How to test fetch result in componentDidMount - reactjs

I need to test if the state was set after promise resolved in componentDidMount
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
const result = todoStore.fetch();
mobx.when(
() => result.state !== mobxUtils.PENDING,
() => (this.setState({todos: result.value}))
)
}
render() {
const { todos } = this.state;
return <div>
{todos && <ul>
{todos.map(t => <li>{t.title}</li>)}
</ul>}
</div>
}
}
So far I have..
const wrapper = shallow(<Todos />);
// TODO Need wait until promise is resolved. But how?
// Maybe listen the setState method ?
// PLEASE don't use setTimout.
assert(wrapper.state('todos'))
See the full example..
https://runkit.com/ridermansb/testing-fetch-result-in-componentdidmount/1.0.0

Related

Ref undefined in ReactJs

When I try to use slider ref inside functionB I'm getting ref undefined error.What am i doing wrong here?
class somecomp extends Component {
constructor(props){
super(props);
this.slider = React.createRef;
}
componentDidMount(){
this.functionA();
}
functionA = () => {
functionB();
}
functionB = () => {
// When i call slider ref here im getting ref undefined
}
render() {
return (
<div ref={slider => (this.slider = slider)}></div>
);
}
}
export default somecomp;
I assume the root of your problem here
this.slider = React.createRef
You are not create ref you pass function which creates ref to slider
Try to use it in such way
this.slider = React.createRef();
Also this
<div ref={slider => (this.slider = slider)}></div>
Might be simplified to this
<div ref={this.slider}></div>
Also do not forget to user current property of ref
In your situation it will be
const node = this.slider.current;
Few problems you've in your code that should be corrected https://codesandbox.io/s/determined-lamport-gfv9j
class somecomp extends Component {
constructor(props){
super(props);
this.slider = React.createRef; // use React.createRef() at here
}
componentDidMount(){
this.functionA();
}
functionA = () => {
functionB(); // use this.functionB() at here
}
functionB = () => {
console.log("this.slider", this.slider);
// When i call slider ref here im getting ref undefined
}
render() {
return (
<div ref={slider => (this.slider = slider)}></div>
);
}
}
export default somecomp;

How to use onload in react?

To run the imagesrore function onload I have to call <img src="image_7.jpg" className="hide" alt="image_7.jpg"/> image but actually there is no use of this line and if I remove this onload doesn't work and function is not called. So how can I call the imagestore() onload in react.
class PicturesList extends React.Component {
constructor(props) {
super(props);
this.state = {
imagesarray: []
};
this.imagestore = this.imagestore.bind(this);
}
render() {
return (
<div>
<div onLoad= {() => this.imagestore()}>
<img src="image_7.jpg" className="hide" alt="image_7.jpg"/>
// To run the imagesrore function onload I have to call this image but actually there is no use of this line and if I remove this onload doesn't work and function is not called
</div>
<Gallery url={this.state.imagesarray}/>
</div>
);
}
imagestore()
{
const imgUrls=this.props.apikeys;
const objarr = Object.values(imgUrls);
this.setState({
imagesarray: objarr
});
}
}
what I want
class PicturesList extends React.Component {
constructor(props) {
super(props);
this.state = {
imagesarray: []
};
this.imagestore = this.imagestore.bind(this);
}
render() {
return (
<div>
<div onLoad= {() => this.imagestore()}>
// but when I did this imagestore() function not called
</div>
<Gallery url={this.state.imagesarray}/>
</div>
);
}
imagestore()
{
const imgUrls=this.props.apikeys;
const objarr = Object.values(imgUrls);
this.setState({
imagesarray: objarr
});
}
}
Instead of rendering the image which you dont want, you could simply load it in componentDidMount like
class PicturesList extends React.Component {
constructor(props) {
super(props);
this.state = {
imagesarray: []
};
this.imagestore = this.imagestore.bind(this);
}
componentDidMount() {
const img = new Image();
img.onload =() => {
// image has been loaded
this.imagestore()
};
img.src = 'image_7.jpg';
}
render() {
return (
<div>
</div>
<Gallery url={this.state.imagesarray}/>
</div>
);
}
imagestore() {
const imgUrls=this.props.apikeys;
const objarr = Object.values(imgUrls);
this.setState({
imagesarray: objarr
});
}
}
The above solution is just to call imageStore once an image is loaded. However if what you intend is to call imageStore when the component has fully loaded,just trigger this.imageStore() in componentDidMount
class PicturesList extends React.Component {
constructor(props) {
super(props);
this.state = {
imagesarray: []
};
this.imagestore = this.imagestore.bind(this);
}
componentDidMount() {
this.imagestore()
}
render() {
return (
<div>
</div>
<Gallery url={this.state.imagesarray}/>
</div>
);
}
imagestore() {
const imgUrls=this.props.apikeys;
const objarr = Object.values(imgUrls);
this.setState({
imagesarray: objarr
});
}
}
Use useEffect() in React. You would use it like this:
useEffect(()=>{
**INSERT CODE YOU WANT RUN ON LOAD HERE **
}, [])
Remember to import useEffect as well with
import React, { useEffect } from 'react'
Your onLoad function should be used at the <img> tag, not the <div/>.

compare the first state to the last state reactjs

I am trying to make an edit page. I am update the state for any changes made. I want to compare the initial state with the last state on the last save. but I can not control the first state.
export default class extends Component {
constructor(props){
super(props);
this.changeDetails = this.changeDetails.bind(this);
this.state = {
driver: this.props.driver
}
}
changeDetails = value => {
this.setState({
driver:value
})
}
onRegister = () => {
//I want to make a comparison here.
}
render() {
const {driver} = this.state
return (
<div>
<EditView driver={driver} changeDetails={this.changeDetails}/>
</div>
);
}
}
EditView.js
export default class extends Component {
render() {
const { driver} = this.props;
const changeDetails = event => {
driver['fname] = event.target.value;
this.props.changeDetails(driver);
};
return (
<div>
<Input
value={driver.fname}
onChange={event => changeDetails(event)}
/>
</div>
);
}
}
Do not mutate driver itself directly. Use something like this:
const changeDetails = event =>
this.props.changeDetails( { ...driver, fname: event.target.value } );

Cancel a promise when a component is unmounted in ReactJS

I've a component named "Item" which creates and calls a promise when it has been mounted.
class Item extends React.Component{
constructor(props){
super(props)
this.onClick = this.onClick.bind(this)
this.prom = new Promise((resolve, reject) => {
setTimeout(() => resolve("PROMISE COMPLETED "+this.props.id),6000)
})
}
componentDidMount(){
this.prom.then((success) => {
console.log(success)
})
}
componentWillUnmount(){
console.log("unmounted")
}
onClick(e){
e.preventDefault()
this.props.remove(this.props.id)
}
render(){
return (
<h1>Item {this.props.id} - <a href="#" onClick={this.onClick}>Remove</a></h1>
)
}
}
As you can see, the promise calls the resolve 6 seconds after it has been called.
There is another component named "List" that is responsible for showing those items on the screen. The "List" is the parent of the "Item" component.
class List extends React.Component{
constructor(props){
super(props)
this.state = {
items : [1,2,3]
}
this.handleRemove = this.handleRemove.bind(this)
}
handleRemove(id){
this.setState((prevState, props) => ({
items : prevState.items.filter((cId) => cId != id)
}));
}
render(){
return (
<div>
{this.state.items.map((item) => (
<Item key={item} id={item} remove={this.handleRemove} />
))
}
</div>
)
}
}
ReactDOM.render(<List />,root)
On the example above, it shows three Item on the screen.
If I remove any of those components, componentWillUnmount() is called but also the promise which has been created in the removed component is run.
For example, I can see the promise of the second item is run even if I remove the second item.
unmounted
PROMISE COMPLETED 1
PROMISE COMPLETED 2
PROMISE COMPLETED 3
I have to cancel the promise when a component is unmounted.
A variation of this https://hshno.de/BJ46Xb_r7 seemed to work for me.
I made an HOC with the mounted instance variable and wrapped all async components in it.
Below is what my code roughly loks like.
export function makeMountAware(Component) {
return class MountAwareComponent extends React.Component {
mounted = false;
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
return (
<Component
mounted = {this.mounted}
{...this.props}
{...this.state}
/>
);
}
}
class AsyncComponent extends React.Component {
componentDidMount() {
fetchAsyncData()
.then(data => {
this.props.mounted && this.setState(prevState => ({
...prevState,
data
}));
});
}
}
export default makeMountAware(AsyncComponent);
You can't cancel native ES6 promises. Read more at https://medium.com/#benlesh/promise-cancellation-is-dead-long-live-promise-cancellation-c6601f1f5082
What you can do, however, is maybe use non-native promise libraries like Bluebird or Q, that give you promises that can be cancelled.
There are various things you can do. The simplest is to reject the promise:
this.prom = new Promise((resolve, reject) => {
this.rejectProm = reject;
...
});
and then
componentWillUnmount(){
if (this.rejectProm) {
this.rejectProm();
this.rejectProm = nil;
}
console.log("unmounted")
}
Since you are using a timeout in this example you should clear it when unmounting.
class Item extends React.Component{
constructor(props){
super(props)
this.onClick = this.onClick.bind(this)
// attribute for the timeout
this.timeout = null;
this.prom = new Promise((resolve, reject) => {
// assign timeout
this.timeout = setTimeout(() => resolve("PROMISE COMPLETED "+this.props.id),6000)
})
}
componentDidMount(){
this.prom.then((success) => {
console.log(success)
})
}
componentWillUnmount(){
// clear timeout
clearTimeout(this.timeout);
console.log("unmounted")
}
My guess is this will result in a rejection and you won't see that console log.

React JS - updating state within an eventListener

I'm trying to update the state of my component inside of an eventListener. I'm getting the following console error:
'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. Please check the code for the Header component'
This is my component code:
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
fixed: false
}
}
handleScroll(event) {
this.setState({
fixed: true
});
}
componentDidMount() {
window.addEventListener("scroll",() => {
this.handleScroll();
});
}
componentWillUnmount() {
window.removeEventListener("scroll",() => {
this.handleScroll();
});
}
render() {
var {
dispatch,
className = "",
headerTitle = "Default Header Title",
onReturn,
onContinue
} = this.props;
var renderLeftItem = () => {
if (typeof onReturn === 'function') {
return (
<MenuBarItem icon="navigation-back" onClick={onReturn}/>
)
}
};
var renderRightItem = () => {
if (typeof onContinue === 'function') {
return (
<MenuBarItem icon="navigation-check" onClick= {onContinue}/>
)
}
};
return (
<div className={"header " + className + this.state.fixed}>
{renderLeftItem()}
<div className="header-title">{headerTitle}</div>
{renderRightItem()}
</div>
)
}
}
Header.propTypes = {
};
let mapStateToProps = (state, ownProps) => {
return {};
};
export default connect(mapStateToProps)(Header);
IMHO this is because you do ont unregister the function as you expect it, and a scroll event is sent after an instance of this component has been unmounted
try this:
componentDidMount() {
this._handleScroll = this.handleScroll.bind(this)
window.addEventListener("scroll", this._handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this._handleScroll);
}

Resources