Why is not entering componentDidUpdate()? - reactjs

Hello I'm trying to test a state that is changed in the componentDidUpdate but is not enetering.
Code
componentDidUpdate (newProps) {
const { dataSource } = newProps
// set value for nextButtonDisabled in first results async load
if (dataSource.length) {
const newPaginationInfo = Object.assign({}, this.state.paginationInfo)
newPaginationInfo.nextButtonDisabled = dataSource.length <= this.pageSize
this.setState({ paginationInfo: newPaginationInfo }) /* eslint-disable-line react/no-did-update-set-state */
}
}
State
this.state = {
paginationInfo: {
currentPage: 0,
nextButtonDisabled: true
}
}
And the test
it('should set nextButtonDisabled to false when gets new props.datasource if datasource length <= 20', () => {
const component = shallow(<VehicleHistoryTable {...makeProps()} />)
component.setProps({ dataSource: createDataSourceMock(3) })
expect(component.instance().state.paginationInfo.nextButtonDisabled).toEqual(true)
})
The function createDataSourceMock() creates an array of numbers, in this case 3 rows.
Any suggestions?
P:S I'm trying to migrate to React 17

Related

State no updating in class component when testing method with jest

I am trying to test my method in a context provider. I have one branch in the method to be covered and that's what I am strangling with. The specific branch is only entered when a specific condition occurs: if (offset !== 0 && total !== 0 && offset >= total)
See my Class component below:
class JourneyProvider extends Component<
{
children: ReactNode;
},
JourneyContextData
> {
constructor(props: { children: ReactNode }) {
super(props);
this.state = {
...defaultValues,
};
}
getContextValue = (): JourneyContextData => {
const { products, total, limit, offset, loading, disabled, setProducts } =
this.state;
return {
products,
total,
limit,
offset,
loading,
disabled,
setProducts,
};
};
setProducts = async (): Promise<void> => {
const { limit, offset, total, products } = this.state;
if (total === 0 || offset < total) {
const gqlRequest = new GQLRequest(query);
this.setLoading(true);
try {
await gqlRequest.post().then(({ products: { edges, totalCount } }) => {
const newOffset = offset + limit;
this.setState({
products,
total: totalCount,
offset: newOffset,
});
this.setLoading(false);
// Disable button if there are no more products
if (offset !== 0 && total !== 0 && offset >= total) {
// never gets in here when testing.
this.setDisabled(true);
}
});
} catch (e) {
this.setLoading(false);
}
}
};
}
This is my test:
it("setProducts is successful and disable button", async () => {
const wrapper = shallow(
<JourneyProvider>
<div>test</div>
</JourneyProvider>
) as any;
const result = {
products: {
edges: [
{
node: {
id: "1",
name: "test-name",
},
},
],
totalCount: 1,
},
};
mockedClient.post.mockResolvedValueOnce(result);
jest
.spyOn(ProductsQuery, "getProductsQuery")
.mockResolvedValueOnce(new Query("test", true) as never);
const setLoadingSpy = jest.spyOn(wrapper.instance(), "setLoading");
const setDisabledSpy = jest.spyOn(wrapper.instance(), "setDisabled");
wrapper.state().limit = result.products.totalCount;
console.log(wrapper.state().offset); //this returns 0
await wrapper.instance().setProducts();
console.log(wrapper.state().offset); //this returns 0
expect(setLoadingSpy).toHaveBeenCalledWith(true); // this passes
expect(setLoadingSpy).toHaveBeenCalledWith(false); // this passes
expect(setDisabledSpy).toHaveBeenCalledWith(true); // this fails
});
You should change the logic in the code where you're making the comparison because you're using a stale state, keep in mind that a call to setState doesn't change the state immediately. Compare the new values you set to the state instead of the old state values
if (newOffset !== 0 && totalCount !== 0 && newOffset >= totalCount) {
or put that code inside the setState callback to guarantee you're using updated values
...
const newOffset = offset + limit;
this.setState({
products,
total: totalCount,
offset: newOffset,
}, () => {
this.setLoading(false);
if (offset !== 0 && total !== 0 && offset >= total) {
this.setDisabled(true);
}
});
...

How to set value for Field at state parent from child component

I have 2 Components: Community.js and Edit.js
I call to Edit from Community below:
<DetailModal
DetailModal={Detail}
errors={this.state.errors}
uploadFile={this.props.uploadFileActions.uploadFile}
onSave={this.save}
onChange={this.onChange}
mode={this.state.mode}
data={this.state.details}
isOpen={this.state.modalIsOpen}
closeModal={this.closeModal}
editable={isHasEditPermisson}
/>
At Community, I have a function onchange() below:
onChange = (field, data) => {
let value = null;
if (data) {
value = data
}
this.setState(state => ({
details: {
...state.details,
[field]: value
},
errors: {
...state.errors,
[field]: undefined
}
}));
// }
}
At Edit, I have a function which called to select image/video file:
selectFile = (file) => {
if (file && file.target.files.length > 0) {
const checkType = file.target.files[0].type.split('/')[0]
const extendType = file.target.files[0].type.split('/')[1]
const fileArr = [];
// if (checkType === "video") {
// console.log('this.getDuration(file)', this.getDuration(file))
// if (this.getDuration(file) > 60) {
// alert("stop");
// return;
// }
// }
// this.props.uploadFile(file.target.files[0], (res) => {
// this.props.onChange('ResourceUrl', `${this.props.data.ResourceUrl ? `${this.props.data.ResourceUrl};` : ''}${res.data.Data}`);
// });
fileArr.push({
file: file.target.files[0],
urlFile: URL.createObjectURL(file.target.files[0]),
});
this.props.onChange('ResourceUrl', `${this.props.data.ResourceUrl ? `${this.props.data.ResourceUrl};` : ''}${fileArr[0].urlFile}`);
this.props.onChange('ResourceFile', this.props.data.ResourceFile ? this.props.data.ResourceFile : fileArr[0].file);
if (checkType === "image") {
this.setState({
fileType: "image/*",
extend: extendType
})
} else {
this.setState({
fileType: "video/*",
countVideo: 1,
extend: extendType
})
}
// file.target.value = '';
}
}
This is Init state in Community:
constructor(props) {
super(props);
this.escFunction = this.escFunction.bind(this);
this.state = {
modalIsOpen: false,
mode: 'add',
details: {},
errors: {},
defaultRole: constants.CollaboratorRole.default,
permanentRole: constants.CollaboratorRole.permanent,
isOpenDeleteConfirm: false
};
}
Here, I call to onchange() in Community to set value for 2 field: ResourceUrl, ResourceFile
But I have an issue when set value for ResourceFile. When I choose second file then I still get value of first file.
I don't know how to set the value of the second file into ResourceFile, which means that I expect that ResourceFile is an array containing the information of the two files I just selected.

React native - Invalid state update warning

The following code produces a Warning 'Cant perform a React State update in an unmounted component. This is a no-op, but it indicated a memory leak in your application..'
I'm new to RN - working my way through my first build - I cant understand why this error is being produced as I am not calling any stae changes until componentDidMount - can anyone offer any tips please?
Class CheckIn extends React.Component {
constructor(props) {
super(props);
this.state = {
venue: undefined,
checkinTime:0,
currentLocation:null,
};
}
getCurrentCoordinates = () => {
navigator.geolocation.getCurrentPosition(
position => {
const currentLongitude = JSON.stringify(position.coords.longitude);
const currentLatitude = JSON.stringify(position.coords.latitude);
const currentLocation = currentLatitude + ',' + currentLongitude;
this.setState({ currentLocation });
this.getCurrentTime();
},
error => Alert.alert(error.message),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
};
minutes_with_leading_zeros=(dt)=>
{
return (dt.getMinutes() < 10 ? '0' : '') + dt.getMinutes();
}
getCurrentTime = () => {
const dt = new Date();
const dtcur = dt.getHours() +':' +this.minutes_with_leading_zeros(dt);
this.setState({
checkinTime: dtcur,
});
};
componentDidMount(){
this.getCurrentCoordinates();
this.getCurrentTime();
}

How to use a method in render reactjs?

i have a method set_data which is used to set data based on id. I know it could be easy to call this set_data in componentdidupdate when id changes. However in doing so it doesnt set some state variables in the parent component.
To get rid of that want to call set_data method in render . However since this set_data method sets state of data it enters into an infinite loop in render . Also cannot provide a condition (like prevprops.id!== this.props.id) to execute set_data method.
To prevent it thought of using this set_data method not to set state at all. and can call this set_data method in render.
Below is the code,
export default class child extends React.Component {
state = {
query: '',
data: null,
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.set_data();
}
}
set_data = () => {
if (!this.props.info) {
return;
}
if (this.props.id === this.empty_id) {
this.setState({data: null});
return;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
this.setState({data: this.filter_data(data, this.state.query)});
};
render = () => {
const shown_data= this.state.data;
/* i want to call set_data method here*/};}
Could someone help me solve this. Thanks.
You can't call setData there, because that would be anti-pattern. It will trigger a loop that will continuously render as well as keeps setting state.
You can probably rewrite the component this way:
export default class child extends React.Component {
state = {
query: ''
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
set_data = () => {
let data = {};
if (!this.props.info) {
return data;
}
if (this.props.id === this.empty_id) {
return data;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
data = this.filter_data(data, this.state.query);
return data;
};
render = () => {
const shown_data= this.state.data;
const data = this.set_data();
/* i want to call set_data method here*/};}
In this, we are not setting data in the state. For every new ID, it will get new data and will compute it from render thereby avoiding antipattern. I have also removed componentDidMount, since we are doing computation in render. Note: This solution means taking away data from the state, if you are not using data anywhere before render, this will work.
Let me know if this helps.

enzyme shallow calls imported function when passed wrong parameters

I have a component that I am writing automated tests for. I want to test a method of this component. THe purpose if this test if if the method will call an imported function when passed the wrong parameters.
This is my component (I removed the non-relevant code):
//Util
import { getMenu } from '../utils/request';
const Dashboard = React.createClass({
getInitialState() {
let today = new Date();
if (today.getDay() == 6) {
today.setDate(today.getDate() + 2);
} else if (today.getDay() == 0) {
today.setDate(today.getDate() + 1);
}
return {
selectedDate: today.toISOString().substring(0, 10)
}
},
componentDidMount() {
getMenu(this.state.selectedDate, (data) => {
if (data.error) {
this.setState({
error: data.error
})
} else {
this.setState({
dailyMenu: data.dailyMenu,
loading: false
})
}
})
},
handleDateChange(date) {
var newDate = date.toISOString().substring(0, 10);
if(newDate !== this.state.selectedDate) {
getMenu(newDate, (data) => {
if (data.error) {
this.setState({
error: data.error
})
} else {
this.setState({
dailyMenu: data.dailyMenu,
loading: false,
selectedDate: newDate
})
}
})
}
},
render() {
return (
<MuiThemeProvider>
<div className="Dashboard">
<div className="Dashboard-body">
<Sidebar dateClick={this.handleDateChange}/>
</div>
</div>
</MuiThemeProvider>
)
}
});
module.exports = Dashboard;
I am mocking the getMenu function using jest.mock:
// Components
import Dashboard from '../components/Dashboard';
// Utils
import { getMenu } from '../utils/request';
jest.mock('../utils/request', () => ({getMenu: jest.fn()}))
Then I have written two tests. The first one passes different dates, the second one passes the same date. So in the second case the getMenu mock should not get called. But for both tests I get the result that the function was called one time:
it('handleDateChange should call getMenu when NOT passed state date', ()=> {
const dashboard = shallow(<Dashboard/>);
const today = new Date();
const longAgo = new Date(1).toISOString().substring(0, 10);
dashboard.setState({ selectedDate: longAgo });
dashboard.instance().handleDateChange(today);
expect(getMenu).toHaveBeenCalledTimes(1);
});
it('handleDateChange should NOT call getMenu when passed state date', ()=> {
const dashboard = shallow(<Dashboard/>);
const today = new Date();
const selectedDate = today.toISOString().substring(0, 10);
dashboard.setState({ selectedDate: selectedDate });
dashboard.instance().handleDateChange(today);
expect(getMenu).toHaveBeenCalledTimes(0);
});
So I did some console logs and so far everything seems as expected:
console.log(today.toISOString().substring(0, 10));
console.log(dashboard.state('selectedDate'));
console.log(today.toISOString().substring(0, 10) !== selectedDate);
This outputs:
2017-01-12
2017-01-12
false
What am I doing wrong?

Resources