Testing React components - mount vs shallow and simulating events - javascript

I have the following React components that I'm trying to test by simulating entry into the input field with id=componentCount. I started using React for the first time less than one week ago, so this framework is very new to me and any help would be appreciated.
I'm also using Semantic UI React for the CSS framework, hence the Form.Group and Form.Input tags
export class SoftwareForm extends React.Component {
constructor(props) {
super(props);
this.state = {
componentCount: 0
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<Form id='softwareForm'>
<Form.Group>
<Form.Input label='Name' placeholder='Component Name' id='componentName' type='text'/>
<Form.Input label='Description' placeholder='Component Description' id='componentDescription' type='text'/>
<Form.Input name='componentCount'
label='Count'
placeholder='Count'
id='componentCount'
type='number'
min='0'
value={this.state.componentCount}
onChange={this.handleInputChange}/>
</Form.Group>
</Form>
);
}
}
Test script
describe('<SoftwareForm />', () => {
it('Count field accepts text input', () => {
const softwareFormComponent = mount(<SoftwareForm />);
const countField = softwareFormComponent.find('#componentCount');
countField.simulate('change', {target: {value: 3}});
expect(softwareFormComponent.state('componentCount')).toBe(3);
});
});
The above test script uses mount for full rendering, but I get the following error, "Method “simulate” is only meant to be run on a single node. 4 found instead."
If I use a shallow mount, the test output is the following
Expected value to be (using ===):
3
Received:
0
This tells me that the simulate method is not working as expected.
Several questions:
Can a shallow render be used in this scenario since I can use the find method to search for the element with the 'componentCount' id attribute or is a full render with a mount necessary since I'm trying to manipulate an input element which is a child of the Form.Group?
Regardless of the answer the question 1, when mount is used, the wrapper that is returned contains 4 nodes - what are these 4 nodes and which one am I supposed to call simulate on if a full render is in fact needed in this case?
I'm struggling due to not having a clear understanding of the structure of the wrapper object that is returned by either shallow or mount.
The idea of the last two statements in the test script is to simulate the change to the value of the number in the input field, have the onChange event handler trigger an update of the componentCount state value and then the perform a comparison using the assert statement. Is this the correct approach?

Trying to answering your questions:
You could use mount, which create test on a full DOM rendering and it is ideal for use cases where you have components that may interact with DOM APIs, or may require the full lifecycle in order to fully test the component.
You can see that find returns using .debug().

After a few more attempts, I was able to get the test to work.
A shallow mount ended up being sufficient in this case even though I was interacting with input DOM element.
The simulate method used in the test script was changed to the following,
"countField.simulate('change',{
target: { name: "componentCount", value: 3 } });

Related

Best way to get values from multiple complex child components?

