Component name with React hooks in DevTools - reactjs

I've just updated a from a class based to a functional component.
When I look in React's DevTools, I'd usually see my component named Gallery with all the named state variables.
Now though, All I see is a component named _default with a bunch of non-descriptive State: definitions.
From other answers, I've read that React Dev Tools now supports hooks but I've not seen any examples of the component name being wrong.
Is this normal behaviour or is there something I'm doing wrong?
Versions
React 16.9.0
React Developer Tools Chrome extension: 4.1.1
Also getting the same issue in Firefox.
Component code
// The component
import React, { useState, useEffect } from 'react';
const Gallery = ({ images, layout }) => {
const [showLightbox, toggleLightbox] = useState(false);
const [activeImage, setActiveImage] = useState(false);
return (
// Some JSX here
)
};
Render code
// Rendering the component
import React from 'react';
import { render } from 'react-dom';
import Gallery from '../../global/scripts/components/Gallery';
render(
<Gallery images={images} />,
document.getElementById('image-gallery'),
);
Devtools screenshot

Try adding a displayName to your component before export. Check the following link for reference.
DisplayName
Use it like Gallery.displayName = 'Gallery'

Related

Testing a react component using useSelector hook

I am having a lot of trouble trying to implement tests for a component using the useSelector hook from react redux. I've seen some questions already about this subject but I didn't manage to fix my problem using the suggested solutions to those questions.
My component is pretty big so I won't post it all but the part giving me trouble looks like this :
Total.tsx
import React from 'react';
import clsx from 'clsx';
import i18next from 'i18next';
import { useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import Box from '#material-ui/core/Box';
import CustomTooltip from '../CustomTooltip/CustomTooltip';
import SkeletonTotal from 'components/Skeletons/Total';
import { ApplicationHelper } from 'helpers';
import './Total.scss';
//Some interfaces here for types since this is in TypeScript
function Total(props: TotalProps) {
const { currency } = useSelector(
(state: { currencyReducer: any }) => state.currencyReducer
);
...
}
I first tried to test it like another component that doesn't use redux like so :
Total.test.js (first attempt)
import React from 'react';
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
);
});
But I was getting an error saying I need a react-redux context value and to wrap my component in a Provider which led me to try this :
Total.test.js (attempt 2)
import React from 'react';
import { Provider } from 'react-redux'
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Provider>
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
</Provider>
);
});
I am now getting a "Cannot read property 'getState' of undefined" error for the Provider component. I did try to mock a store to pass to my Provider as well as using jest to mock a return value like so
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({currency: 'cad'})
Unfortunately I was unsuccessful to make this work and could not find a working solution in the other questions that might relate to this. Any ideas how I could make this work? Thanks
The useSelector hook relies on the redux Context in order to access the state, so it must be inside of a Provider component in order to work. Your second attempt is on the right track, but you haven't set the store prop on the Provider, so the store is undefined and you get error "Cannot read property 'getState' of undefined".
Since you'll likely have many components that you'll want to test with redux context, the redux docs suggest creating your own version of the react testing library's render function which wraps the element in a provider before rendering it. This new render function adds two new optional options to the standard RTL options: initialState and store.
You can basically copy and paste that entire test-utils.js example from the docs, but I modified the return to include the created store so that we can dispatch to it directly (rather than just interacting with the component in ways that will dispatch an action).
return {
...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }),
store
};
With typescript annotations.
Inside your component test file, you will use your test-utils to render the Total component. It's fine to return the container element but you don't actually need to because you can query matching elements on the global RTL screen object or on the bound queries for your base element. We are basically looking to see that the outputted HTML code matches the expectations. You could test the selector itself in isolation, but it seems like you are trying to test the component.
Your test might look something like this:
import React from "react";
import Total from "./Total";
import { render, screen } from "./test-utils";
// if you want events: import userEvent from "#testing-library/user-event";
test( 'gets currency from redux', () => {
// render with an initial currency
const { store, container, getByLabelText } = render(
// not sure where these props come from, presumable constants in the file
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />,
{ initialState: { currency: USD } }
);
// some sort of RTL matcher or document.querySelector
const currencyElement = getByLabelText(/currency/i); // uses regex for case-insensitivity
// some sort of check for value
expect(currencyElement?.innerText).toBe("USD");
// dispatch an action to change the currency
// might want to wrap in `act`?
store.dispatch(setCurrency("EUR"));
// check that the value changed
expect(currencyElement?.innerText).toBe("EUR");
});
Working example that I created based on a basic counter component.

How to test a component which is built with styled component

