I'm writing tests for my new React app, part of my intention with this project is to fully understand this testing thing - it's been on my radar for a while but I haven't put it into production before.
Written a fair number of test so far that are using snapshots and other static & synchronous approaches. This seems to work fine until now where I'm dealing with a setState -> expect(postFunctionState).toEqual(desiredState) situation and while I console.log my way through the flow and can see that setState() is being called and I can see the results in the browser, I can't seem to write a test that replicates the behaviour.
Here's the relevant code:
//Component (extracted):
export class CorsetCreator extends React.Component {
constructor(props) {
super(props);
this.state = {
productName: '',
productType: 'Overbust',
enabled: false,
created: false,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleProductChange = this.handleProductChange.bind(this);
}
handleNameChange(e) {
this.setState({ productName: e.target.value });
this.handleChange.bind(this)(e);
}
handleProductChange(e) {
this.setState({ productType: e.target.value });
this.handleChange.bind(this)(e);
}
handleChange(e) {
e.preventDefault();
if (e.target.value === '') {
this.setState({ enabled: false }); //User should not be able to submit an empty product name
return;
}
const { corsets } = this.props.corsetGallery;
if (!corsets) {
this.forceUpdate();
this.setState({ enabled: true }); //Point of this exercise is to guarantee unique combos of name&type. If there are no pre-existing corsets then no need to test for uniqueness
return;
}
const productType =
e.target.value === 'Underbust' || e.target.value === 'Overbust'
? e.target.value
: this.state.productType;
const productName =
e.target.value === 'Underbust' || e.target.value === 'Overbust'
? this.state
: e.target.value;
const filteredCorsets = corsets.filter(
corset => corset.type === productType && corset.name === productName,
);
this.setState({
enabled: !(filteredCorsets && filteredCorsets.length > 0),
});
}
//Test (extracted)
it('handles statechanges correctly with a valid new corset', () => {
const store = configureStore({}, browserHistory);
const creator = mount(
<Provider store={store}>
<CorsetCreator />
</Provider>,
);
const namebox = creator.find('NameBox').at(0);
const nameBoxField = namebox.find('input').at(0);
const submitbutton = creator.find('SubmitButton').at(0);
creator.setState({ enabled: false });
expect(submitbutton.props().enabled).toEqual(false);
nameBoxField.simulate('change', { target: { value: 'Test' } });
creator.update();
expect(creator.state().enabled).toEqual(true);
});
Because setState is asynchronous I feel like some sort of callback or promise may be the solution here but I've tried both and can't seem to sort through the best way. What is the best way to think about this type of scenario?
TL;DR: Remember React components are functions. In all their glory, they take in props and you receive an output of the render() function. Test the output.
If you have a variable in the state, chances are you're passing it to a child component or manipulating the visual output of the current component. Or said item in state would be useless :)
Testing the state is redundant, as it's like testing React itself.
A concern one normally raises is "But I'm showing/hiding that element by using setState(...)", or "I'm passing the state down into one of the children as a prop".
When writing tests, render the component. Simulate an action and check if the output of the render function has changed.
Take this component for example:
class TextWithClick extends React.Component {
state={ count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1})
}
render() {
return (
<input
value={this.state.count} {/* state passed down as props */}
onClick={this.handleClick}
/>
)
}
}
ReactDOM.render(<TextWithClick/>
, document.getElementById('root'))
It's simple. Clicking on the input field, increases the text it shows, which is a prop. Here are some test assertions:
// using enzyme
it("should increase the count", () => {
const wrapper = shallow(<TextWithClick />);
const input = wrapper.find("input").at(0);
// test props
input.simulate("click");
expect(input.props().value).toEqual(1);
input.simulate("click");
expect(input.props().value).toEqual(2);
input.simulate("click");
expect(input.state().count).toEqual(3); // this tests React more than your component logic :)
});
Remember React components are functions. In all their glory, they take in props and you receive an output of the render() function. Test the output.
In the case of Redux, same thing. Testing state change is like testing Redux's connect() functionality. Mozilla uses a real redux store to test their app. I.e. test the final output.
I quote from the above link regarding testing a React/Redux app (can't seem to to multi-line blockquotes in SO:
"We dispatch real Redux actions to test application state changes. We test each component only once using shallow rendering.
"We resist full DOM rendering (with mount()) as much as possible.
"We test component integration by checking properties.
"Static typing helps validate our component properties.
"We simulate user events and make assertions about what action was dispatched.
A good article: Testing React Component’s State by Anthony Ng.
Related
I'm relatively new to react and am having a little trouble understanding passing props/states from a child to parent component. I've managed to implement something that works but it is extremely laggy.
Overview: I am trying to create a form with multiple Material UI TextFields, with a submission button at the end to submit the form.
My approach: I am using state to update individual textfields on their inputs, and then dynamically updating another state in the parent FormSection file. Once the user clicks on the 'Submit' Button (not implemented yet), it will then take all the states from the parent class.
Existing Classes:
CompanyField.js
JobTitle.js
FormSection.js (Main File)
Implementation Appearance
CompanyField Class (will be same for Job Title, etc):
const Root = styled('div')(({ theme }) => ({
}));
class CompanyField extends React.Component {
state = { company: '' }
handleOnChange = (event) => {
event.preventDefault();
this.setState({ company: event.target.value.toLowerCase() });
this.props.onCompanyChange(this.state.company);
}
render() {
return (
<Root>
<Box
noValidate
autoComplete="off"
>
<TextField
id = "outlined-basic"
label = "Company"
variant = "outlined"
fullWidth
value = {this.state.company}
onChange = { this.handleOnChange }
/>
</Box>
</Root>
);
}
}
export default CompanyField;
Index Class
class FormSection extends React.Component {
state = { company: '', jobTitle: '' }
onCompanyUpdate = (value) => {
this.setState({company: value})
// console.log('Company:', this.state.company);
}
render() {
return (
<FormContainer>
<FormContentHeaderWrapper>
<FormContentHeader>
Company & Job Information
</FormContentHeader>
</FormContentHeaderWrapper>
<FormWrapperFull>
<CompanyField onCompanyChange={ this.onCompanyUpdate } />
<JobTitleField onJobTitleChange={ this.onJobTitleUpdate } />
</FormWrapperFull>
</FormContainer>
)
}
Could someone explain whether I am doing this the correct way? Else, would there be a better way to resolve this state passing method?
Thanks in advance!
When you update the parent component the whole tree re-renders on every keystroke. In your case your component very small it should not be a big impact. There are three approaches in my mind.
first of all, you have to use react developer tools for investigating further re-renders and finding the real problem.
first: You might use form validation libraries. For example; "react hook forms"
second: You might use React's "React.memo" function to memorize components.
third: You might use refs for input value management. You add values to ref and when you need them you iterate that ref object. You don't update the state. If there is no state update there will be no rerender.
for example:
In parent component:
const values = useRef({description: "", jobtitle: ""});
const onChange(name, value) {
values.current[name] = value;
}
In child component: (it must be an "uncontrolled component")
const handleCompanyChange = (evt) => {
const value = evt.target.value;
const name = "company";
props.onChange(name, value);
}
so.. i tried some similar answer here but non of theme work,
and need to listen to live update from firestore db
and i have to work with class component
always get this 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.
my code:
class Exchange extends React.Component {
constructor(props) {
super(props);
this.state = {
exchangePrice: 0,
newExchangePrice: "",
};
this.updateExchangePrice = this.updateExchangePrice.bind(this);
}
updateExchangePrice() {
const docRef = doc(db, "exchange", "exchange");
if (
this.state.newExchangePrice !== "" &&
this.state.newExchangePrice !== 0
) {
updateDoc(docRef, {
exchange_price: Number(this.state.newExchangePrice),
});
this.setState({ newExchangePrice: "" });
}
}
componentDidMount() {
const exchanges = collection(db, "exchange");
onSnapshot(exchanges, (x) => {
x.docs.forEach((doc) => {
if (doc.data().exchange_price !== this.state.exchangePrice) {
this.setState({ exchangePrice: doc.data().exchange_price });
}
});
});
}
Memory leaks is an issue when implementing React code, when using side-effects. Explore more on this from here. Some side-effects require clean-up. And that clean-up can be done by the use of ComponentWillUnmount() life-cycle method in a class based component in React. Using the ComponentWillUnmount() method, you can do the necessary un-subscriptions there that you previously implemented.
You may have a look at the React official doc to explore more on the method.
Also, you may have a look at the Stackoverflow case.
I have a prop being passed from a parent component to a child component which changes based on the user's input.
I want to trigger a data fetch in the child component when that prop changes before the child component is rendered. How can I do it?
I tried in the following manner by using useEffects(()=>{},[props.a, props.b]) but that is always called after the render. Please help!
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function parentComponent() {
const [inputs, setInputs] = useState({ a: "", b: "" });
return (
<>
<input
value={inputs.a}
onChange={(event) => {
const value = event.target.value;
setInputs((prevState) => {
return { ...prevState, a: value };
});
}}
/>
<input
value={inputs.b}
onChange={(event) => {
const value = event.target.value;
setInputs((prevState) => {
return { ...prevState, b: value };
});
}}
/>
<ChildComponent a={inputs.a} b={inputs.b} />
</>
);
}
function ChildComponent(props) {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState({});
useEffect(() => {
console.log("updating new data based on props.a: " + props.a);
setData({ name: "john " + props.a });
return () => {};
}, [props.a, props.b]);
useEffect(() => {
console.log("data successfully changed");
console.log(data);
if (Object.keys(data).length !== 0) {
setIsLoading(false);
}
return () => {};
}, [data]);
function renderPartOfComponent() {
console.log("rendering POC with props.a: " + props.a);
return <div>data is: {data.name}</div>;
}
return (
<div className="App">{isLoading ? null : renderPartOfComponent()}</div>
);
}
In the console what I get is:
rendering POC with props.a: fe
rendering POC with props.a: fe
updating new data based on props.a: fe
rendering POC with props.a: fe
rendering POC with props.a: fe
data successfully changed
Object {name: "john fe"}
rendering POC with props.a: fe
rendering POC with props.a: fe
If you know how I can make the code more efficient, that would be a great help as well!
Here's the codesandbox link for the code: https://codesandbox.io/s/determined-northcutt-6z9f8?file=/src/App.js:0-1466
Solution
You can use useMemo, which doesn't wait for a re-render. It will execute as long as the dependencies are changed.
useMemo(()=>{
doSomething() //Doesn't want until render is completed
}, [dep1, dep2])
You can use function below:
// utils.js
const useBeforeRender = (callback, deps) => {
const [isRun, setIsRun] = useState(false);
if (!isRun) {
callback();
setIsRun(true);
}
useEffect(() => () => setIsRun(false), deps);
};
// yourComponent.js
useBeforeRender(() => someFunc(), []);
useEffect is always called after the render phase of the component. This is to avoid any side-effects from happening during the render commit phase (as it'd cause the component to become highly inconsistent and keep trying to render itself).
Your ParentComponent consists of Input, Input & ChildComponent.
As you type in textbox, ParentComponent: inputs state is modified.
This state change causes ChildComponent to re-render, hence renderPartOfComponent is called (as isLoading remains false from previous render).
After re-render, useEffect will be invoked (Parent's state propagates to Child).
Since isLoading state is modified from the effects, another rendering happens.
I found the solution by creating and maintaining state within the ChildComponent
So, the order of processes was this:
props modified -> render takes place -> useEffect block is executed.
I found the workaround by simply instantiating a state within the childComponent and making sure that the props state is the same as the one in the child component before rendering, else it would just show loading... This works perfectly.
Nowadays you can use useLayoutEffect which is a version of useEffect that fires before the browser repaints the screen.
Docs: https://beta.reactjs.org/reference/react/useLayoutEffect
I have a complete running code, but it have a flaw. It is calling setState() from inside a render().
So, react throws the anti-pattern warning.
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount
My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
checkData: true,
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost !== nextProps.cost) {
this.setState({
checkData: true
});
}
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
if (this.props.cost.length && this.state.checkData) {
const tmp = this.props.cost;
//some calculations
....
....
this.setState({
theData: tmp,
checkData: false
});
}
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
<PieGraph theData={this.state.theData} />
</div>
);
}
}
The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.
handleFormSubmit = (e) => {
this.props.onGpChange(this.state.value);
e.preventdefaults();
}
The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?
Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.
checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.
Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost != nextProps.cost) {
this.setState({
theData: this.props.cost
});
}
}
shouldComponentUpdate(nextProps, nextState){
if(nextProps.cost !== this.props.cost){
return true;
}
return false;
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
{this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
</div>
);
}
}
PS:- The above solution is for version React v15.
You should not use componentWillReceiveProps because in most recent versions it's UNSAFE and it won't work well with async rendering coming for React.
There are other ways!
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps is invoked right before calling the render
method, both on the initial mount and on subsequent updates. It should
return an object to update the state, or null to update nothing.
So in your case
...component code
static getDerivedStateFromProps(props,state) {
if (this.props.cost == nextProps.cost) {
// null means no update to state
return null;
}
// return object to update the state
return { theData: this.props.cost };
}
... rest of code
You can also use memoization but in your case it's up to you to decide.
The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps
For example updating a list (searching) after a prop changed
You could go from this:
static getDerivedStateFromProps(props, state) {
// Re-run the filter whenever the list array or filter text change.
// Note we need to store prevPropsList and prevFilterText to detect changes.
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
to this:
import memoize from "memoize-one";
class Example extends Component {
// State only needs to hold the current filter text value:
state = { filterText: "" };
// Re-run the filter whenever the list array or filter text changes:
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// Calculate the latest filtered list. If these arguments haven't changed
// since the last render, `memoize-one` will reuse the last return value.
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}
i'm learning redux along side with react and did a first app to catch a few infos from Destiny and present for the user. The app has a select box where the user can choose one of the many activities and I save that activity to check with the API on the ActivityComponent, the problem is, I do that (get the activity identifier with redux and save on a store) then later I have to retrieve on the ActivityComponent but somehow I had to implement this:
componentWillReceiveProps(nextProps) {
this.props = {};
this.replaceProps(nextProps, cb => this.getAjax());
}
replaceProps(props, callback){
this.props = Object.assign({}, this.props, props);
this.setState(initialState);
callback();
}
Well here's my repository on github if anyone could help me: https://github.com/persocon/destiny-weekly
So the quick answer is no, it's not necessary. Why ? Well, you're not really using redux yet. If you look at that ajax call your are doing in replace props, getAjax, I inspected that in your codebase, and see you're calling setState in the component after receiving a request there.
With redux, you would rather use an action and reducer. The action would be handled, calling the api, and setting the state in the redux "store" with a reducer after receiving this data.
Ok so a full blown example would be something like the following, just first add in redux-thunk, it will definitely help you out going forward, be sure to go read through the example on the README to get a better idea of the how and why.
function startLoading() {
return {
type: 'LOADING_STARTED',
isLoading: true
}
}
function doneLoading(){
return {
type: 'LOADING_ENDED',
isLoading: false
}
}
function setActivity(result) {
let lastGist = result[0];
let activity = {
identifier: result.display.identifier,
title: (result.display.hasOwnProperty('advisorTypeCategory'))? result.display.advisorTypeCategory : '',
name: (result.hasOwnProperty('details') && result.details.hasOwnProperty('activityName')) ? result.details.activityName : '',
desc: (result.hasOwnProperty('details') && result.details.hasOwnProperty('activityDescription')) ? result.details.activityDescription : '',
backgroundImg: (result.display.hasOwnProperty('image')) ? 'http://bungie.net' + result.display.image : '',
modifiers: (result.hasOwnProperty('extended') && result.extended.hasOwnProperty('skullCategories')) ? result.extended.skullCategories : [],
bosses: (result.hasOwnProperty('bosses')) ? result.bosses : [],
items: (result.hasOwnProperty('items') && result.display.identifier == "xur") ? result.items : [],
bounties: (result.hasOwnProperty('bounties')) ? result.bounties : []
}
return {
type: 'SET_ACTIVITY',
activity: activity
}
}
export function findActivity(activity_id) {
return dispatch => {
dispatch(startLoading())
$.get(activity_id, (result)=>{
dispatch(doneLoading())
if(response.status == 200){
dispatch(setActivity(response.json))
}else {
dispatch(errorHere)
}
})
}
}
So it might look a bit intimidating at first, but after a go or two, it will feel more natural doing things this way, instead of in the component.
There shouldn't be any need for replaceProps, as the props will be updated automatically. componentWillReceiveProps is a chance for you to take a peek at what is to come in this lifecycle.
Note: You should never clobber this.props as that is used internally.
I would recommend comparing this.props to nextProps inside componentWillReceiveProps to see if the selected Activity has changed. If so, then fire the ajax call (which I recommend using a redux action passed into the component).
Yeah, I screwed up the comment haha sorry, on the SelectContainer.jsx now I'm doing that to retrieve the activity json after the select change:
const mapDispatchToProps = (dispatch) => {
return {
onSelectChange: (activity) =>{
dispatch(changeApiUrl(activity));
dispatch(findActivity(activity));
}
}
}
UPDATE
import { connect } from 'react-redux';
import { changeApiUrl, findActivity } from '../actions/index.jsx';
import ActivityComponent from '../components/ActivityComponent.jsx';
const mapStateToProps = (state) => {
return state.activity;
}
export class ActivityContainer extends ActivityComponent {
componentDidMount() {
const { dispatch, identifier } = this.props;
dispatch(findActivity(identifier));
}
}
export default connect(mapStateToProps)(ActivityContainer);
Generally speaking on life cycle of methods in react with redux. you should use redux methods. unless you have to use in react life cycle methods.