When is it necessary to use `rerender` with the React Testing Library? - javascript

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'))

Related

Will JSX conditional rendering out of an object code split?

Will conditional rendering out of an object code split and lazy load as expected? Here's a short example of what I'm talking about.
const Component1 = lazy(() => import('some path'));
const Component2 = lazy(() => import('some path'));
const Component3 = lazy(() => import('some path'));
render () {
const { selectionIndex } = this.state;
<Suspense fallback={<div>Loading...</div>}>
{{
one: <Component1 />,
two: <Component2 />,
three: <Component3 />,
}[selectionIndex]}
</Suspense>
}
I want to know whether all three components will load on render, or just the one selected by selectionIndex. I'm trying to use this to conditionally select something to display based on a menu set by state, but I don't want to load everything at once.
They will not get rendered all at once. You can experiment by yourself, put console.log inside components is an easy way to find out.
React for web consists of two libs, "react" and "react-dom". "react" is in charge of encapsulating your logic intention into declarative data structures, while "react-dom" consumes these data structures and handles the actual "rendering" part of job.
The JSX element creation syntax <Component {…props} /> translates to plain JS as an API call to React.createElement(Component, props). The return value of this API call is actually just a plain object of certain shape that roughly looks like:
{
type: Component,
props: props
}
This is the aforementioned "declarative data structure". You can inspect it in console.
As you can see, calling React.createElement just return such data structure, it will not directly call the .render() method or functional component’s function body. The data structure is submitted to "react-dom" lib to be eventually "rendered".
So your example code just create those data structures, but the related component will not be rendered.
seems like its conditionally loaded based on selectionIndex. all the other components are not loaded at once.
P.S.: if you ever feel like which will get load first, just put a console log in that component and debug easily
conditionally load demo link - if you open this, the components are being loaded initially based on selectionIndex value being "one".
I'm not going to go into too much technical detail, because I feel like #hackape already provided you with a great answer as to why, point of my answer is just to explain how (to check it)
In general, I'd recommend you to download download the React Developer Tools
chrome link
firefox link
and then you can check which components are being rendered if you open the components tab inside your developer console. Here's a sandboxed example, best way to find out is to test it yourself afterall :-)
As you can see in the developer tools (bottom right), only the currently set element is being rendered

Prevent Vue.js from rendering component

My case is that I have a static component on the desktop and it must become carousel on mobile.
The component is rendered server side because of seo and I use is="my-component" to trigger vue on it. Typically when I duplicate the markup and check in created() the breakpoint, I can trigger some carousel constructor. However, if a breakpoint is set to desktop, vue will still rerender component which is redundant.
I know that one case may not be that effective, but I have a lot of performance and parsing problems because of vue in my previous project, so I need to keep performance in mind from the beginning.
Is it possible to somehow prevent rendering on beforeCreate() hook, but still be able to use it in some conditional?
As I have read your comment, and you would like to use something else that is not v-if, I can think only in two ways of doing it.
1) If you are using vue-router you can run make use of Lazy Loading Routes which is basically a function that can return an import('component') (which is a promise).
MobileCarousel.ts
import { isMobile } from '#/utils/mediaQuery';
const MobileCarousel = (): Promise<Vue> | void => {
if (!isMobile()) {
return;
}
return import('#/components/MobileCarousel/MobileCarousel.vue');
};
export default MobileCarousel;
Routes.ts
import MobileCarousel from '#/components/MobileCarousel/MobileCarousel.ts';
...
{
path: 'route-that-has-a-mobile-only-carousel',
name: 'mobile-only-carousel',
component: MobileCarousel,
},
enter code here
My only concern with this approach is related to the server-side rendering. As I never have played with server side-rendering with Vue I cannot assure you that it will work as you expect, you can give a try. Hope it helps you.
2) Apart from using Lazy Loading Routes, I believe that a Vue component with a render function that returns only if it is mobile also can be useful for your case.

Utilizing componentDidMount when switching between different instances of the same React component

