How to check state changing between two components after button click? - reactjs

I have 2 components for this task.First one is my wrapper component and the second one is component for buttons.
I am clicking one of the buttons of my button component and it changes state in my wrapper component.
Now how can i bring that whole action in Jest(Enzyme) environment?
it('should update state', () => {
const onButtonClickMock = jest.fn();
const wrapper = shallow(<MyComponent/>)
const buttons = shallow(<Colors onClick={onButtonClickMock} colors={['gray', 'black', 'white']}/>);
const d = buttons.find('div#buttons');
const b = d.find('button.btn');
b.at(1).simulate('click');//Pick second button from bunch and click
wrapper.update();
expect(onButtonClickMock).toHaveBeenCalledTimes(1);//This works
expect(component2.state(['selection'])).toBe(1);
})

Related

Why React is rendering parent element, even if changed state isn't used in jsx? (Using React Hooks)

Im doing a React small training app using Hooks. Here's the example:
There is a MainPage.js and it has 3 similar child components Card.js. I have global state in MainPage and each Card has its own local state. Every Card has prop "id" from MainPage and clickButton func.
When I click button in any Card there are 2 operations:
Local variable 'clicked' becomes true.
The function from parent component is invoked and sets value to global state variable 'firstCard'.
Each file contains console.log() for testing. And when I click the button it shows actual global variable "firstCard", and 3x times false(default value of variable "clicked" in Card).
It means that component MainPage is rendered after clicking button ? And every Card is rendered too with default value of "clicked".
Why MainPage componenet is rendered, after all we dont use variable "firsCard", except console.log()?
How to make that after clicking any button, there will be changes in exactly component local state, and in the same time make global state variable "firstCard" changed too, but without render parent component(we dont use in jsx variable "firstCard")
Thanks for your help !
import Card from "../Card/Card";
const Main = () => {
const [cards, setCards] = useState([]);
const [firstCard, setFirstCard] = useState(null);
useEffect(() => {
setCards([1, 2, 3]);
}, []);
const onClickHandler = (id) => {
setFirstCard(id);
};
console.log(firstCard); // Showing corrrect result
return (
<div>
{cards.map((card, i) => {
return (
<Card
key={Date.now() + i}
id={card}
clickButton={(id) => onClickHandler(id)}
></Card>
);
})}
</div>
);
};
import React, { useState } from "react";
const Card = ({ id, clickButton }) => {
const [clicked, setClicked] = useState(false);
const onClickHandler = () => {
setClicked(true);
clickButton(id);
};
console.log(clicked); // 3x false
return (
<div>
<h1>Card number {id}</h1>
<button onClick={() => onClickHandler()}> Set ID</button>
</div>
);
};
export default Card;
You have wrong idea how react works.
When you change something in state that component will re render, regardless if you use that state variable in render or not.
Moreover, react will also re render all children of this component recursively.
Now you can prevent the children from re rendering (not the actual component where state update happened though) in some cases, for that you can look into React.memo.
That said prior to React hooks there was a method shouldComponentUpdate which you could have used to skip render depending on change in state or props.

Toggle two components using react hooks

I have two instances of the same component. The component can be opened or closed.
Component has const [isOpen, setIsOpen] = useState(false)
which is being set using useCallback
const openComponent = useCallback(() => {
setIsOpen(true)
}, [])
const closeComponent = useCallback(() => {
setIsOpen(false)
}, [])
Components are within their own context. But I have trouble even coming up with an idea how to handle via parent, when one instance opens, close the other conundrum.
Passing state to parent and closing all of them before opening a new one is not an option.
Any ideas?
You should lift the state of these two components (open or false) to parent, so parent can have this logic (only one open at a time).
Parent should pass through props the open state to each children, along with a reference to a changeStateHandler(boolean) which children could call to notify the parent that they have been toggled.
So in parent you would have :
const [firstChildState, setFirstChildState] = useState(false);
const [secondChildState, setSecondChildState] = useState(false);
const handlStateChanged = () => {
// toggle both states
}
[...]
<Child isOpen={firstChildState} onStateChange={handlStateChanged} />
<Child isOpen={secondChildState} onStateChange={handlStateChanged} />
In children :
// calls props.onStateChange on toggle, use props.isOpen to display proper state

React Native - Component UI getting blocked after rerender

