Unable to pass props to makeStyles when using material UI with next.js - material-ui

I am using Next.js with material UI.
I have created a Icon and when clicked it calls the setOpenFn() and sets the open variable to be true. This variable is then passed as props to the useStyles(). Now I display the search bar if open is true. But I get the below error
webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:67 Warning: Prop `className` did not match. Server: "makeStyles-search-3 makeStyles-search-8" Client: "makeStyles-search-3 makeStyles-search-9"
When the search Icon is clicked display: flex property is also not working.
I tried to create .babelrc file and added this
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
but still nothing works.
const useStyles = makeStyles((theme) => ({
search: {
[theme.breakpoints.down('sm')]: {
display: (props) => (props.open ? 'flex' : 'none'),
width: '70%',
},
}
}))
const Navbar = () => {
const [open, setOpen] = useState(false);
const setOpenFn = () => {
setOpen(true);
};
const classes = useStyles({ open });
return(
<Search
className={classes.searchButton}
onClick={() => {
setOpenFn();
}}
/>
)
}

I am assuming you are using mui version 5+
According to this migration guide! you need to wrap your JSX with the following component.
<StyledEngineProvider injectFirst>
</StyledEngineProvider>

Related

Adhoc styling in Material-UI 5

Trying to upgrade from MUI 4 to 5. In MUI 4, I used to be able to introduce adhoc classes using makeStyles:
const useStyles = makeStyles((theme: Theme) => ({
root: {
cursor: 'pointer',
},
}));
export const FakeLink = ({ children }) => {
const classes = useStyles();
return (
<span className={classes.root}>
{children}
</span>
);
};
Now trying to put the same cursor: 'pointer' on an ImageListItem. How do I do this?
<ImageListItem>
<img ... />
</ImageListItem>
I thought I could do it using sx, but it doesn't implement the cursor property. What am I missing?
The cursor property should be supposed by the sx and styled API. Maybe you are facing a TypeScript issue?

Material UI Alerts using custom colors