According to the react documentation: http://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount we are adviced use componentDidMount for AJAX call that should be issued when a component is brought into view.
However, when switching between to instances of the same component with different props, componentDidMount is only called for the first component. So what are we supposed to do in this situation?
Currently I have the following workaround: In componentDidMount i do my AJAX call and In componentDidUpdate I compare old and new props to check if I am on a new "instance", and if so I do my AJAX call. But that seems exactly like a workaround. So my question is: is this really the way to do it?
I am aware that I could wrap my component in different empty components to solve my problem. However, this is not possible because we are building a data driven application that uses configurable components and it makes sense to use the same component with different configurations - which is where I'm running into problems.
I am aware that we are actually talking about react elements and not instances as such - witch I guess is part of the problem. Probably I have different react elements utilizing the same instance.
I have made a tiny example to illustrate the react behavior, using plain react (just to make sure I wasn't tricked by react-router or redux and what else we are using the real app):
class Foo extends React.Component {
componentDidMount() {
console.log('componentDidMount ' + this.props.foo);
}
componentDidUpdate() {
console.log('componentDidUpdate ' + this.props.foo);
}
render() {
return <div>Route is {this.props.foo}</div>;
}
}
function navigated() {
ReactDOM.render(
<Foo foo={window.location.hash} />,
document.getElementById('app')
);
}
window.addEventListener('hashchange', navigated, false);
navigated();
Initially when I go to #/bar I get 'componentDidMount #/bar' and when I go to #/baz i get 'componentDidUpdate #/baz'.
I seems like this unanswered question is a specific case of the same issue: React does not know when i render the same component
You can add the key property with unique value for each of hashes:
ReactDOM.render(
<Component hash={hash} key={hash} />, domNode
);
This will update the component every time when the hash is really changed.
https://facebook.github.io/react/docs/multiple-components.html#dynamic-children
TL DR - your 'workaround' looks correct to me
When you initially render the component componentDidMount is called, when you change the hash prop componentDidUpdate is called. It is still the same component, it is just that a specific prop has changed value. In your case, you have logic (running an AJAX call when hash changes) that is specific to your application. React does not known that the hash prop is special, you make is special by adding the logic in componentDidMount. So I believe you have a good interpretation of the React docs and this way of achieving your goal is perfectly valid.

Pass method from one file to another in react

Say in my first file, open.jsx, I have:
// Default Import Statements here
var open = React.createClass({
render() {
return (
<div>
<Dialog
title="Test"
ref="openDialog">
</Dialog>
</div>
);
},
_handleTouchTap() {
this.refs.openDialog.setState({open:true});
}
});
module.exports = open;
And in my app.jsx file I have:
const testComponent = React.createClass({
render() {
return (
<FlatButton label="Test" onTouchTap={this._handleTouchTap}/>
);
},
_handleTouchTap: function() {
Open._handleTouchTap();
}
});
module.exports = testComponent;
The error I am getting currently is:
Uncaught TypeError: Open._handleTouchTap is not a function
Does anyone know how I can pass methods in between files for React?
I want to call open.jsx's _handleTouchTap() method when they press the button in app.jsx.
When you call Open._handleTouchTap, you are attempting to call the method as if it was static, on the Open class. This method, however, is only available once an Open component has been instantiated. You must attach a ref to the component and call the method via this.refs.someOpenComponent._handleTouchTap().
You may want to provide more of your code so better examples can be provided.
Also, methods with an underscore in front of their names typically denote "private" methods, and should not be called from a different class. You may want to consider renaming this function so it is more clear what its purpose is.
I'm assuming you want to render some page with a button, and show the dialog as soon as someone presses the FlatButton. I also notice you're using material-ui, so let's go with that.
When starting any React project, it's a good idea to think about your component hierarchy. Because you're using material-ui and the Dialog component's opening is controlled by passing props, it's easiest to use the following approach.
Simple case
Use a root component App (in app.jsx), which mounts a button and mounts a dialog, but the dialog is initially in a hidden state (the "open" prop on Dialog defaults to false) so doesn't visually show up yet (even though it is mounted). In this case, you will want the button to set the open prop on Dialog to true as soon as the button is pressed.
Please note I would recommend separating most of this rendering stuff into separate components; for illustration purposes, let's keep everything in App.jsx.
The way you want to organise in this case is as follows:
// App.jsx (render + handle click function only)
render: function() {
return (
<div>
<FlatButton label="Test" onTouchTap={this._handleTapTestButton}/>
<Dialog
title="Test"
open={this.state.dialogOpen}>
<div>My dialog contents</div>
</Dialog>
</div>
);
},
_handleTapTestButton: function() {
this.setState({dialogOpen: !this.state.dialogOpen}); // flip the state
}
See? No refs needed even (and that's good!). Now, this works fine if your Dialog component is located nice and close to your FlatButton.
More complex case: Dialog is far away from FlatButton
Your next question might be "how can I organise this when the Dialog component is nested somewhere deep inside a totally different component that is not a child or parent of the App.jsx component", but instead a sibling?
Well, this smells a little to me (just an opinion). It's not an anti-pattern per sé, but if you can avoid this, I would recommend you do. Ie: for your own convenience and for maintainability's sake, try to keep components that naturally interact with each other close (in terms of parent-child) to each other in the component hierarchy. This way, you can communicate pretty easily using props (see React's info on this. That's definitely not an absolute rule though, there are plenty of reasons to deviate from that.
Let's assume you have a valid case for not doing that, and even worse: the component are siblings, not direct or indirect grandparent/parent/child.
In that case, you have two options:
Use a store and associated events (or any other javascript code that communicates state) to communicate the state change to the other component (ie using Flux, Redux, or whatever you prefer). In this case, when the button is clicked, you fire an event somewhere that gets picked up by the other component. This event triggers a state change in the other component. Warning: this can get unmanageable pretty quickly, which is one of the reasons state-managing-frameworks like Flux and Redux exist.
OR, onTouchTap, have the FlatButton call a function that was passed down from a shared parent component. This function then flips the state at the shared parent component, and passes this state as props to the Dialog. Ie, when both components share a grandparent somewhere, you can define a function at the grandparent level and pass that function as a prop down to the FlatButton. The function's role is to change the state at the grandparent (dialogOpen). This state is then passed down one or more components as a prop all the way down the hierarchy until it ends up at the Dialog, which will auto show itself as the prop switches to true.
There are serious advantages/disadvantages to either approach. The first approach leaks your UI rendering logic into your stores (which is usually inevitable anyway, but can be managed using things like Flux), the second leaks it into the component hierarchy (tricky for maintainability) and tends to create tight coupling (yuck).

Best pattern to rerender my react component?

I have a global data object I update and then I call React.renderComponent() again on the top/main component.
Is this the right pattern for triggering this update?
You should generally pass the data object into the component as a prop, even if it's a global variable. This lets you test the component, and also use it elsewhere without being tied to that global.
As Mike said, there's nothing wrong with using React.renderComponent to update it.
He also mentioned flux, but that's overkill for this. A simple event emitter where you do something like .emit('change', newData), and the component is listening for the change event tends to be better for simpler cases. See my answer to this question for an example of how that can be done.
This is the correct pattern. React.renderComponent will either mount a component for the first time, or get an already mounted component to update.
If you're using a global object though, you might want to look in to the Flux architecture here:
http://facebook.github.io/flux/docs/overview.html
I had the same problem and asked myself if I really needed to re-render the component.
You can do so with this.forceUpdate() but it's not advisable. As React docs states:
You should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.
So what I did was create a data property like exists and test it:
// renderDeleteButton() is being called on render()
renderDeleteButton () {
if (!this.props.store.exists) {
return;
}
return(
<DeleteButton
...
deleteAction={this.delete} />
);
}
Whenever I delete/save, I toggle exists and component will show up or hide based on that. React handles that for me.

Categories

Resources