How do I test the method shouldComponentUpdate? - javascript

I would like to test out the method, shouldComponentUpdate but the test I wrote doesn't catch the side effects. How do I test this?
I have tried the solution proposed here but there are issues with it which I will show in the code: How to unit test React Component shouldComponentUpdate method
I have tried using shallow rather than mount but when I did that, my test fails at this point:
expect(shouldComponentUpdate).to.have.property("callCount", 1);
// AssertionError: expected [Function: proxy] to have a property 'callCount' of 1, but got 0
Here's my component's shouldComponentUpdate function:
shouldComponentUpdate(nextProps, nextState) {
if (this.props.storageValue !== nextProps.storageValue) {
localStorage.setItem(constantFile.storageKey, nextProps.storageValue);
localStorage.setItem(constantFile.timestampKey, now());
this.setState({
newStorage: nextProps.storageValue
});
return true;
}
...
return false;
}
Here's my test:
it("Test shouldComponentUpdate", () => {
/* eslint-disable one-var */
const wrapper = mount(
<Provider store={store}>
<MyComponent />
</Provider>),
shouldComponentUpdate = sinon.spy(CapitalHomeAccessPointContainer.prototype, "shouldComponentUpdate");
wrapper.setProps({
storageValue: true
});
expect(shouldComponentUpdate).to.have.property("callCount", 1);
expect(shouldComponentUpdate.returned(true)).to.be.equal(true);
expect(localStorage.getItem(constantFile.timestampKey)).to.equal(someExpectedTimestamp);
});
expect(shouldComponentUpdate).to.have.property("callCount", 1);
fails with
AssertionError: expected false to equal true
Why does this happen? I thought shouldComponentUpdate should have returned true? Later, I'm hoping to test the state and local storage side effects but I do not understand this error.

You're handling of the shouldComponentUpdate lifecycle is wrong. You don't setState within it, instead you only return a boolean (true or false) to let React know when to re-render the DOM. This allows you to block updates from outside props. For example:
shouldComponentUpdate(nextProps, nextState) {
return this.state.newStorage !== nextState.newStorage
}
The above states: if current newStorage state doesn't match next newStorage state, then re-render the component. The above would block prop changes from rerendering the component. If you changed storage props, it WOULD NOT update this component because the state wasn't changed. You want to use shouldComponentUpdate to prevent unnecessary double re-renders and/or to prevent a parent component's re-renders from unnecessarily re-rendering a child component. In this case, you won't need to use this lifecycle method.
Instead, you should either be using static getDerivedStateFromProps for props to update state before a render OR using componentDidUpdate to update state after a render.
In your case, since you only want to update state if this.props.storageValue changes, then you should use componentDidUpdate:
componentDidUpdate(prevProps) {
if (this.props.storageValue !== prevProps.storageValue) {
localStorage.setItem(constantFile.storageKey, nextProps.storageValue);
localStorage.setItem(constantFile.timestampKey, now());
this.setState({ newStorage: nextProps.storageValue });
}
}
The above checks if the current props don't match previous props, then updates the state to reflect the change.
You won't be able to use static getDerivedStateFromProps because there's no comparison between old and new props, but only comparison to incoming props and what's current in state. That said, you COULD use it if you stored something in state that is directly related to the storage props. An example might be if you stored a key from the props and into state. If the props key was to ever change and it didn't match the current key in state, then it would update the state to reflect the key change. In this case, you would return an object to update state or null to not update the state.
For example:
static getDerivedStateFromProps(props, state) {
return props.storage.key !== state.storageKey
? { storageKey: props.storage.key }
: null
}
When it comes to testing, you'll want manually update props to affect state changes. By testing against state changes, you'll indirectly test against componentDidUpdate.
For example (you'll have to mock localStorage, otherwise state won't update -- I'd also recommend exporting the class and importing it, instead of using the Redux connected component):
import { MyComponent } from "../MyComponent.js";
const initialProps = { storageValue: "" };
describe("Test Component", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<MyComponent { ...initialProps } />);
})
it("updates 'newStorage' state when 'storageValue' props have changed, () => {
wrapper.setProps({ storageValue: "test" });
expect(wrapper.state('NewStorage')).toEqual(...); // this should update NewStorage state
wrapper.setProps({ someOtherProp: "hello" )};
expect(wrapper.state('NewStorage')).toEqual(...); // this should not update NewStorage state
});
});

