I've set up some tests of a React component that displays a table using Mocha. I can assert on its initial state but I have a click event which sorts the data that I'd like to test.
If I use React.addons.TestUtils.Simulate.click(theComponent) to try to test the sort.
I can see that the event is handled,
that the state change is fired
the data is sorted before calling setState
but when I assert against the component nothing has changed.
it('sorts the data when the year header is clicked', function() {
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var payTable = TestUtils.renderIntoDocument(
<PayTable payYears={data} />
);
var headers = TestUtils.scryRenderedDOMComponentsWithTag(payTable, 'th');
var yearHeader = headers[0];
TestUtils.Simulate.click(yearHeader.getDOMNode());
var columnValues = getYearColumnValues(payTable, TestUtils);
columnValues.should.match([ 'Year', '1066', '1067', '1068' ]);
});
Do I need to force an update? Re-read the component?
The code is available on Github.
I can test other aspects of the Component but not the Component values after setState
I had the same issue. The thing is, TestUtils.Simulate.click(yearHeader.getDOMNode()) is making a click on the DOM, which brings about sorting data and DOM manipulation. Even if you use jsDom and not a real dom, this manipulation itself is an async event. So, just by checking the DOM state right after the click event you are making a syncronous call, within which the DOM state has not been changed yet.
The solution is, use a setTimeout with 0 milliseconds timeout, and check the DOM state there. As for you example :
setTimeout(function() {
var columnValues = getYearColumnValues(payTable, TestUtils);
columnValues.should.match([ 'Year', '1066', '1067', '1068' ]);
},0)
This would make your DOM update its state and you will be able to test your component.
I've been spending a lot of time trying to find a clean way to work with the asynchronousity... Ended up making this for testing:
https://github.com/yormi/test-them-all
Under the hood, it uses componentDidUpdate lifecycle to listen on props/state/route changes.
Hopefully, it'll help you guys. Anyhow your opinion would be greatly appreciated :)
Related
I'm trying to set a state in an onTick event for a clock.
<Viewer>
<Clock
startTime={start.clone()}
stopTime={stop.clone()}
currentTime={start.clone()}
multiplier={50}
onTick={_.throttle(handleValue, 1000)} // this thing ticks every millisecond
/>
<Entity
ref={ref} // here is the ref to get the value I want to set state with
position={positionProperty}
tracked
selected
model={{ uri: model, minimumPixelSize: 100, maximumScale: 100.0 }}
availability={
new TimeIntervalCollection([
new TimeInterval({ start: start, stop: stop }),
])
}
/>
</Viewer>
Here is the handleValue function.
const handleValue = (clock) => {
//setting the state here ( I want to display the chaning the value over time)
setHeadingValue(ref.current.cesiumElement._properties._heading.getValue(clock.currentTime));
}
};
The problem is it looks like it tries to re-render over and over which freezes the app.
Due to the nature of setState, this behavior makes sense. But I feel like there is an answer that's escaping me.
May I have some insight as to what I could do? I'm out of ideas.
I'm using Resium ( a react library) and right now I'm setting the value using .getElementByID() and appending to the dom.. which defeats using react in the first place...
Here is a code sandbox: https://codesandbox.io/s/resium-cesium-context-forked-bpjuw?file=/src/ViewerComponent.js
Some elements are not showing because we need a token, but that does not affect the functionality I'm looking for. Just open the console of the code sandbox and go to the ViewerComponent.js
thank you for your help
As I see, the problem here not in the library, but in a way of managing calculation and visualization.
As few people already mentioned, for UI, user don't need more than 60fps, but for process sometimes we need more.
So the solution is to separate processing from visualization.
To be more generic, here is an example in pure JavaScript:
// Here it may be some component
const clockView = document.getElementById('speedyClock')
let state = null
// This function mimicking dispatch on state update
function setState(val) {
state = val
clockView.innerHTML = state
}
const FPS = 30
// Any state out of visualization scope
// Not the React state!!!
let history = []
let nextUiUpdate = Date.now()+(1000/FPS)
/// Super speedy process
setInterval(function() {
history.push(Math.random())
const now = Date.now()
// Update according to visual frame rate
if(now >= nextUiUpdate) {
// Prepare visual updates
setState(JSON.stringify({count: history.length, history}, null, 2))
history = []
nextUiUpdate = Date.now()+(1000/FPS)
}
}, 1)
<pre id="speedyClock"></pre>
I would suggest using requestAnimationFrame, instead of setInterval as their is no point rerendering the React tree more than once every screen refresh.
By using a combination of shouldComponentUpdate and setState you can effectively setState and prevent unnecessary renders.
This is my codesandbox url to show it.
You can access your wanted value as often as you want, but we need to prevent 1 render per millisecond with shouldComponentUpdate function.
In times past, my colleagues and I would typically write React Testing Library (RTL) tests for the main parent components, which often have many nested child components. That testing made sense and worked well. Btw the child components in question are very much dedicated to that parent component and not of the reusable variety.
But now we're trying to write RTL tests for every single component. Today I was trying to build tests for an Alerts component, which is the parent of an Alert component and about 4 levels down from the top-level component. Here's some sample code in my test file:
function renderDom(component, store) {
return {
...render(<Provider store={store}>{component}</Provider>),
store,
};
}
let store = configureStore(_initialState);
const spy = jest.spyOn(store, 'dispatch');
const { queryByTestId, queryByText, debug } = renderDom(
<Alerts question={store.getState().pageBuilder.userForm.steps[0].tasks[0].questions[1]} />,
store
);
I then started writing the typical RTL code to get the Alerts component to do its thing. One of these was to click on a button which would trigger an ADD_ALERT action. I stepped through all of the code and the Redux reducer was apparently working correctly with a new alert, as I intended, yet back in the Alerts component, question.alerts remained null whereas in the production code it was definitely being updated properly with a new alert.
I spoke with a colleague and he said that for this type of test, I would need to artificially rerender the component like this:
rerender(<Provider store={store}><Alerts question={store.getState().pageBuilder.userForm.steps[0].tasks[0].questions[1]} /></Provider>);
I tried this and it appears to be a solution. I don't fully understand why I have to do this and thought I'd reach out to the community to see if there was a way I could avoid using rerender.
It's hard to be certain without seeing more of your code, but my typical approach with RTL is to take the fireEvent call that simulates clicking the button and wrap it in an act call. This should cause React to finish processing any events from your event, update states, rerender, etc.
Alternatively, if you know that a particular DOM change should occur as a result of firing the event, you can use waitFor. An example from the React Testing Library intro:
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('alert'))
I'm currently facing a problem with React. I need to know when a component is removed from DOM.
All I find in component lifecycle is componentWillUnmount which gets called before the component is removed from DOM.
Is there any way to achieve this with React ?
In plain javascript ?
Thanks.
[EDIT]
#jpdelatorre
"Plain javascript" === Not using a library ;-)
My use case is the use of jsPlumb within a react component.
Basically jsPlumb is a library that draws svg in the DOM with position calculation.
In my main component there is a list of items.
Each item is a component.
On each rendered item I use JsPlumb to draw on it.
But... When I remove an item of the list the items are changing there positions in the DOM so I need to ask to jsPlumb to redraw things based on new positions. So that's why I need to know when component is fully removed from DOM.
componentWillUnmount is the correct lifecycle method. As you say, it is triggered prior to the component being removed. If you have something you need to wait to to until after it's been removed, you can use setTimeout with a short timeout value to schedule a callback once the current task completes.
componentWillUnmount is the life cycle method which can be hooked.
But as mentioned if you want to know in component1 about unmounting of component2 then you need to trigger an action in component2 in its lifecycle method, which should be subscribed in component1 and do some action in component1 listener.
As other already mentioned you need componentWillUnmount.
Here is a simple example in React(I added there some comment to get what is going one):
var Button = React.createClass({
componentWillUnmount: function(){
// console will show this message when compoent is being Unmounted("removed")
console.log('Button removed');
},
render() {
return <h1 ref='button_node'>
<ReactBootstrap.Button bsStyle="success">Red</ReactBootstrap.Button>
</h1>;
}
});
var RemoveButton = React.createClass({
getInitialState: function() {
// this state keep tracks if Button removed or not
//(you can use it for some redrawing or anything else in your code)
return {buttonMounted: true}
},
mountRedButton: function(){
ReactDOM.render(<Button/>, document.getElementById('button'));
this.setState({buttonMounted: true});
},
unmountRedButton: function(){
ReactDOM.unmountComponentAtNode(document.getElementById('button'));
this.setState({buttonMounted: false});
},
render() {
return <h1>
//based on condition if Button compoennt removed or not we show/hide different buttons
{ this.state.buttonMounted ? <ReactBootstrap.Button onClick={this.unmountRedButton } bsStyle="danger">Remove Red Button!</ReactBootstrap.Button> : null}
{ this.state.buttonMounted ? null :<ReactBootstrap.Button onClick={this.mountRedButton } bsStyle="success">Add Red Button!</ReactBootstrap.Button> }
</h1>;
}
});
// mount components
ReactDOM.render(<Button/>, document.getElementById('button'));
ReactDOM.render(<RemoveButton/>, document.getElementById('remove'));
Here is a full working example on JSFiddle
As about "plain javascript" - you are already using React JS,my example is based on React and ReactDom, nothing more(actually there is also react-bootstrap, I added it only for pretty buttons, it is not required at all)
Update:
What about use of MutationObserver? If you need a time when Node in DOM is removed but componentWillUnmount is fired before node removal(which seems unsutable for you) you can use it. Following my example with buttons:
var removalWatcher = new MutationObserver(function (e) {
var removalTimeStamp = '[' + Date.now() + '] ';
if (e[0].removedNodes.length) {
console.log('Node was removed', e[0].removedNodes, 'timestamp:', removalTimeStamp)
};
});
here is a JsFiddle with updated example. You can compare timestamps that are printed into console both for MutationObserver and React ComponentWillUnmount.
Ok thanks to all ! I found a solution that fits my needs and is (IMHO) clean...
In my parent component I handle componentDidUpdate lifecycle event and in there I can know (with prevProps and props) if the changes that are made need to fire my action...
class MyComponent extends Component {
// this one is called each time any props is changed
componentDidUpdate(prevProps, prevState) {
// if condition is matched then do something
}
}
I'm new to React testing and I'm having a hard time figuring out the following issue:
I'm trying to simulate an input onChange event. It's a text input that filters the results in a table. InteractiveTable has a controlled input field (ControlledInput) and an instance of Facebook's FixedDataTable.
This is the test:
let filter = ReactTestUtils.findRenderedComponentWithType(component, ControlledInput);
let input = ReactTestUtils.findRenderedDOMComponentWithTag(filter, 'input');
input.value = 'a';
ReactTestUtils.Simulate.change(input);
console.log(component.state);
On input change the component updates its state with the value of the input, but since setState is asynchronous, here the console.log will log out the previous state, and I can't query the structure of the component for testing, because it's not updated yet. What am I missing?
Edit: to be clear, if I make the assertion in a setTimeout, it will pass, so it's definitely a problem with the asynchronous nature of setState.
I found one solution, where I overwrite the componentDidUpdate method of the component:
component.componentDidUpdate = () => {
console.log(component.state); // shows the updated state
let cells = ReactTestUtils.scryRenderedComponentsWithType(component, Cell);
expect(cells.length).toBe(30);
done();
};
This wouldn't be possible if the component had its own componentDidUpdate method, so it's not a good solution. This seems to be a very common problem, yet I find no solution to it.
Normally when I run into similar scenarios when testing, I try to break things apart a little. In your current test, (depending on your flavor of test framework), you could mock the component's setState method, and simply ensure that it's called with what you expect when you simulate a change.
If you want further coverage, in a different test you could call the real setState with some mock data, and then use the callback to make assertions about what's rendered, or ensure other internal methods are called.
OR: If your testing framework allows for simulating async stuff, you could try calling that too and test the whole thing in one go.
I've got MDL running with React at the moment and it seems to be working fine at the moment.
I've got the Progress Bar appearing on the page as needed and it loads up with the specified 'progress' on page load when either entering in a number directly:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(10);
})
or when passing in a number via a Variable:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
It stops working after this though. I try to update the value via the Variable and it doesn't update. I've been advised to use this:
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(45);
to update the value but it doesn't work. Even when trying it directly in the console.
When trying via the Console I get the following Error:
Uncaught TypeError: document.querySelector(...).MaterialProgress.setProgress is not a function(…)
When I try to increment the value via the Variable I get no errors and when I console.log(value) I am presented the correct number (1,2,3,4...) after each click event that fires the function (it fires when an answer is chosen in a questionnaire)
What I want to know is if there's something obvious that I'm missing when using MTL and React to make components to work? There was an issue with scope but I seem to have it fixed with the following:
updateProgressBar: function(value) {
// fixes scope in side function below
var _this = this;
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
},
In React I've got the parent feeding the child with the data via props and I'm using "componentWillReceiveProps" to call the function that updates the progress bar.
I've used the "componentDidMount" function too to see if it makes a difference but it still only works on page load. From what I've read, it seems that I should be using "componentWillReceiveProps" over "componentDidMount".
It's being fed from the parent due to components sending data between each other. I've used their doc's and some internet help to correctly update the parent function to then update the progress bar in the separate component.
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.props.updateProgressBarValue(questionsAnsweredTotal);
}
The parent function looks like the following (I think this may be the culprit):
// this is passed down to the Questions component
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.setState({
progressBarQuestionsAnswered : questionsAnsweredTotal
})
}
I can post up some more of the code if needed.
Thank you
Looks I needed a fresh set of eyes on this.
I moved the function to the child of the parent. It seems that using document.querySelector... when in the parent doesn't find the element but when it's moved to the child where I do all the question logic it seems to be fine. It increments the progress correctly etc now :)
// goes to Questionnaire.jsx (parent) to update the props
updateProgressBarTotal: function(questionsAnsweredTotal) {
// updates the state in the parent props
this.props.updateProgressBarValue(questionsAnsweredTotal);
// update the progress bar with Value from questionsAnsweredTotal
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(questionsAnsweredTotal);
},
I had same problem in angular2 application.
You don't necessary need to move to the child component.
I found after struggling to find a reasonable fix that you simply have to be sure mdl-componentupgradedevent already occurred before being able to use MaterialProgress.setProgress(VALUE). Then it can be updated with dynamic value.
That is why moving to the child works. In the parent component mdl-componentupgraded event had time to occur before you update progress value
My solution for angular2 in this article
Adapted in a React JS application :
in componentDidMount, place a flag mdlProgressInitDone (initiated to false) in mdl-componentupgraded callback :
// this.ProgBar/nativeElement
// is angular2 = document.querySelector('.mdl-js-progress')
var self = this;
this.ProgBar.nativeElement.addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(0);
self.mdlProgressInitDone = true; //flag to keep in state for exemple
});
Then in componentWillReceiveProps test the flag before trying to update progress value :
this.mdlProgressInitDone ? this.updateProgress() : false;
updateProgress() {
this.ProgBar.nativeElement.MaterialProgress.setProgress(this.currentProgress);
}
After attaching the progress bar to the document, execute:
function updateProgress(id) {
var e = document.querySelector(id);
componentHandler.upgradeElement(e);
e.MaterialProgress.setProgress(10);
}
updateProgress('#questionnaireProgressBar');