Redux/Enzyme testing of nested components - reactjs

I have a redux connected navigation bar component that renders two sub-components. One of these is a glorified button (not redux), the other a redux connected search bar.
In a test, I want to be able to render the button and confirm the right behaviour happens when it is clicked. However if I use shallow() then it only renders a placeholder for the button and the button itself is not available to be found and clicked. If I use mount(), then the test fails as my unit test is using the non redux export for the navigation bar, which then tries to render the child search bar component, which is redux connected - and it does not have the store to pass down.
using the non-redux export is fine for testing shallow renders, but what can I do to be able to test my navigation bar component by clicking the button - that can only be fully rendered with a mount() call?
The problem I have is in the below test, if I use shallow() then it cant find the button to simulate the click, as it has only rendered as a placeholder. If I use mount() then it fails to render the <Searchbar /> component, as that is a redux connected component and my test is passing props manually without a connected store.
Is there a way to configure my navigation bar component to pass the props through to the search bar if the store doesn't exist? Or to only conditionally deep render certain components? I only want to render the PanelTileButton, not the SearchBar
My navigation bar component
interface IControlBarProps {
includeValidated: boolean,
includeValidatedChanged: (includeValidated:boolean) => void,
}
export class ControlBar extends React.Component<IControlBarProps, {}> {
constructor(props: any) {
super(props);
}
public render() {
return <div className="Control-bar">
<div className="Control-left" >
<SearchBar />
</div>
<div className="Control-center" />
<div className="Control-right">
{this.getDashboardButton("IV", "Include Validated", this.props.includeValidated, () => this.props.includeValidatedChanged(!this.props.includeValidated))}
</div>
</div>
}
private getDashboardButton(key: string, title: string, newValue: boolean, action: (value:boolean) => void)
{
return <div className="Control-Bar-Right" key={key}>
<PanelTileButton text={title} iswide={false} highlighted={newValue}
// tslint:disable
onClick={() => action(newValue)} />
</div>
}
}
function mapStateToProps(state: IStoreState) {
return {
includeValidated: state.trade.includeValidated
};
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
includeValidatedChanged: (includeValidated:boolean) => {
dispatch(getIncludeValidatedChangedAction(includeValidated))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ControlBar);
My test
it('should handle clicking include validated button', () => {
const mockCallback = jest.fn();
const wrapper = mount(<ControlBar includeValidated={false} includeValidatedChanged={mockCallback} />);
expect(wrapper.find('div.Control-bar').exists()).toEqual(true);
expect(wrapper.find({highlighted: false}).exists()).toEqual(true);
const pb = wrapper.find("PanelTileButton").first();
pb.find('button').simulate('click', {preventDefault() {} });
expect(mockCallback.mock.calls.length).toBe(1);
})

You can also mock components so that you can avoid having to mock the redux store all the time when you prefer to use shallow
In the same folder as SearchBar.tsx, create a sub-folder named __mocks__, and in it place a file with the same name SearchBar.tsx (it's convention) that returns minimal html
import * as React from 'react';
export default function SearchBar() {
return <div>SearchBar</div>
}
And then in your test file
jest.mock('../SearchBar') // this detects the corresponding mock automatically
import { ControlBar } from '../ControlBar';
import { shallow } from 'enzyme';
...
it('should handle clicking include validated button', () => {
const mockCallback = jest.fn();
const wrapper = shallow(<ControlBar includeValidated={false} includeValidatedChanged={mockCallback} />);
...
})
...

For others looking into how to do this, I eventually solved it by wrapping my mounted component in a <Provider> tag and using redux-mock-store to pass a mock store down to the child components that would at least let them render.
import configureMockStore from 'redux-mock-store'
it('should handle clicking include validated button', () => {
const mockCallback = jest.fn();
const mockStore = configureMockStore([])(getTestStore());
const wrapper = mount(<Provider store={mockStore}><ControlBar includeValidated={false} includeValidatedChanged={mockCallback} /></Provider>);
expect(wrapper.find('div.Control-bar').exists()).toEqual(true);
expect(wrapper.find({highlighted: false}).exists()).toEqual(true);
const pb = wrapper.find("PanelTileButton").first();
pb.find('button').simulate('click', {preventDefault() {} });
expect(mockCallback.mock.calls.length).toBe(1);
})

Related

How can I use a toast of Chakra UI throughout all the components?

I am trying to show an alert when user makes any request in my react application. The thing is that currently I am using separate toast components for separate components. How should I use one single toast component throughout the whole application. I tried putting the toast component in App.jsx but in order to manage the toast message and color I have to do prop-drilling, which I want to avoid. I am using redux so I can not use useContext for managing the toast. Any idea would be appreciated.
I prefer using a higher-order component called HOC to wrap the toast and provide the necessary props to it. This way, you can keep the toast in a central location and use the HOC to wrap other components that need to display the toast.
For example:
// withToast.js
import { useState } from "react";
import { ToastProvider } from "#chakra-ui/core";
const withToast = (WrappedComponent) => {
return (props) => {
const [toast, setToast] = useState({
message: "",
color: "",
isOpen: false,
});
const showToast = (message, color) => {
setToast({ message, color, isOpen: true });
};
const hideToast = () => {
setToast({ message: "", color: "", isOpen: false });
};
return (
<ToastProvider>
<WrappedComponent
{...props}
showToast={showToast}
hideToast={hideToast}
toast={toast}
/>
</ToastProvider>
);
};
};
export default withToast;
Now you can use the same toast in every component that is being wrapped by withToast:
import React from 'react';
import withToast from './withToast';
const App = (props) => {
const { showToast, toast } = props;
return (
<div>
<button onClick={() => showToast("Hello, World!", "green")}>
Show Toast
</button>
<Toast message={toast.message} color={toast.color} isOpen={toast.isOpen} />
</div>
);
};
export default withToast(App);
You can also wrap multiple components in the HOC and use the showToast and hideToast functions in any component that is rendered within the wrapped component, this way you don't have to prop-drill showToastand hideToast.

How to refer to other component in React?

I am trying to make a simple confirmation overlay. I have an overlay component that will overlay any parent div. I want to be able to call the confirm function from any place in my app. The code below works, but I feel like I am going against the React philosophy. My main questions are:
What is the alternative of using getElementById('main')? At the moment I want to overlay the same div, which I have given the 'main' id. This div is part of another React component. Can I use a ref in some way? Can I export this ref from the component I want to overlay and pass it to the confirm function?
My main App Component also uses the createRoot function. Is it wrong to use it again?
Am I using promises in the right way?
How to optimize below code?
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Overlay } from './Overlay'
interface IConfirm {
text: string,
destroy: () => void,
resolve: (value: boolean | PromiseLike<boolean>) => void
}
export const Confirm = (props: IConfirm) => {
return (
<Overlay
text={props.text}
visible
>
<button onClick={() => {
props.resolve(true)
props.destroy()
}}>Yes</button>
<button onClick={() => {
props.resolve(false)
props.destroy()
}}>No</button>
</Overlay>
)
}
export default function confirm (text: string) {
const container = document.createElement('div')
const main = document.getElementById('main')
main!.appendChild(container)
const root = createRoot(container)
const destroy = () => {
root.unmount()
main?.removeChild(container)
}
const promise = new Promise<boolean>((resolve) => {
root.render(<Confirm text={text} destroy={destroy} resolve={resolve} />)
})
return promise
}
Thanks a lot.

How React Testing Library can assert children props and Redux store updates?

I just started exploring a unit testing framework for my react application. I locked my choice at #testing-library/react instead of Enzyme as it is recommended by React and helps writing more robust tests. I'm also planning to use Jest for completeness.
But before I could proceed with React Testing Library, I'm stuck at few questions. Can someone please help?
For instance,
There is an Editor component which updates Redux store based on user input.
There is another component Preview which reads value directly from store.
There is a third component Container which contains both of the above components passing some props.
Questions:
How to test if user input in Editor component actually updates store?
How to test if Preview component can render value reading from store?
How to test if Container component passes props correctly to its children?
Container.jsx
import React from 'react';
import Editor from './Editor';
import Preview from './Preview';
const Container = ({editorType}) => {
return (
<div>
<Editor type={editorType} />
<Preview />
</div>
);
};
export default Container;
Editor.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {setName} from './actions';
const Editor = ({type}) => {
const name = useSelector(state => state.name);
const dispatch = useDispatch();
return (
<div>
<h1>Type: {type}</h1>
<input value={name} onChange={evt => dispatch(setName(evt.target.value))} />
</div>
);
};
export default Editor;
Preview.jsx
import React from 'react';
import { useSelector } from 'react-redux';
const Editor = ({type}) => {
const name = useSelector(state => state.name);
return (
<div>
<h1>Name: {name}</h1>
</div>
);
};
export default Preview;
Here is how I would do it :
How to test if user input in Editor component actually updates store?
Looking at your code, Editor does not update store, it does not call useDispatch. But assuming it does, using https://github.com/reduxjs/redux-mock-store I would setup a mocked store provider to the component in the test, then trigger user input and check the mocked store if the correct action have been dispatched, with the correct payload :
describe('<Editor />', () => {
let store: MockStoreEnhanced;
let rtl: RenderResult;
beforeEach(() => {
store = mockStore({ /* your store structure */ });
rtl = render(
<Provider store={store}>
<Editor type={...} />
</Provider>
);
});
test('dispatch action correctly', () => {
fireEvent.change(rtl.container.querySelector('input'), { target: {value: 'test' } });
// check actions that were dispatched by change event
// getActions return an array avec redux actions (type, payload)
expect(store.getActions()).toEqual(...);
}
}
Beside of this you should test your store reducer and action creators, to ensure that the action modifies the store correctly.
How to test if Preview component can render value reading from store?
Same principle, setup your test with a mocked store, init this store with relevant value, and just test that the component displays what's in the store :
describe('<Preview />', () => {
let store: MockStoreEnhanced;
let rtl: RenderResult;
beforeEach(() => {
store = mockStore({ name: 'This is name from store' });
rtl = render(
<Provider store={store}>
<Preview type={...} />
</Provider>
);
});
test('displays store content correctly', () => {
expect(rtl.getByText('This is name from store')).toBeTruthy();
}
}
How to test if Container component passes props correctly to its children?
Here you should test Container + Preview + Editor together, and find something visual to test (as a real user would perceive it), involving the props passed by Container to its children.
For example, if Preview displays props.type, which comes from Container editorType props :
describe('<Container />', () => {
// mocked store may not be necessary here, depending on your use case
let store: MockStoreEnhanced;
let rtl: RenderResult;
beforeEach(() => {
store = mockStore({ name: 'This is name from store' });
rtl = render(
<Provider store={store}>
<Container editorType={'toTest'} />
</Provider>
);
});
test('displays store content correctly', () => {
// check that 'toTest' exists in DOM because it is rendered by Preview component, a child of Container
expect(rtl.getByText('toTest')).toBeTruthy();
}
}
To sum it up, my approach would be :
Test actions and reducers alone (very simple to test, state machines, just plain Jest tests)
Test that components dispatch correct actions with correct payloads on correct user inputs
Mock store to setup relevant use cases (init store value) for components reading data from store
For the last 2 items I use https://github.com/reduxjs/redux-mock-store.

How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?

So I have a SSR app using Next.js. I am using a 3rd party component that utilizes WEB API so it needs to be loaded on the client and not the server. I am doing this with 'two-pass' rendering which I read about here: https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
I'm trying to figure out why when 'ssrDone' state changes in the next.js page state the entire <Layout> component unnecessarily re-renders which includes the page's Header, Footer, etc.
I've read about React.memo() as well as leveraging shouldComponentUpdate() but I can't seem to prevent it from re-rendering the <Layout> component.
My console.log message for the <Layout> fires twice but the <ThirdPartyComponent> console message fires once as expected. Is this an issue or is React smart enough to not actually update the DOM so I shouldn't even worry about this. It seems silly to have it re-render my page header and footer for no reason.
In the console, the output is:
Layout rendered
Layout rendered
3rd party component rendered
index.js (next.js page)
import React from "react";
import Layout from "../components/Layout";
import ThirdPartyComponent from "../components/ThirdPartyComponent";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
ssrDone: false
};
}
componentDidMount() {
this.setState({ ssrDone: true });
}
render() {
return (
<Layout>
{this.state.ssrDone ? <ThirdPartyComponent /> : <div> ...loading</div>}
</Layout>
);
}
}
export default Home;
ThirdPartyComponent.jsx
import React from "react";
export default function ThirdPartyComponent() {
console.log("3rd party component rendered");
return <div>3rd Party Component</div>;
}
Layout.jsx
import React from "react";
export default function Layout({ children }) {
return (
<div>
{console.log("Layout rendered")}
NavBar here
<div>Header</div>
{children}
<div>Footer</div>
</div>
);
}
What you could do, is define a new <ClientSideOnlyRenderer /> component, that would look like this:
const ClientSideOnlyRenderer = memo(function ClientSideOnlyRenderer({
initialSsrDone = false,
renderDone,
renderLoading,
}) {
const [ssrDone, setSsrDone] = useState(initialSsrDone);
useEffect(
function afterMount() {
setSsrDone(true);
},
[],
);
if (!ssrDone) {
return renderLoading();
}
return renderDone();
});
And you could use it like this:
class Home extends React.Component {
static async getInitialProps({ req }) {
return {
isServer: !!req,
};
};
renderDone() {
return (
<ThirdPartyComponent />
);
}
renderLoading() {
return (<div>Loading...</div>);
}
render() {
const { isServer } = this.props;
return (
<Layout>
<ClientSideOnlyRenderer
initialSsrDone={!isServer}
renderDone={this.renderDone}
renderLoading={this.renderLoading}
/>
</Layout>
);
}
}
This way, only the ClientSideOnlyRenderer component gets re-rendered after initial mount. 👍
The Layout component re-renders because its children prop changed. First it was <div> ...loading</div> (when ssrDone = false) then <ThirdPartyComponent /> (when ssrDone = true)
I had a similar issue recently, what you can do is to use redux to store the state that is causing the re-render of the component.
Then with useSelector and shallowEqual you can use it and change its value without having to re-render the component.
Here is an example
import styles from "./HamburgerButton.module.css";
import { useSelector, shallowEqual } from "react-redux";
const selectLayouts = (state) => state.allLayouts.layouts[1];
export default function HamburgerButton({ toggleNav }) {
let state = useSelector(selectLayouts, shallowEqual);
let navIsActive = state.active;
console.log("navIsActive", navIsActive); // true or false
const getBtnStyle = () => {
if (navIsActive) return styles["hamBtn-active"];
else return styles["hamBtn"];
};
return (
<div
id={styles["hamBtn"]}
className={getBtnStyle()}
onClick={toggleNav}
>
<div className={styles["stick"]}></div>
</div>
);
}
This is an animated button component that toggles a sidebar, all wrapped inside a header component (parent)
Before i was storing the sidebar state in the header, and on its change all the header has to re-render causing problems in the button animation.
Instead i needed all my header, the button state and the sidebar to stay persistent during the navigation, and to be able to interact with them without any re-render.
I guess now the state is not in the component anymore but "above" it, so next doesn't start a re-render. (i can be wrong about this part but it looks like it)
Note that toggleNav is defined in header and passed as prop because i needed to use it in other components as well. Here is what it looks like:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
I'm using an id and fn because all my layouts are stored inside an array in redux, but you can use any logic or solution for this part.

Could not find "store" in either the context or props when showing modal

This is my start point of the app:
const App = () => (
<Provider store={store}>
<StackNav />
</Provider>
);
AppRegistry.registerComponent('MyApp', () => App);
For the StackNav I have the following configuration:
const routeConfiguration = {
TO_LOGIN: { screen: Login },
TO_HOME: { screen: Home },
};
const stackNavigatorConfiguration = {
headerMode: 'screen',
navigationOptions: {
header: null,
}
};
Inside Login screen I show a modal inside which I cannot use the connect method of react-redux. This is the full error:
Here is how I am trying to use it:
class SignModal extends Component {
}
export default connect()(SignModal);
What can be the reason? Thank you in advanced!
Modal will exist outside the current component hierarchy. The pattern is called a Portal. This means that it cannot access the React context, hence cannot find the Redux store. Currently there is no API in React Native to fix this. You need to explicitly pass the store as a prop.
Probably better to just connect the component which has the Modal, and pass needed values as props to inner component.
Have you tried
export const mapStateToProps = (state) => {
return {
//what you want from the store
}
}
and then
export default connect(mapStateToProps)(SignModal);
This is possible. Use your modal as a view component and create a connected container component.
// components/Modal.js
class Modal extends Component {
render() {
<Modal />
}
}
export default Modal
// containers/ModalContainer
import Modal from "../components/Modal"
const mapState = state => ...
const mapDispatch = dispatch => ...
export default connect(mapState, mapDispatch)(Modal)
Import Modal from "../containers/ModalContainer" when you use it.

Resources