Related

React can't perform state update on unmounted component

I'm using the following method to control my header from other components. However I'm getting the old "can't perform a react state update on unmounted component" error when changing page
export const store = {
state: {},
setState(value) {
this.state = value;
this.setters.forEach(setter => setter(this.state));
},
setters: []
};
store.setState = store.setState.bind(store);
export function useStore() {
const [ state, set ] = useState(store.state);
if (!store.setters.includes(set)) {
store.setters.push(set);
}
return [ state, store.setState ];
}
My header then uses it to set a class and control if it needs to be black on white or white on black
const Header = () => {
const [type] = useStore();
render( ... do stuff )
};
And my components on page import useStore and then call setType based on a number of factors, certain layouts are one type, some others, some vary depending on API calls so there are a lot of different Components that need to call the function to set the headers state.
const Flexible = (props) => {
const [type, setType] = useStore();
if( type !== 'dark ){ setType('dark') }
... do stuff
};
The header its self is always on page, is before and outside the router and never unmounts.
This all works perfectly fine and sets the headers sate. However when I change page with React Router I get the can't set state error. I can't see why I would get this error. I first thought that the Component might be trying to run again with react router so I moved the code to set the headers state into a useEffect that only runs on initialisation but that didn't help.
You only ever add to the setters, never remove. So when a component unmounts, it will remain in the setters, and the next time some other part of the app tries to set the state, all the setters get called, including the setter for the unmounted component. This then results in the error you're seeing.
You'll need to modify your custom hook to make use of useEffect, so that you can have teardown logic when unmounting. Something like this:
export function useStore() {
const [ state, set ] = useState(store.state);
useEffect(() => {
store.setters.push(set);
return () => {
const i = store.setters.indexOf(set);
if (i > -1) {
store.setters.splice(i, 1);
}
}
}, []);
return [ state, store.setState ];
}
This error is pretty straightforward it means that you are mutating the state (calling setState) in a component that is not mounted.
This mostly happens with promises, you call a promise, then when its resolved you update the state, but if you switch the page before it resolves, when the promise is resolved it still tries to update the state of a component that now its not mounted.
The easy and "ugly" solution, is to use some parameter that you control in componentWillUnmout to check if you still need to update the state or not like this:
var mounted = false;
componentWillMount(){
mounted = true
}
componentWillUnmount(){
mounted = false
}
// then in the promise
// blabla
promise().then(response => {
if(mounted) this.setState();
})

ReactJS ComponentWillMount() after passing Props

I want to ask why the child ( ComponentWillMount() ) component is only once rendered, once I am passing props to it everytime on onClick.
Once I click some button that is passing props to the child, the ComponentWillMount() of child is not triggering again, only in the first click only.
Parent Component:
render(){
return(
<div>
<AppModuleForm
editID = {this.state.editID}
editURL = {this.state.editURL}
editConf = {this.state.editConf}
editDesc = {this.state.editDesc}
editIcon = {this.state.editIcon}
editParent = {this.state.editParent}
editOrder= {this.state.editOrder}
status={this.state.status}
moduleList={this.state.moduleList}
updateAppModuleTree={this.updateAppModuleTree.bind(this)}/>
</div>
)
}
Child Component:
constructor(props){
super(props)
console.log(this.props.editDesc)
this.state={
url:'',
description:'',
parentID:'',
order:'',
configuration:'',
icon:'',
parentIDList:[],
urlDuplicate: false,
isSuccess: false,
errorMessage: '',
}
}
componentWillMount(){
if(this.props.status==='edit'){
let {
editURL,
editDesc,
editParent,
editConf,
editIcon,
editOrder} = this.props
this.setState({
url:editURL,
description:editDesc,
parentID:editParent,
order:editOrder,
configuration:editConf,
icon:editIcon,
})
}
}
componentWillReceiveProps(nextProps){
if(nextProps.status != this.props.status){
if(this.props.status==='edit'){
let {
editURL,
editDesc,
editParent,
editConf,
editIcon,
editOrder} = this.props
this.setState({
url:editURL,
description:editDesc,
parentID:editParent,
order:editOrder,
configuration:editConf,
icon:editIcon,
})
}
}
}
ComponentWillMount is mounting lifecycle method which will be called before mounting your component hence initialisation can be done in that while ComponentWillReceiveProps will be called once props are changed and you will get changes in nextProps parameter.
First you need to read https://reactjs.org/docs/state-and-lifecycle.html
and understand where to use props and why you need to pass something into component state.
From http://lucybain.com/blog/2016/react-state-vs-pros/
So when would you use state?
When a component needs to keep track of information between renderings
the component itself can create, update, and use state.
So you shouldn't transfer to state anything that will not change internally during component live cycle. As I can see all props those you pass to component are most likely will not be changed from within the component, all callbacks and icons you should take from props in component jsx.
If you have some editable data that you pass into its props from parent, on component mount (use componentWillMount()) you can copy that data to component state.That means all data will be stored in component internally and will not being overwritten on every render() call from passed props.
If you need to check if new props contains changes you can use componentWillReceiveProps(newProps) and there you can compare newProps with this.props and and process changes if needed.
Also i can suggest you to rename component callbacks handlers with respect to naming best practices:
<div>
<AppModuleForm
handleEditID = {this.onEditID}
handleEditURL = {this.onEditURL}
handleEditConf = {this.onEditConf}
handleEditDesc = {this.onEditDesc}
handleEditIcon = {this.onEditIcon}
handleEditParent = {this.onEditParent}
handleEditOrder= {this.onEditOrder}
status={this.state.status}
moduleList={this.state.moduleList}
updateAppModuleTree={this.updateAppModuleTree.bind(this)}/>
</div>
And I dont see any reasonable purpose to declare or to store functions in components state. So you can consider to move your handlers this.state.editID
etc. to parent component this scope. Like that
onEditId = () => { /* function code */ }
If you use arrow function = () => it automatically binds to component this and you don't need to bind them manually like you do in
{this.updateAppModuleTree.bind(this)}
After all that may be you will understand more clearly how you should manage your components life cycle and your problem will no longer be relevant.

implementation of component did mount in stateless component

Im new to react, i'm trying to implement/learn stateless component, Im having difficulty in using component will mount in stateless component.
My code
const Terms = (actions, commonReducer) => {
componentDidMount() {
actions.userActions()
}
return (
<div className="jobUpdate">
<form onSubmit={(e) => {
e.preventDefault(); actions.userInput(document.getElementById('enteredVal').value)
}}>
<input type="text" id="enteredVal" />
<button type="submit"></button>
</form>
</div>
);
};
I know the stateless component does not have life cycle hooks, but wanted alternate approach to preform component did mount in stateless component.
Any help with this is much appreciated. Thanks in advance
You could always wrap the component in another component, using a pattern known as higher-order components.
A higher-order component (HOC) is a function that takes a component and returns a new component.
Perhaps the most widely used example of this technique is react-redux, which uses the connect() method to create components connected to the redux store.
Instead of creating your own HOC, there are also libraries out there that can do it for you, such as react-redux-lifecycle.
However, what you are attempting with this component is not a very common pattern -- it is much more common to instead keep the handling of business and data in a container component, and leave presentational components to inherit store actions and data from props. Check out Dan Abramov's Presentational and Container Components for a very good overview on how and why to break down components into these two categories!
Starting in React 16.8, you can accomplish the same kind of functionality using a useEffect hook.
In your specific example, we'd have something like this:
import React, { useEffect } from 'react';
// other imports and declarations
function Example() {
// Similar to componentDidMount
useEffect(() => {
// This function will be run on component mount
myAction();
}, []); // The second argument of [] tells react to only perform the effect on mount
return (
<div>
... your component
</div>
);
}
export default Example;
The docs do a great job of explaining this, and I'd encourage you to read up on it. Keep in mind that it's not exactly the same thing going on behind the scenes, and so the patterns will not be a one-to-one correspondence; but these patterns should help with the majority of your cases.
Just know the following basic ideas:
The first argument of a useEffect hook is a "side effect" function. It is always run after the first component's render, and then conditionally afterwards.
This "side effect" function can return a "cleanup" function. The "cleanup" function is run just before the next time the "side effect" function is run. It will always be run before unmounting the component.
The second, optional, argument of a useEffect hook is an array of dependencies.
If any value in the dependency changes, the "side effect" function will be run after the next render.
Anyway, in the meantime, here's a few patterns to emulate class component behavior.
componentDidMount + componentDidUpdate
useEffect(() => {
console.log("This line will be run after each render ");
});
componentDidUpdate when a given value changes
// get myValue from component props
const { myValue } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue changed");
}, [myValue]);
componentDidUpdate when a given value changes, pt. 2
// get myValue from component props
const { myValue } = props;
const myCondition = myValue === "yes";
useEffect(() => {
console.log('This line will be run after each render where the returned value of the statement `myValue === "yes"` changes from false to true or true to false ');
}, [myCondition]);
componentDidUpdate when a given value changes, pt. 3
// get myValue from component props
const { myValue, myValue2 } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue OR myValue2 changed");
}, [myValue, myValue2]);
componentDidMount
useEffect(() => {
console.log("This line will be run only after the first render ");
}, []);
componentWillUnmount
useEffect(() => {
// nothing will be run as a side effect...
return () => {
// but this will be run as clean up
console.log("This line will be run just before the component unmounts");
};
}, []);
componentDidUpdate (without componentDidMount)
import React, {useEffect, useRef} from 'react';
export default function MyComponent() {
// create a reference value which does not trigger a re-render when changed
const isMounted = useRef(false);
useEffect(() => {
if( isMounted.current === false ){
// on mount, set the ref to true
isMounted.current = true;
} else {
// the component is already mounted
console.log("This line will be run after each render except the first ");
}
});
return (<div />);
}
Hope this will be useful to someone.
Throw a dumb work around using HOC (high order component)
const withLifecycles = (MyStatelessComp) => class extends React.PureComponent {
static propTypes = {}
static displayName = "withPure(xxx)"
state = {}
componentDidMount() {}
render() {
return <MyStatelessComp {..this.state, ...this.props} />
}
}
then use it as
MyStatelessWithLifecycles = withLifecycles(props => {
...
return <Bla />
}
Though not sure what's the reason for a stateless component to have the lifecycles there, it's meant to be pure and simple (presentation only).

React router + redux navigating back doesn't call componentWillMount

Currently I pre-load data from api in container component's lifecycle method componentWillMount:
componentWillMount() {
const { dept, course } = this.props.routeParams;
this.props.fetchTimetable(dept, course);
}
It is called when user navigates to route /:dept/:course, and it works fine, until you navigate from let's say: /mif/31 to /mif/33 and then press back button. The component is not actually reinitialized, so the lifecycle method is not called, and the data isn't reloaded.
Is there some sort of way to reload data in this case? Should I maybe use another method of preloading data? I see react router emits LOCATION_CHANGE event on any location change, including navigating back, so maybe I can somehow use that?
If it matters, here's is how I implement data loading:
import { getTimetable } from '../api/timetable';
export const REQUEST_TIMETABLE = 'REQUEST_TIMETABLE';
export const RECEIVE_TIMETABLE = 'RECEIVE_TIMETABLE';
const requestTimetable = () => ({ type: REQUEST_TIMETABLE, loading: true });
const receiveTimetable = (timetable) => ({ type: RECEIVE_TIMETABLE, loading: false, timetable });
export function fetchTimetable(departmentId, courseId) {
return dispatch => {
dispatch(requestTimetable());
getTimetable(departmentId, courseId)
.then(timetable => dispatch(receiveTimetable(timetable)))
.catch(console.log);
};
}
You need to use componentWillReceiveProps to check if new props (nextProps) are same as existing props (this.props). Here's relevant code in Redux example: https://github.com/reactjs/redux/blob/e5e608eb87f84d4c6ec22b3b4e59338d234904d5/examples/async/src/containers/App.js#L13-L18
componentWillReceiveProps(nextProps) {
if (nextProps.dept !== this.props.dept || nextProps.course !== this.props.course) {
dispatch(fetchTimetable(nextProps.dept, nextProps.course))
}
}
I might be wrong here, but I believe the function you are looking for is not componentWillMount but componentWillReceiveProps,
assuming you are passing down variables (like :courseId) from redux router to your component, using setState in componentWillReceiveProps should repaint your component.
Otherwise, you can subscribe to changes in your store: http://redux.js.org/docs/api/Store.html
Disclaimer: I probably know less about redux then you.

How to listen state changes in react.js?

What is the angular's $watch function equivalent in React.js?
I want to listen state changes and call a function like getSearchResults().
componentDidMount: function() {
this.getSearchResults();
}
The following lifecycle methods will be called when state changes. You can use the provided arguments and the current state to determine if something meaningful changed.
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
In 2020 you can listen to state changes with the useEffect hook like this
export function MyComponent(props) {
const [myState, setMystate] = useState('initialState')
useEffect(() => {
console.log(myState, '- Has changed')
},[myState]) // <-- here put the parameter to listen
}
I haven't used Angular, but reading the link above, it seems that you're trying to code for something that you don't need to handle. You make changes to state in your React component hierarchy (via this.setState()) and React will cause your component to be re-rendered (effectively 'listening' for changes).
If you want to 'listen' from another component in your hierarchy then you have two options:
Pass handlers down (via props) from a common parent and have them update the parent's state, causing the hierarchy below the parent to be re-rendered.
Alternatively, to avoid an explosion of handlers cascading down the hierarchy, you should look at the flux pattern, which moves your state into data stores and allows components to watch them for changes. The Fluxxor plugin is very useful for managing this.
Since React 16.8 in 2019 with useState and useEffect Hooks, following are now equivalent (in simple cases):
AngularJS:
$scope.name = 'misko'
$scope.$watch('name', getSearchResults)
<input ng-model="name" />
React:
const [name, setName] = useState('misko')
useEffect(getSearchResults, [name])
<input value={name} onChange={e => setName(e.target.value)} />
I think you should be using below Component Lifecycle as if you have an input property which on update needs to trigger your component update then this is the best place to do it as its will be called before render you even can do update component state to be reflected on the view.
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
If you use hooks like const [ name , setName ] = useState (' '), you can try the following:
useEffect(() => {
console.log('Listening: ', name);
}, [name]);
Using useState with useEffect as described above is absolutely correct way. But if getSearchResults function returns subscription then useEffect should return a function which will be responsible for unsubscribing the subscription . Returned function from useEffect will run before each change to dependency(name in above case) and on component destroy
It's been a while but for future reference: the method shouldComponentUpdate() can be used.
An update can be caused by changes to props or state. These methods
are called in the following order when a component is being
re-rendered:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
ref: https://reactjs.org/docs/react-component.html
I use this code to see which one in the dependencies changes. This is better than the pure useEffect in many cases.
// useWatch.js
import { useEffect, useMemo, useRef } from 'react';
export function useWatchStateChange(callback, dependencies) {
const initialRefVal = useMemo(() => dependencies.map(() => null), []);
const refs = useRef(initialRefVal);
useEffect(() => {
for(let [index, dep] of dependencies.entries()) {
dep = typeof(dep) === 'object' ? JSON.stringify(dep) : dep;
const ref = refs.current[index];
if(ref !== dep) {
callback(index, ref, dep);
refs.current[index] = dep;
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
}
And in my React component
// App.js
import { useWatchStateChange } from 'useWatch';
...
useWatchStateChange((depIndex, prevVal, currentVal) => {
if(depIndex !== 1) { return } // only focus on dep2 changes
doSomething("dep2 now changes", dep1+dep2+dep3);
}, [ dep1, dep2, dep3 ]);

Categories

Resources