Can I set a custom color for a MUI Alert component? The docs suggest that the only color options are the four that match the four severity props.
For MUI V5 in case someone is looking to change the background color in the theme this worked for me.
export const theme = createTheme({
components: {
MuiAlert: {
styleOverrides: {
standardSuccess: {
backgroundColor: 'green',
color: 'white'
},
standardError: {
backgroundColor: 'red',
color: 'white'
},
standardWarning: {
backgroundColor: 'orange',
color: 'white'
},
standardInfo: {
backgroundColor: 'grey',
color: 'black'
}
}
},
},
});
And is working by setting the variant:
<Alert severity="success">Success</Alert>
<Alert severity="error">Error</Alert>
<Alert severity="warning">Warning</Alert>
<Alert severity="info">Info</Alert>
It's possible. Quoting the API docs:
You can override the style of the component thanks to one of these
customization points:
With a rule name of the classes object prop.
With a global class name.
With a theme and an overrides property.
A crude example in this codesandbox
A simple approach would be:
const useStyles = makeStyles((theme) => ({
myAlert: {
color: "#ffffff",
backgroundColor: "#000000"
}
});
function MyCustomAlert() {
const classes = useStyles();
return (
<Alert severity="error" className={classes.myAlert}>
<AlertTitle>Error</AlertTitle>
{"I am an error message !"}
</Alert>
);
}
I guess another option, if you don't wanna deal with css classes, could be:
(using mui v5 and typescript)
first u define your styled component:
interface IAlert extends AlertProps {
background?: string
}
export const Alert = styled(MUIAlert)<IAlert>(({ background }) => ({
background,
}))
then, on your component that uses the Alert:
const Component = ({type}) => {
...
const CUSTOM_COLORS_MAPPING = {
success: palette.success[500],
info: palette.secondary[500],
error: palette.error[500],
warning: palette.warning[500],
}
return (
...
<Alert severity={type} background={CUSTOM_COLORS_MAPPING[type]}>
{message}
</Alert>
)

React-testing-library with Ionic v5 (react) and react-hook-form- change events do not fire

I am trying to test a component rendered with Controller from react-hook-form with react-testing-library
<Controller
render={({ onChange, onBlur, value }) => (
<IonInput
onIonChange={onChange}
onIonBlur={onBlur}
value={value}
type="text"
data-testid="firstname-field"
/>
)}
name="firstName"
control={control}
defaultValue={firstName}
/>
Default values are as expected when I render the component with a some mock data. However, when I go about changing values, it seems the events are not firing. From this blog post it looks like ionic exports a set of test utils to handle ionic's custom events. After setting that up in my setupTests.ts I'm attempting to use both the ionFireEvent and the fireEvent from RTU, neither of which reflect changes in the component when I use debug(). I've set it up so I can use both fireEvent and ionFireEvent to test:
import { render, screen, wait, fireEvent } from "#testing-library/react";
import { ionFireEvent } from "#ionic/react-test-utils";
// using RTL fireEvent - no change
it("fires change event on firstname", () => {
const { baseElement } = renderGolferContext(mockGolfer);
const firstNameField = screen.getByTestId("firstname-field") as HTMLInputElement;
fireEvent.change(firstNameField, { target: { detail: { value: "Jill" } } });
expect(firstNameField.value).toBe("Jill");
});
// using IRTL ionFireEvent/ionChange - no change
it("fires change event on firstname", () => {
const { baseElement } = renderGolferContext(mockGolfer);
const firstNameField = screen.getByTestId("firstname-field") as HTMLInputElement;
ionFireEvent.ionChange(firstNameField, "Jill");
expect(firstNameField.value).toBe("Jill");
});
screen.debug(baseElement);
I've also tried moving the data-testid property to the controller rather than the IonInput suggested here, with the result being the same: no event is fired.
Here are the versions I'm using:
Using Ionic 5.1.1
#ionic/react-test-utils 0.0.3
jest 24.9
#testing-library/react 9.5
#testing-library/dom 6.16
Here is a repo I've created to demonstrate.
Any help would be much appreciated!
this line appears to be incorrect...
expect(firstNameField.value).toBe("Jill");
It should be looking at detail.value since that is what you set
expect((firstNameField as any).detail.value).toBe("Jill");
this is my test,
describe("RTL fireEvent on ion-input", () => {
it("change on firstname", () => {
const { baseElement, getByTestId } = render(<IonicHookForm />);
const firstNameField = screen.getByTestId(
"firstname-field"
) as HTMLInputElement;
fireEvent.change(firstNameField, {
target: { detail: { value: "Princess" } },
});
expect((firstNameField as any).detail.value).toEqual("Princess");
});
});

Testing Material UI components using Hooks API & Enzyme shallow rendering

The latest versions of Material UI now have a Hooks alternative for styling components, instead of the HoC. So instead of
const styles = theme => ({
...
});
export const AppBarHeader = ({ classes, title }) => (
...
);
export default withStyles(styles)(AppBarHeader);
you can choose to do this instead:
const useStyles = makeStyles(theme => ({
xxxx
}));
const AppBarHeader = ({ title }) => {
const classes = useStyles();
return (
....
)
};
export default AppBarHeader;
In some ways this is nicer, but as with all hooks you can no longer inject a 'stub' dependency to the component. Previously, for testing with Enzyme I just tested the non-styled component:
describe("<AppBarHeader />", () => {
it("renders correctly", () => {
const component = shallow(
<AppBarHeader title="Hello" classes="{}" />
);
expect(component).toMatchSnapshot();
});
});
However, if you use hooks, without the 'stub' dependency for classes, and you get:
Warning: Material-UI: the `styles` argument provided is invalid.
You are providing a function without a theme in the context.
One of the parent elements needs to use a ThemeProvider.
because you always need a provider in place. I can go and wrap this up:
describe("<AppBarHeader />", () => {
it("renders correctly", () => {
const component = shallow(
<ThemeProvider theme={theme}>
<AppBarHeader title="Hello" classes="{}" />
</ThemeProvider>
).dive();
expect(component).toMatchSnapshot();
});
});
but that no longer seems to render the children of the component (even with the dive call). How are folks doing this?
EDIT: As per the comments below, this implementation presents some timing issues. Consider testing with mount instead of shallow testing, or use the withStyles HOC and export your component for shallow rendering.
So I have been grappling with this for a day now. Here is what I have come up with.
There are some issues trying to stub makeStyles since it appears MUI has made it readonly. So instead of creating a useStyles hook in each component, I created my own custom useStyles hook that calls makeStyles. In this way I can stub my useStyles hook for testing purposes, with minimal impact to the flow of my code.
// root/utils/useStyles.js
// My custom useStyles hook
import makeStyles from '#material-ui/styles/makeStyles';
export const useStyles = styles => makeStyles(theme => styles(theme))();
Its almost like using the withStyles HOC
// root/components/MyComponent.js
import React from 'react';
import { useStyles } from '../util/useStyles';
// create styles like you would for withStyles
const styles = theme => ({
root: {
padding: 0,
},
});
export const MyComponent = () => {
const classes = useStyles(styles);
return(
</>
);
}
// root/component/MyComponent.spec.js
import { MyComponent } from './MyComponent';
import { shallow } from 'enzyme';
import { stub } from 'sinon';
describe('render', () => {
it('should render', () => {
let useStylesStub;
useStylesStub = stub(hooks, 'useStyles');
useStylesStub.returns({ });
const wrapper = shallow(<MyComponent />);
console.log('profit');
});
});
This is the best I can come up with for now, but always open to suggetions.

How to pass context down to the Enzyme mount method to test component which includes Material UI component?

I am trying to use mount from Enzyme to test my component in which a several Material UI component are nested. I get this error when running the test:
TypeError: Cannot read property 'prepareStyles' of undefined
After some digging, I did found that a theme needs to be passed down in a context. I am doing that in the test but still get this error.
My test:
import expect from 'expect';
import React, {PropTypes} from 'react';
import {mount} from 'enzyme';
import SearchBar from './SearchBar';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
function setup() {
const muiTheme = getMuiTheme();
const props = {
closeSearchBar: () => {},
fetchSearchData: () => {},
data: [],
searching: false
};
return mount(<SearchBar {...props} />, {context: {muiTheme}});
}
describe('SearchBar Component', ()=> {
it('Renders search toolbar properly', () => {
const wrapper = setup();
expect(wrapper.find('.toolbar').length).toBe(1);
expect(wrapper.find('button').length).toBe(1);
});
});
My searchbar component is a stateless component, so I am not pulling in any context. But even when I am, I still get the same error.
What am I doing wrong?
Try adding childContextTypes in the mount options:
return mount(
<SearchBar {...props} />, {
context: {muiTheme},
childContextTypes: {muiTheme: React.PropTypes.object}
}
);
By doing it you set the Enzyme wrapper to make the muiTheme available to it's children through the context.
this is my handy method to test Material UI with shallow and mount
...
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import getMuiTheme from 'material-ui/styles/getMuiTheme';
const muiTheme = getMuiTheme();
const shallowWithContext = (node) => shallow(node, {context: {muiTheme}, childContextTypes: {muiTheme: PropTypes.object}});
const mountWithContext = (node) => mount(
node, {context: {muiTheme}, childContextTypes: {muiTheme: PropTypes.object}}
);
// now you can do
const wrapper = shallowWithContext(<Login auth={auth} onChange={() => 'test'} />);