I'm new to React testing and with Jest and Enzyme.
I'm trying to learn how to use a TDD approach first and due to that, I'm building my tests before starting coding.
What I did was to create a sample app in React and I installed Enzyme dependencies and then I wrote the test:
import { shallow } from "enzyme";
import React from "react";
import AppLayout from "./AppLayout";
import { ContentLayout } from "./styles";
it("renders <AppLayout /> component", () => {
const wrapper = shallow(<AppLayout />);
expect(wrapper.find(ContentLayout)).to.have.lengthOf(1);
});
Then I built the component which contains a styled component called ContentLayout
import React from "react";
import { ContentLayout } from "./styles";
const AppLayout = () => {
return (
<>
<ContentLayout>
<h1>HELLO</h1>
</ContentLayout>
</>
);
};
export default AppLayout;
I'm unable yo make the test pass as what I got was the next error:
TypeError: Cannot read property 'have' of undefined
I would like to learn how shoulæd be the practice to test this kind of component and what rules to follow in general when I start a project from scratch with TDD in mind.
The AppLayout is called then in App.js
import React from "react";
import AppLayout from "./Components/AppLayout";
function App() {
return <AppLayout />;
}
export default App;
You should use .toHaveLength(number) matchers of expect in jestjs.
expect(wrapper.find(ContentLayout)).toHaveLength(1);
For nested components, there are two strategies generally:
Shallow Rendering API
Shallow rendering is useful to constrain yourself to test a component as a unit, and to ensure that your tests aren't indirectly asserting the behavior of child components.
This means we don't want to render the nested component(ContentLayout), we only test the behavior(lifecycle methods, event handlers, data fetching, condition render, etc.) of the parent component(AppLayout).
Full Rendering API (mount(...))
Full DOM rendering is ideal for use cases where you have components that may interact with DOM APIs or need to test components that are wrapped in higher order components.

useState() Breaks Gatsbyjs/React

I have imported useStats into my index page but when I use it it breaks gatsby/react and I get this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer
(such as React DOM)
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app See fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
I tried to trouble shoot using this from the site:
// Add this in node_modules/react-dom/index.js
window.React1 = require('react');
// Add this in your component file
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);
But I got back true.
Here is my code:
import React, { useState } from "react";
import { Link } from "gatsby";
// components
import Layout from "../components/Layout/Layout";
import SEO from "../components/seo";
import IndexComponent from "../components/IndexComponent/IndexComponent";
const IndexPage = () => {
const [sku] = useState();
return (
<Layout>
<SEO title="Home" />
<IndexComponent />
</Layout>
);
};
export default IndexPage;
1.) you need [sku, setSku] = useState().
2.) Where are you rendering IndexPage? Are you doing IndexPage() instead of <IndexPage />?
I think It is a terminal Issue with windows.
Seams to work fine with bash.

Storybook: Addon-Docs Cannot read property 'classes' of undefined

I’m using Storybook 5.2.6 for React 16.9.0 with Typescript 3.5.3 and using Material UI themed components.
Having added, and configured, #storybook/addon-docs the Storybook Docs page displays: “Cannot read property 'classes' of undefined” in the PropsTable when a component is wrapped withStyles from #material-ui.
Component:
import React, {FunctionComponent} from 'react';
import { Typography } from '#material-ui/core';
import {
  withStyles,
  WithStyles,
} from '#material-ui/core/styles';
import styles from './index.styles';
export interface IProps extends WithStyles<typeof styles> {
  message?: string;
  testId?: string;
}
const Bar: FunctionComponent<IProps> = (props) => {
  const {
    classes,
    message,
    testId = ‘test-bar',
  } = props;
  if (!message) { return null; }
  return (
    <Typography className={classes.message} data-testid={testId}>
      {message}
    </Typography>
  );
};
export default withStyles(styles)(Bar);
Story
import React from 'react';
import { storiesOf } from '#storybook/react';
import { MuiThemeProvider } from '#material-ui/core/styles';
import Bar from './index';
import theme from '../../../others/global/material-ui-theme';
storiesOf('Bar', module)
  .addDecorator(story => <MuiThemeProvider theme={theme}>{story()}</MuiThemeProvider>)
  .addParameters({ component: Bar, componentSubtitle: 'Displays the Bar with message’ })
  .add('Warning', () => <Bar message="warning" />);
In React devtools and debugging in Chrome devtools I can see the classes do get injected as props so I’m kinda stumped at the moment how to resolve this?
So a work around exists, you export the "unwrapped" component and use that as the component in docs:
As mentioned here:
https://github.com/storybookjs/storybook/issues/8361
and commented here: https://github.com/storybookjs/storybook/issues/8435#issuecomment-547075209
Example:
export const PureBar: FunctionComponent<IProps> = (props) => {
// ...
};
export default withStyles(styles)(PureBar);
Then in the story, update the component parameters to target the "Pure" component:
// import both the pure and wrapped components:
import Bar, { PureBar } from './Bar'
// Add to the story:
storiesOf('Bar', module)
.addParameters({ component: PureBar, componentSubtitle: 'Displays the Bar with message’ })
.add(/* ... */);

React functional/stateless pure component

I'm beginning to switch to functional/stateless components in React, like the one below.
I do however have a problem with react-chartjs-2 re-drawing my charts, even though the data for them didn't change. Before I switched to functional components, this was easily solved by using Reacts PureComponent.
Is there a way to make React use the PureComponent for a stateless function?
const ListGroup = props => {
const {title, width} = (props);
return (
<ul className={"grid"} style={{width}}>
<h1>{title}</h1>
{props.children}
</ul>
)
}
Since React v16.6.0 you can use React.memo():
import React from 'react';
const PureListGroup = React.memo(ListGroup);
With React versions before v16.6.0, You can use recompose (discontinued but still maintained) and wrap the component with the pure or onlyUpdateForKeys higher order components:
import pure from 'recompose/pure';
const PureListGroup = pure(ListGroup);

Resources