Enzyme call method - javascript

Let's say I have a method in my React component:
doSomething() {
// method uses this.props and this.state
}
I want to test this method for different props and states that are set. So how can I call it? MyClass.prototype.doSomething will call the function, but then this.props and this.state are not set.

You can use enzyme's instance function to get an instance of the rendered component, and call methods on it.
const wrapper = shallow(<MyClass {...props} />)
wrapper.instance().doSomething()

Let's proceed under the assumption your doSomething() method is increaseByOne() which just adds +1
describe('Calculator', () => {
it('should update state by 1 on increaseByOne()', () => {
const wrapper = shallow(<Calculator />);
expect(wrapper.instance().state().score).toBe(0);
wrapper.instance().increaseByOne();
expect(wrapper.instance().state().score).toBe(1);
});
});
To find out more you can check out the documentation about state() and Instance() docs

NOTE: #Tyler's answer below is the better solution
You could just apply the value of this that you want to use:
// MyClass.prototype.doSomething() becomes
var mockThis = {
props: { coolProp: 'coolValue1' },
state: { coolStateKey: 'coolValue2' }
};
MyClass.prototype.doSomething.apply(mockThis);`

Related

How to send a ref to a function as a parameter in React Native?

I've imported a custom component into my screen and rendered it in the render() function. Then, created a ref to that custom component. Now, the render() function simply looks like this.
render() {
return (
<View>
<MyComponent ref={component => this.myComponent = component} />
</View>
)
}
Then, I've created another function to access the state of my custom component. I wrote it like this.
myFunction = (ref) => {
ref.setState({ myState: myValue })
}
Then, I called that function like this.
this.myFunction(this.myComponent)
But, it does not work. It gives me the following error.
null is not an object (evaluating 'ref.setState')
Actually what I need this myFunction to do is,
this.myComponent.setState({ myState: myValue })
Can you please help me to solve this problem?
ref is not your this object. it's dom for your componnet. For setState you need this of your component.
you can pass this as argument.
myFunction(this)
Now you will be able to do ref.setState in myFunction.
function myFunction(ref) {
ref.setState({ myState: myValue })
}
To use setState, just use your component's context (this keyword). The context also have your ref in it, so you don't need to pass it as an argument if you are inside one component(not forwarding down to children)
myFunction = (event) => {
this.myComponent // -> points to your ref, DOM element
this.setState() // use your setState like that
}
Don't forget to bind your context in parent component if you want to pass the handler to the child components. Refer to this useful topic
EDIT: Based on your comment, I guess you want to update the parent state by calling a handler in some other component. To do that, you need to create a handler in your parent component, bind the context and pass it as a property to the child component. Next up, you need to assign this handler in your child component. You cannot pass a context with setState method via argument or ref, this is just not how it works in javascript and in react.
Example:
// ParentComponent.js
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 1,
};
this.onChangeHandler = this.onChangeHandler.bind(this);
}
onChangeHandler(event) {
this.setState({
value: someNewValue // will update state on parent component
})
}
render() {
return (
<View>
<SomeComponent>{this.state.value}</SomeComponent>
<ChildrenComponent onChangeHandler={this.onChangeHandler} />
</View>
);
}
}
// ChildrenComponent.js
const ChildrenComponent = (props) => (
<View>
<Button
onPress={props.onChangeHandler}
title="click me to change parent state"
/>
</View>
);
Hopefully, this is what you need :)

React setState re-render

First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys

Creating a React higher order component, to serve as a "loader"(animation) wrapper for child components

I have a lot of components, that require some ajax function being sent, in the componentDidMount method. I would like to create a HOC, whose sole purpose is to "apply" some animation to the component, and stop this animation once a certain promise is resolved.
Of course, i could just copy paste this code for each component, but i would like to create some abstraction that deals with it.
The problem is, that i don't know how to pass the function properly, from the child to the parent. For instance, let's assume the intended child component, has this componentDidMount:
componentDidMount() {
ajax('/costumers')
.then(({ data }) => {
this.setState(() => ({ costumers: data.content }))
})
}
Technically, i need to either pass this function as an argument to the HOC, or perhaps somehow "hijack" the child's componentDidMount(if something like that is possible...). The HOC would then apply an animation once it's loaded, then send the ajax, and only when it's solved, the animation is eliminated, and the child component gets rendered.
How can this be achieved?
Any idea will be appreciated
Here is how you can write a HOC for such a case, refer to React docs for more info on the subject.
const withLoader = (loader, Component) =>
class WithLoader extends React.Component {
state = { ready: false, data: null };
async componentDidMount() {
const data = await loader();
this.setState({ ready: true, data });
}
render() {
if (!this.state.ready) return <div>LOADING</div>; // or <ComponentWithAnimation />
return <Component data={this.state.data} />;
}
};
const Test = props => <div>DATA: {props.data}</div>;
const fakeLoader = () =>
new Promise(res => setTimeout(() => res("My data"), 1000));
const TestWithLoader = withLoader(fakeLoader, Test);

React ref to component not exposing method

I'm using a ref to component to imperatively trigger a reset on a filter form from its parent.
In the same component we have:
handleFilterReset() {
// this.filterForm is defined but reset() isn't exposed
// see console.log(this.filterForm) output below
this.filterForm.reset()
}
render() {
return (
<FilterBox onReset={::this.handleFilterReset}>
<FilterForm ref={(ref) => { this.filterForm = ref }} />
</FilterBox>
)
}
And in FilterForm we have:
class FilterForm extends React.Component {
reset() {
// this is not being called
}
}
console.log output:
ProxyComponent {props: Object, context: Object, refs: Object, updater: Object, _reactInternalInstance: ReactCompositeComponentWrapper…}
It seems to me that everything is done according to the official docs. However, I get the following error:
Uncaught TypeError: this.filterForm.reset is not a function
at SalesChannelsList.handleFilterReset
Thanks
Found it ! It was "because" of React Intl.
1) Use the withRef option set to true when using injectIntl:
injectIntl(SalesChannelsFilterForm, { withRef: true })
2) In the ref callback of your component, you can access your instance with the following code
ref={(ref) => this.filterForm = ref.refs.wrappedInstance}
However this will crash because the ref callback is being called twice during the render(), the 1st time with a null value. So you should 1st verify that it has been defined. My complete solution:
In the render() method, on the component:
ref={::this.setFilterFormRef}
Then the handler:
setFilterFormRef(ref) {
if (ref && ref.refs) {
this.filterForm = ref.refs.wrappedInstance
}
}
Enjoy!
I would use ref="refName" instead of having a function (which is created again every render) and then access it through this.refs.
Anyway, here is a working example:
https://www.webpackbin.com/bins/-KjHtMcw3LcVEycggzWU

Is there a way to check if the react component is unmounted?

I have a usecase where i need to unmount my react component. But in some cases, the particular react component is unmounted by a different function.
Hence, I need to check if the component is mounted before unmounting it.
Since isMounted() is being officially deprecated, you can do this in your component:
componentDidMount() {
this._ismounted = true;
}
componentWillUnmount() {
this._ismounted = false;
}
This pattern of maintaining your own state variable is detailed in the ReactJS documentation: isMounted is an Antipattern.
I'll be recommended you to use the useRef hook for keeping track of component is mounted or not because whenever you update the state then react will re-render the whole component and also it will trigger the execution of useEffect or other hooks.
function MyComponent(props: Props) {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
return () => { isMounted.current = false }
}, []);
return (...);
}
export default MyComponent;
and you check if the component is mounted with if (isMounted.current) ...
I think that Shubham answer is a workaround suggested by react for people that need to transition their code to stop using the isMounted anti-pattern.
This is not necessarily bad, but It's worth listing the real solutions to this problem.
The article linked by Shubham offers 2 suggestions to avoid this anti pattern. The one you need depends on why you are calling setState when the component is unmounted.
if you are using a Flux store in your component, you must unsubscribe in componentWillUnmount
class MyComponent extends React.Component {
componentDidMount() {
mydatastore.subscribe(this);
}
render() {
...
}
componentWillUnmount() {
mydatastore.unsubscribe(this);
}
}
If you use ES6 promises, you may need to wrap your promise in order to make it cancelable.
const cancelablePromise = makeCancelable(
new Promise(r => component.setState({...}}))
);
cancelablePromise
.promise
.then(() => console.log('resolved'))
.catch((reason) => console.log('isCanceled', reason.isCanceled));
cancelablePromise.cancel(); // Cancel the promise
Read more about makeCancelable in the linked article.
In conclusion, do not try to patch this issue by setting variables and checking if the component is mounted, go to the root of the problem. Please comment with other common cases if you can come up with any.
Another solution would be using Refs . If you are using React 16.3+, make a ref to your top level item in the render function.
Then simply check if ref.current is null or not.
Example:
class MyClass extends React.Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
}
checkIfMounted() {
return this.elementRef.current != null;
}
render() {
return (
<div ref={this.elementRef} />
);
}
}
Using #DerekSoike answer, however in my case using useState to control the mounted state didn't work since the state resurrected when it didn't have to
What worked for me was using a single variable
myFunct was called in a setTimeout, and my guess is that when the same component initialized the hook in another page it resurrected the state causing the memory leak to appear again
So this didn't work for me
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
return () => setIsMounted(false)
}, [])
const myFunct = () => {
console.log(isMounted) // not always false
if (!isMounted) return
// change a state
}
And this did work for me
let stillMounted = { value: false }
useEffect(() => {
stillMounted.value = true
return () => (stillMounted.value = false)
}, [])
const myFunct = () => {
if (!stillMounted.value) return
// change a state
}
I got here because I was looking for a way to stop polling the API.
The react docs does cover the websocket case, but not the polling one.
The way I worked around it
// React component
React.createClass({
poll () {
if (this.unmounted) {
return
}
// otherwise, call the api
}
componentWillUnmount () {
this.unmounted = true
}
})
it works. Hope it helps
Please, let me know if you guys know any failing test case for this =]
If you're using hooks:
function MyComponent(props: Props) {
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
return () => {
setIsMounted(false);
}
}, []);
return (...);
}
export default MyComponent;
The same idea but enother implementation
/**
* component with async action within
*
* #public
*/
class MyComponent extends Component {
constructor ( ...args ) {
// do not forget about super =)
super(...args);
// NOTE correct store "setState"
let originSetState = this.setState.bind(this);
// NOTE override
this.setState = ( ...args ) => !this.isUnmounted&&originSetState(...args);
}
/**
* no necessary setup flag on component mount
* #public
*/
componentWillUnmount() {
// NOTE setup flag
this.isUnmounted = true;
}
/**
*
* #public
*/
myCustomAsyncAction () {
// ... code
this.setState({any: 'data'}); // do not care about component status
// ... code
}
render () { /* ... */ }
}
I have solve with hot reload and react to different it events ✅
const {checkIsMounted} = useIsMounted(); //hook from above
useEffect(() => {
//here run code
return () => {
//hot reload fix
setTimeout(() => {
if (!checkIsMounted()) {
//here we do unmount action
}
}, 100);
};
}, []);
Pproblem
There is a problem when using the useState() hook. If you are also trying to do something else in a useEffect function (like fetching some data when the component is mounted) at the same time with setting the new value for the hook,
const [isMounted, setIsMounted] = useState(false)
useEffect(() =>
{
setIsMounted(true) //should be true
const value = await fetch(...)
if (isMounted) //false still
{
setValue(value)
}
return () =>
{
setIsMounted(false)
}
}, [])
the value of the hook will remain the same as the initial value (false), even if you have changed it in the beggining. It will remain unchanged for that first render, a new re-render being required for the new value to be applied.
For some reason #GWorking solution did not work too. The gap appears to happen while fetching, so when data arrives the component is already unmounted.
Solution
You can just combine both and and check if the component is unmounted during any re-render and just use a separate variable that will keep track to see if the component is still mounted during that render time period
const [isMounted, setIsMounted] = useState(false)
let stillMounted = { value: false }
useEffect(() =>
{
setIsMounted(true)
stillMounted.value = true
const value = await fetch(...)
if (isMounted || stillMounted.value) //isMounted or stillMounted
{
setValue(value)
}
return () =>
{
(stillMounted.value = false)
setIsMounted(false)
}
}, [isMounted]) //you need to also include Mounted values
Hope that helps someone!
There's a simple way to avoid 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.
You can redefine setState method inside your class component using this pattern:
componentWillUnmount() {
this._unmounted = true;
}
setState(params, callback) {
this._unmounted || super.setState(params, callback);
}
i found that the component will be unmounted, generate fill this var
if(!this._calledComponentWillUnmount)this.setState({vars});
You can use:
myComponent.updater.isMounted(myComponent)
"myComponent" is instance of your react component.
this will return 'true' if component is mounted and 'false' if its not..
This is not supported way to do it. you better unsubscribe any async/events
on componentWillUnmount.

Categories

Resources