I have a parent component that has base data called script, which has multiple sequences and each sequence is composed of multiple items (inputs, dropdown, ... ).
Now I need the updated data in parent since I want to put a save button that is going to save all forms with one click.
It looks something like this:
I tried two ways of handling this:
That each child had an onChange property
in which parent sets the state with the new data. But the problem
here is, that since this is quite a complex form, it re-renders
everything each time, so there was a noticeable delay when typing in
inputs.
The "bad" of just changing the props object in a child,
which is fast, but I know it is a bad practice.
What is the best way of handling forms on a scale like this? Should it be set up differently?
This is a question I've spent some time struggling with myself. There are multiple ways to maintain child state at a higher level; however, I've found that in your particular situation it is often best to use Redux.
To be clear, I generally avoid Redux at all costs (in favor of React's context), but Redux gives you the ability to subscribe to a particular piece of state in your child components. Listening to one piece of state in a child component will prevent your parent and sibling components from updating when you only need a single child to update. This ends up being far more efficient when handling multiple forms at one time.
For example, the following component will only listen to state updates that affect its own state. These updates will bypass the forms parent and sibling components:
import React from 'react';
import { connect } from 'react-redux';
import * as actions from 'redux/actions';
// Custom component
import { InputField } from 'shared';
const FormOne = ({ me, actions }) => (
<form>
<InputField
inputId="f1f1"
label="field one"
value={me.fieldOne}
onChange={(e) => actions.setFormOneFieldOne(e.target.value)}
/>
<InputField
inputId="f1f2"
label="field two"
value={me.fieldTwo}
onChange={(e) => actions.setFormOneFieldTwo(e.target.value)}
/>
<InputField
inputId="f1f3"
label="field three"
value={me.fieldThree}
onChange={(e) => actions.setFormOneFieldThree(e.target.value)}
/>
</form>
);
export default connect(state => ({ me: state.formOne }), actions)(FormOne);
In the above example FormOne is only listening for its own state updates; whereas, similar logic utilizing context instead of Redux will cause the entire component tree that the context provider is wrapping to update (including parent and sibling components):
import React, { useContext } from 'react';
// Custom component
import { InputField } from 'shared';
// Custom context - below component must be wrapped with the provider
import { FormContext } from 'context';
const FormTwo = () => {
const context = useContext(FormContext);
return(
<form>
<InputField
inputId="f2f1"
label="field one"
value={context.state.formTwo.fieldOne}
onChange={(e) => context.setFormTwoFieldOne(e.target.value)}
/>
<InputField
inputId="f2f2"
label="field two"
value={context.state.formTwo.fieldTwo}
onChange={(e) => context.setFormTwoFieldTwo(e.target.value)}
/>
<InputField
inputId="f2f3"
label="field three"
value={context.state.formTwo.fieldThree}
onChange={(e) => context.setFormTwoFieldThree(e.target.value)}
/>
</form>
);
};
export default FormTwo;
There are some improvements that can be made to both of the above components, but they are meant to serve as an example for how to connect child components to an elevated state. It is also possible to connect to a single parent component using props, but that is the least efficient option possible, and will clutter up your architecture.
Key takeaway: Use Redux for your use case. It's the most efficient option if it is implemented correctly.
Good luck!
Wrap all the forms in a component that will only deal with saving all the forms data and running the "save all" function:
the wrapper component should have a state the includes all the forms data, it should probably look something like this:
class Wrapper Component extends React.Component {
constructor(props) {
super(props);
this.state = {
formsData: {},
};
}
}
formsData should probably be structured pretty much like that:
{ 0: { title:"text", type:"video", etc:"etc" },
1: { title:"text", type:"video", etc:"etc" }}
the keys (0,1, etc..) represents the form id, and can be set to any unique modifier each for has.
then make the wrapper component handle the onChange for every individual form -> every change on each individual form should uplift the new state (new updated data) and update the formsData state obj accordingly:
const onChange(formData) {
const formattedData = {[formData.id]: {...formData}}
this.setState({formsData: {...formsData, ...formattedData}})
}
* This is just an example of a case where in each change in each form you uplift the entire data object, you can do it in many ways
Than, the save all button should also be handled in the wrapper component, and uplift all the data you stored with it to the relevant function in a parent component / handle it itself.
Good luck!
Lifting state up is indeed the correct way of doing this. To optimize child sections you can use
PureComponent ==> https://reactjs.org/docs/react-api.html#reactpurecomponent
AKA Memoized Component ==> https://reactjs.org/docs/react-api.html#reactmemo
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
Also if you are within the hooks universe checkout
useCallback : https://reactjs.org/docs/hooks-reference.html#usecallback
useMemo : https://reactjs.org/docs/hooks-reference.html#usememo
If you are using Redux by any chance remember to look at
reselect : https://github.com/reduxjs/reselect

React setState not firing in first time

i saw similar questions but can't get some solution.
i have some input when you paste some text to input i'm trying to set it to state and render this text. But when paste in first time state not change in second time when paste everything works.
I don't understand why it happen i already used some setTimeOut function too for it but not helped.
here is my code:
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor() {
super();
this.state = {
no: ""
};
}
pasteSomeText = e => {
this.setState({
no: e.target.value
});
};
render() {
return (
<div style={styles}>
{this.state.no}
<input onPaste={event => this.pasteSomeText(event)} />
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here is codeSandBox example: https://codesandbox.io/s/1-react-hello-world-tvscf?fontsize=14
To get the data from the event, use e.clipboardData.getData('text'). See https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
e.target.value gives you the value for the text field before it updates with the paste data, so you can cancel/modify the paste if you want.
pasteSomeText(e) {
this.setState({
no: e.clipboardData.getData('text')
});
};
Side note: with the call from the HTML, you don't need to use this arrow function (which uses more memory), see my example above
Basically the reason why you get empty state on firts onpaste call is that onpaste fires before the input's value is changed. So you get empty event.target.value and for all other calls you get previous value.
And if you still decide to keep going with onpaste you should keep in mind that you can get pasted value with event.clipboardData.getData('Text') but it can differ from what have been pasted in input because of different behavior of clipboardData for different input types.
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
So for work with clipboard data I will recommend to use DataTransfer module from https://www.npmjs.com/package/fbjs to get more consistant behavior in different systems and browsers.
Is a diferente kind of event you will need to get the clipboard data from the event and set it to the state
const value = e.clipboardData.getData('text')
use
onChange
instead of using
onPaste
Updated https://codesandbox.io/s/1-react-hello-world-h2jgv

Alter react component state properly

I'm working at a project in which I have to display graphs.
For displaying graphs I'm using vis.js in particular react-vis-network a implementation for using parts of vis.js in React with its stateful approaches.
Initial nodes and edges are loaded before my component is mounted and are passed as props for an initial state.
I attached two eventHandler one direct to a vis.js (the underlying DOM library) and the other at a decorator (button).
The desired/expected behaviour:
A node is removed by clicking either the node or the corresponding button.
Observed behavior:
Sometimes a node is removed and sometimes a node just disappears for a few ms and is reattached but without a decorator/button.
I already tried to start with an empty state and attaching the nodes,edges in componentDidMount() but I got the same result. I hope you can give me a hint.
BTW: Is the way I use to attach components a/the right way?
Every other help to improve my class is appreciated also
class MyNetwork extends Component {
constructor(props){
super(props);
let componentNodes = [];
for (let node of props.nodes){
componentNodes.push(this.createNode(node));
}
let componentEdges = [];
for (let edge of props.edges){
componentEdges.push(this.createEdge(edge));
}
this.state = {nodes:componentNodes,edges:componentEdges};
["_handleButtonClick"].forEach(name => {
this[name] = this[name].bind(this);
});
}
createNode(node){
const Decorator = props => {
return (
<button
onClick={() =>{this._handleButtonClick(props);}}
>
Click Me
</button>
);
};
node.decorator = Decorator;
return React.createElement(Node,{...node})
}
createEdge(edge){
return React.createElement(Edge,{...edge})
}
addNode(node){
this.setState({
nodes: [...this.state.nodes, this.createNode(node)]
})
}
_handleButtonClick(e) {
if(e){
console.log("clicked node has id:" +e.id);
this.removeNode(e.id);
}
}
onSelectNode(params){
console.log(params);
window.myApp.removeNode(params[0]);
}
removeNode(id) {
let array = [...this.state.nodes]; // make a separate copy of the array
let index = array.findIndex(i => i.props.id === id );
array.splice(index, 1);
this.setState({nodes: array});
}
render() {
return (
<div id='network'>
<Network options={this.props.options} onSelectNode={this.onSelectNode}>
{[this.state.nodes]}
{[this.state.edges]}
</Network>
</div>
);
}
}
export default MyNetwork
Before clicking node 2
After clicking node 2
Update 1
I created a live example at stackblitz which isn't working yet caused by other failures I make and can't find.
The components I use are:
Network
Node
Edge
Edge and Node are extending Module
I reworked my MyNetwork component according to some mistakes xadm mentioned.
Components (espacially dynamic) shouldn't be stored in state.
I implemented two new functions nodes() and edges() // line 15-41*
key prop should be used, too.
key is used now // line 18 + 32*
Passed props cannot be modified, you still have to copy initial data
into state. State is required for updates/rerendering.
line 9*
*line numbers in live example I mentioned above
Update 2
I reworked my code and now the life sample is working.
My hope is that I could use the native vis.js events and use them in MyNetwork or other Components I will write.
I read about using 3rd Party DOM event in this question can't figure out to adapt it for my particular case. Because I don't know how to attach the event handler to . Is this possible to do so I can use the event in other components?
Or should I open another question for this topic?
I see several possibilities of problems here.
<Decorator/> should be defined outside of <MyNetwork /> class. Click handler should be passed as prop.
Components (espacially dynamic) shouldn't be stored in state. Just render them in render or by rendering method (called from render). Use <Node/> components with decorator prop, key prop should be used, too.
Passed props cannot be modified, you still have to copy initial data into state. State is required for updates/rerendering. You probably need to remove edge(-es) while removing node.
Create a working example (on stackblitz?) if a problem won't be resolved.
It sounds like React is re-initializing your component when you are clicking a button. Maybe someone smarter than I am can figure out why that is happening...
But since no one has commented on this yet, one way I have handled these sorts of issues is to take the state management out of the display component. You say you are passing the nodes and edges via props from a parent component. You might consider moving the addNode, removeNode, createEdge, and other methods up to the parent component so that it is maintaining the state of the node/edge structure and your display component <MyNetwork/> is only displaying what it receives as props.
Perhaps this isn't an option in your app, but I generally use Redux to remove the state management from the components all together. I find it reduces situations like this where "who should own the state" isn't always clear.

Reactjs: how to write a method to handle component creation and unmount

So let's say there is acomponent which displays 2 child components: a document list and the selected document. By default the selected document component is not rendered, only when a document is selected from the list. And i also want this whole thing work when a new document is selected from the list.
There is a state which holds the document content and responsible for the selected document rendering, so i thought i'm going to set it to null in the method which handles the list item selection in order to unmount the previously created child component. Like this (excerpts from the parent class):
handleResultListItemClick(docname) {
if (this.state.sectioncontainer != null) this.setState({sectioncontainer: null},()=>{console.log("muhoo");});
var selected_doc = this.state.resultlist.filter((doc) => {
return docname === doc.properties.title;
});
this.setState({sectioncontainer: selected_doc[0].content.sections},()=>{console.log("boohoo");});
}
...
render() {
return (
...
{this.state.sectioncontainer != null && <SectionContainer listOfSections={this.state.sectioncontainer}/>}
);
}
The only problem is that state handling is not fast enough (or something) in react, because putting the state nullification and its new value setting in the same method results in no change in ReactDOM.
With the above code, the component will be created when the parent component first rendered, but after selecting a new doc in the list results in no change.
How should i implement this in way which works and also elegant?
I found this: ReactDOM.unmountComponentAtNode(container) in the official react docs. Is this the only way? If yes, how could i get this container 'name'?
Edit:
Based on the answers and thinking the problem a bit more through, i have to explain more of the context.
As kingdaro explained, i understand why there is no need to unmount a child component on a basic level, but maybe my problem is bit more sophisticated. So why did i want to unmount the child?
The documents consist of several subsections, hence the document object which is passed to the child component is an array of objects. And the document is generated dynamically based on this array the following way (excerpt from the SectionContainer class which is responsible to display the document):
buildSectionContainer() {
return this.props.listOfSections.map((section, index) =>
{
if (section.type === 'editor') return (
<QuillEditor
key={index}
id={section.id}
modules={modules}
defaultValue={section.content}
placeholder={section.placeholder}
/>
);
else if (section.type === 'text') return (
<div key={index}>{section.value}</div>
);
}
);
}
render() {
return (
<div>
{this.buildSectionContainer()}
</div>
);
}
The SectionContainer gets the array of objects and generate the document from it according to the type of these sections. The problem is that these sections are not updated when a different doc is selected in the parent component. I see change only when a bigger length array is passed to the child component. Like the firstly selected doc had an array of 2 elements, and then the newly selected doc had 3 elements array of sections and this third section is added to the previously existing 2, but the first 2 sections remained as they were.
And that’s why i though it’s better to unmount the child component and create a new one.
Surely it can happen that i miss something fundamental here again. Maybe related to how react handles lists. I just dont know what.
Edit2:
Ok, figured out that there is a problem with how i use the QuillEditor component. I just dont know what. :) The document updates, only the content of QuillEditors doesnt.
The reason your current solution doesn't actually do anything is because React's state updates are batched, such that, when setState is called a bunch of times in one go, React "combines" the result of all of them. It's not as much of a problem with being "not fast enough" as it is React performing only the work that is necessary.
// this...
this.setState({ message: 'hello', secret: 123 })
this.setState({ message: 'world' })
// ...becomes this
this.setState({ message: 'world', secret: 123 })
This behavior doesn't really have much to do with the problem at hand, though. As long as your UI is a direct translation of state -> view, the UI should simply update in accordance to the state.
class Example extends React.Component {
state = {
documentList: [], // assuming this comes from the server
document: null,
}
// consider making this function accept a document object instead,
// then you could leave out the .find(...) call
handleDocumentSelection = documentName => {
const document = this.state.documentList.find(doc => doc.name === documentName)
this.setState({ document })
}
render() {
const { document } = this.state
return (
<div>
<DocumentList
documents={this.state.documentList}
onDocumentSelection={this.handleDocumentSelection}
/>
{/*
consider having this component accept the entire document
to make it a little cleaner
*/}
{document && <DocumentViewer document={document.content.sections} />}
</div>
)
}
}

React refs how are they used, and when are they used?

Hello and thank you for reading this question!
I have been learning React for some weeks and I have difficulties trying to understand how refs can get React's instance and put it into a JS variable.
For example we could discuss the documentation's example:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
I understand that the ref gets the input element which will be rendered and stores it into a class field using this.textInput.
However I do not understand why the parameter being passed to the refs is (input) what could happen if there were jsx tags nested? For example two inputs?
Also is there not a clear way to reference the elements being created with React render/return? I am talking about something like object oriented programming: className.instanceName or creating instance from the HTML elements with: new elementName().
Thank you for your help!
React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.
When you write
<input
type="text"
ref={(input) => { this.textInput = input; }} />
React extracts the ref attribute and invokes a callback after the component is mounted. In that callback function react passes the instance of the input, which is what you get as the parameter here. Think of the above as a function
<input
type="text"
ref={this.callback} />
and that callback will look like
const callback = (input) => {
this.textInput = input;
}
According to the docs:
When the ref attribute is used on an HTML element, the ref callback
receives the underlying DOM element as its argument.
Regarding your next question:
However I do not understand why the parameter being passed to the refs
is (input) what could happen if there were jsx tags nested
The parameter being passed to div is just referenced as input in your example, you can call it anything you want, like input, inputRef, instance
In multiple jsx tags are nested, the ref is applied on the element on which ref attribute is passed. For instance
<div ref={this.getRef}>
<div>Hello React</div>
</div>
The ref gets the instance of is applied to the outer div from which you can access the child elements.
Codesandbox
Also is there not a clear way to reference the elements being created
with React render/return
Well refs is a way provided by React to reference, elements being created. However you should only use refs when
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
Most often, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. For example if you wish to change the class on an element, instead of getting an access to the element and changing it class, you would pass the dynamic prop to it instead like
<div className={this.props.className} />
Or as a matter of fact, use state to make necessary updates
To explain what is happening with the ref tag, let's break it down into smaller pieces...
This block:
<input
type="text"
ref={(input) => { this.textInput = input; }} />
Says to call this (input) => { this.textInput = input; } when this input field is mounted and unmounted. The (input) is just a parameter name, and you could call it whatever you want. So you could rewrite it as:
<input
type="text"
ref={(myinputfield) => { this.textInput = myinputfield; }} />
And it will do the same thing. In both case, input and myinputfield both reference the text field on which the ref attribute was defined.
For your second question,
Also is there not a clear way to reference the elements being created
with React render/return? I am talking about something like object
oriented programming: className.instanceName or creating instance from
the HTML elements with: new elementName().
The react model is a bit different. state is the primary way to have components interact with each other, rather than one component calling another. It's not entirely clear what you're trying to do, but when you want to update one component based on some action another component does, you would typically update the state, which would re-render the components with the new state.
You can still lookup other components in the DOM, but I'd encourage you to think more in the react model that uses state to update components.

Categories

Resources