I have two components that share a state through redux useSelector ().
CalendarDateList (with VirtualizedList) and Menu. In the Menu I have some buttons that change a seasonFilter status. This seasonFilter is used within the CalendarDateList to filter the array of the list. The problem is: when the button is pressed, the state changes and consequently the CalendarDateList rerender occurs, but this blocks the button's UI taking a long time so that it has the opacity of "pressed".
Is there any configuration to set the filter option of the other component's array without blocking the menu UI?
Basically I set through an action the seasonFilter on a Menu component and then access it on another List Component. When I hit the menu button, it blocks the UI thread stopping any animation from the TouchableOpacity button, for example. The UI is released only after the Calendar List is totally rendered.
export default function NavbarItem({ icon, text }) {
const seasonFilter = useSelector(state => state.seasonFilter);
const dispatch = useDispatch();
function toggleFilter(season) {
let seasonFilterTmp = seasonFilter.slice();
const seasonIndex = seasonFilter.indexOf(season);
seasonIndex === -1 ? seasonFilterTmp.push(season) : seasonFilterTmp.splice(seasonIndex, 1);
dispatch({ type: 'SET_FILTER', seasonFilter: seasonFilterTmp });
};
...
function CalendarTimeline() {
const calendarDates = useSelector(state => state.calendarDates);
const seasonFilter = useSelector(state => state.seasonFilter);
const filteredCalendarDates = calendarDates.filter(calendarDate => seasonFilter.includes(calendarDate.season))
return (
<Container >
<VirtualizedList
...

React Ant Design Modal Method update on state change

I'm currently migrating to antd, and have a modal appear on a certain route (ie /userid/info). I'm able to achieve this if I use the antd Modal react component, but I'd like to be able to use the modal methods provided such as Modal.confirm,Modal.info and Modal.error as they offer nicer ui straight out of the box.
I'm running to multiple issues such as having the modal rendered multiple times (both initially and after pressing delete in the delete user case), and unable to make it change due to state (ie display loading bar until data arrives). This is what i've tried but it constantly renders new modals, ive tried something else but that never changed out of displaying <Loader /> even though isFetching was false. I'm not sure what else to try.
const UserInfoFC: React.FC<Props> = (props) => {
const user = props.user.id;
const [isFetching, setIsFetching] = React.useState<boolean>(true);
const [userInfo, setUserInfo] = React.useState<string>('');
const modal = Modal.info({
content: <Loader />,
title: 'User Info',
});
const displayModal = () => {
const renderInfo = (
<React.Fragment>
<p>display user.info</p>
</React.Fragment>
);
const fetchInfo = async () => {
try {
user = // some api calls
setUserInfo(user.info);
modal.update({ content: renderInfo })
} catch (error) {
// todo
}
setIsFetching(false);
};
fetchInfo();
};
displayModal();
return(<div />);
};
reference: https://ant.design/components/modal/#components-modal-demo-confirm
edit: here is a replication of one of the issues I face: https://codesandbox.io/embed/antd-reproduction-template-1jsy8
As mentioned in my comment, you can use a useEffect hook with an empty dependency array to run a function once when the component mounts. You can initiate an async call, wait for it to resolve and store the data in your state, and launch a modal with a second hook once the data arrives.
I made a sandbox here
Instead of going to /:id/info and routing to a component which would have returned an empty div but displayed a modal, I created a displayInfo component that displays a button and that controls the modal. I got rid of attempting to use routes for this.
What I have now is similar to the docs

Why is my button undefined in my test?

My test:
describe('button component', () => {
it('should toggle off when clicked', () => {
let component;
component = ReactTestUtils.renderIntoDocument(<Search />);
let searchbtn = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'button');
ReactTestUtils.Simulate.click(searchbtn);
console.log(searchbtn, 'search button***'); //UNDEFINED
expect(searchbtn.calledOnce).to.equal(false);
})
});
This is my search component:
render() {
return (
<div className="search">
<button className="searchButton" onClick={this.handleSearch}>{this.state.on ? 'ON' : 'OFF'}</button>
</div>
);
}
Do I need to spy on it or mock it? or is there a better way to test buttons in react?
Since you have added enzyme tag I will answer using enzyme.
It can be tested very easily via shallow rendering -
const searchWrapper = shallow(<Search />);
const button = searchWrapper.find('button').first();
When you simulate click event on button the onClick handler provided via onClick prop which is handleSearch in your case will be called.
So if you are setting some state based on the onClick function call corresponding ui changes based on the state changes can be compared or checked if changes were reflecting correctly in the dom.
or
if you just want to check if method was called or not by using a fake method of similar name -
const onButtonClick = sinon.spy();
expect(onButtonClick.calledOnce).to.equal(false);
button.setProps({ onClick:onButtonClick});
button.simulate('click');
expect(onButtonClick.calledOnce).to.equal(true);

Resources