Let's say I have global.css
.test {
background-color: black;
width: 50px;
height: 50px;
}
For some reason, I need to get the styles data from applied element. I tried refs but it always return empty string.
import { useState, useEffect, useRef } from "react";
const IndexPage = () => {
const divEl = useRef<HTMLDivElement | null>(null);
const [divStyle, setDivStyle] = useState({} as CSSStyleDeclaration);
useEffect(() => {
if (divEl.current) {
setDivStyle(divEl.current.style);
}
}, [divEl.current]);
return (
<div>
<div ref={divEl} className="test"></div>
<pre>{JSON.stringify(divStyle, undefined, 2)}</pre>
</div>
);
};
export default IndexPage;
Is it because next.js SSR or should I add something to dependency array?
code sandbox here
You can use computed styles to get what you need, although it won't be a "simple" object with properties. You'll need to query each property individually:
if (divEl.current) {
setDivStyle(getComputedStyle(divEl.current));
}
and then you can do something like:
divStyle.getPropertyValue("background-color")
Here's an example sandbox (forked from yours) that demostrates it.
New React user here. I'm trying to make my first Navbar with React and getting 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
I tried to execute npm i -S react react-dom and also check that I'm executing the Hook correctly (understanding this concept yet, sorry). My current code is very simple:
import React from 'react';
import { Nav } from './NavbarElements';
const Navbar = () => {
return (
<Nav>
</Nav>
);
};
export default Navbar;
Why I'm getting this error with Nav tag? The element Nav is declared in NavbarElements.js:
import styled from 'styled-components';
export const Nav = styled.nav`
background: #000;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
position: sticky;
top: 0;
z-index: 10;
#media screen and (max-width: 960px) {
trasition: 0.8s all ease;
}
`;
Moreover, I'm using React to make the front part of a DApp (blockchain App). I have my file main file App.js (which's kinda big):
class App extends Component {
state = { storageValue: 0, web3: null, accounts: null, contract: null };
componentDidMount = async () => {
try {
// Get network provider and web3 instance.
const web3 = await getWeb3();
// Use web3 to get the user's accounts.
const accounts = await web3.eth.getAccounts();
// Get the contract instance.
const networkId = await web3.eth.net.getId();
const deployedNetwork = SimpleStorageContract.networks[networkId];
const instance = new web3.eth.Contract(
SimpleStorageContract.abi,
deployedNetwork && deployedNetwork.address,
);
instance.options.address = "0xA65990EC0CA555d2eCDD1d84E9D1397CFA967E60"
// Set web3, accounts, and contract to the state, and then proceed with an
// example of interacting with the contract's methods.
this.setState({ web3, accounts, contract: instance }, this.runExample);
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load web3, accounts, or contract. Check console for details.`,
);
console.error(error);
}
};
runExample = async () => {
const { accounts, contract } = this.state;
// Stores a given value, 5 by default.
await contract.methods.set(25).send({ from: accounts[0] });
// Get the value from the contract to prove it worked.
const response = await contract.methods.get().call();
// Update state with the result.
this.setState({ storageValue: response });
};
render() {
if (!this.state.web3) {
return <div>Loading Web3, accounts, and contract...</div>;
}
return (
<div className="App">
<Router>
<Navbar />
</Router>
</div>
);
}
}
export default App;
And, the error is on the line this.setState({ web3, accounts, contract: instance }, this.runExample);. My question is: Am I declaring my react "interface" in a bad place? It's after render() instruction (at the end of the file), where I thought it is. But, the point is that I'm getting that error only when I use the Nav tag. Am I misunderstanding something?
I have this code:
/* eslint-disable react/display-name */
import { Box, Button, LinearProgress, makeStyles } from '#material-ui/core';
import { Refresh } from '#material-ui/icons';
import { SearchHistoryContext } from 'appContext';
import useSnackBar from 'components/useSnackbar';
import PropTypes from 'prop-types';
import QueryString from 'qs';
import React, { useContext, useEffect, useState } from 'react';
import { formatDate } from 'utils/formatDate';
import http from 'utils/http';
const styles = makeStyles(() => ({
progress: {
margin: '-15px -15px 11px -15px',
},
button: {
width: '150px',
},
details: {
fontSize: '15px',
},
}));
const LogsMainPage = props => {
const classes = styles();
const { location, history, match, updateQuery } = props;
const [displaySnackbar] = useSnackBar();
const updateData = async () => {
history.push({
search: QueryString.stringify(query),
state: 'compliance-logs',
});
};
const logTableColumn = [
{
columns: [
{
Header: 'Timestamp',
id: '_id',
accessor: c => formatDate(c.timestamp, 'd/MM/yyyy, H:mm:ss'),
minWidth: 120,
},
{
Header: 'User Email',
id: 'user_email',
accessor: c => c.user_email,
minWidth: 80,
},
],
},
];
return (
<div>
{isLoading && <LinearProgress className={classes.progress} />}
<Box display="flex" justifyContent="flex-end">
<Button
id="refresh-button"
variant="outlined"
color="primary"
className={classes.button}
disabled={isLoading}
onClick={updateData}
startIcon={<Refresh />}
>
Refresh
</Button>
</Box>
<Box mb={2} />
</div>
);
};
LogsMainPage.propTypes = {
history: PropTypes.object,
match: PropTypes.object,
location: PropTypes.object,
updateQuery: PropTypes.func,
};
export default LogsMainPage;
Unit Test:
import LogsMainPage from 'containers/Log/LogsMainPage';
import { shallow } from 'enzyme';
import notistack from 'notistack';
import React from 'react';
jest.mock('notistack', () => ({
useSnackbar: jest.fn(),
}));
const enqueueSnackbar = jest.fn();
jest.spyOn(notistack, 'useSnackbar').mockImplementation(() => {
return { enqueueSnackbar };
});
jest.mock('react', () => ({
...jest.requireActual('react'),
useContext: () => ({
searches: {},
}),
}));
afterEach(() => {
jest.clearAllMocks();
});
describe('render test', () => {
const mockCallBack = jest.fn();
const wrapper = shallow(
<LogsMainPage history={{ push: jest.fn() }} location={{ search: {} }} />,
);
it('renders without crashing', () => {
expect(wrapper).toHaveLength(1);
});
it('renders refresh button without crashing', () => {
const button = wrapper.find('#refresh-button');
expect(button).toHaveLength(1);
button.setProps({ onClick: mockCallBack });
button.simulate('click');
expect(mockCallBack).toHaveBeenCalledTimes(1);
});
});
When I ran my single test (each of expect) it always pass. But if I ran the describe part it always failed.
TypeError: Cannot read property 'get' of undefined
the editor shown the error is on const classes = styles() part but all my other unit test using same as this, is passed.
Any solution?
Short Answer: In your case, it's because you mocked useContext BUT makeStyles internally uses useContext, hence it cannot find the context object it was supposed to find and try a get method of an undefined result in the mocked context you provided. So you have to:
Mock makeStyles correctly.
Mock useContext EXCEPT for the Material UI contexts.
Let's dive deeper.
Mocking Material UI makeStyles with simple jest function make you loose test coverage. When making it more complex, it leads to some problems.
But when you fix it, or reverse it, each problem solved leads to another when using shallow rendering (mount will not do it, can be slow on top level components):
it lost test coverage when calling useStyles which is now an empty function with no style (const useStyles = makeStyles(theme => {...}))
Not mocking it throw error for additional values of a custom theme
Binding mocked function argument with the custom theme works, and you can call function argument to fill coverage. But you loose coverage if passing parameters when calling useStyles({ variant: 'contained', palette: 'secondary' }) (result function of makeStyles)
Lot of things broke when mocking useContext, because makeStyles result function uses useContext internally.
(example of useStyles parameter handling)
{
backgroundColor: props => {
if (props.variant === 'contained') {
return theme.palette[props.palette].main;
}
return 'unset';
},
}
You should know how to correctly mock makeStyles for maximum testing experience. I managed to solved all of these problems and use manual mock https://jestjs.io/docs/en/manual-mocks (or inline mocking as alternative at the bottom):
Step 1:
I mocked in the core path instead, but both should work: <root>/__mocks__/#material-ui/core/styles.js
// Grab the original exports
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Styles from '#material-ui/core/styles';
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
import options from '../../../src/themes/options'; // I put the theme options separately to be reusable
const makeStyles = func => {
/**
* Note: if you want to mock this return value to be
* different within a test suite then use
* the pattern defined here:
* https://jestjs.io/docs/en/manual-mocks
*/
/**
* Work around because Shallow rendering does not
* Hook context and some other hook features.
* `makeStyles` accept a function as argument (func)
* and that function accept a theme as argument
* so we can take that same function, passing it as
* parameter to the original makeStyles and
* bind it to our custom theme, created on the go
* so that createMuiTheme can be ready
*/
const theme = createMuiTheme(options);
return Styles.makeStyles(func.bind(null, theme));
};
module.exports = { ...Styles, makeStyles };
So basically, this is just using the same original makeStyles and passes it the custom theme on the go which was not ready on time.
Step 2:
makeStyles result uses React.useContext, so we have to avoid mocking useContext for makeStyles use cases. Either use mockImplementationOnce if you use React.useContext(...) at the first place in you component, or better just filter it out in your test code as:
jest.spyOn(React, 'useContext').mockImplementation(context => {
// only stub the response if it is one of your Context
if (context.displayName === 'MyAppContext') {
return {
auth: {},
lang: 'en',
snackbar: () => {},
};
}
// continue to use original useContext for the rest use cases
const ActualReact = jest.requireActual('react');
return ActualReact.useContext(context);
});
And on your createContext() call, probably in a store.js, add a displayName property (standard), or any other custom property to Identify your context:
const store = React.createContext(initialState);
store.displayName = 'MyAppContext';
The makeStyles context displayName will appear as StylesContext and ThemeContext if you log them and their implementation will remain untouched to avoid error.
This fixed all kind of mocking problems related to makeStyles + useContext. And in term of speed, it just feels like the normal shallow rendering speed and can keep you away of mount for most use cases.
ALTERNATIVE to Step 1:
Instead of global manual mocking, we can just use the normal jest.mock inside any test. Here is the implementation:
jest.mock('#material-ui/core/styles', () => {
const Styles = jest.requireActual('#material-ui/core/styles');
const createMuiTheme = jest.requireActual(
'#material-ui/core/styles/createMuiTheme'
).default;
const options = jest.requireActual('../../../src/themes/options').default;
return {
...Styles,
makeStyles: func => {
const theme = createMuiTheme(options);
return Styles.makeStyles(func.bind(null, theme));
},
};
});
Since then, I also learned to mock useEffect and calling callback, axios global interceptors, etc.
---Store.js----
import React, { createContext, useReducer } from "react";
import Reducer from "./Reducer";
const initialState = {
userActivityData: {},
};
const Store = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>{children}</Context.Provider>
);
};
export const Context = createContext(initialState);
Context.displayName = "MyAppContext";
export default Store;
---Component.js--- using the above context store.
#adding context store in the test file works for me. mocking usecontext will break material styles.
---Component.test.js---
import Store from "../../Utils/Store";
beforeEach(() => {
wrapper = mount(
<Store>
<Component/>
</Store>
);
});
Note: This is not the complete code. But the point is, adding context store to the test file solved the problem.
I am using a react library for swiping: https://github.com/voronianski/react-swipe
Making it work is straight-forward:
import React, {useEffect, useState} from 'react';
import ReactDOM from 'react-dom';
import ReactSwipe from 'react-swipe'
import styled from 'styled-components'
const StyledBox = styled.div`
height: 50px;
width: 100%;
background-color: orange;
`
const Carousel = () => {
let reactSwipeEl;
const [position, setPosition] = useState(0)
console.log('position', position)
const swipeOptions = {
continuous: false,
transitionEnd() {
setPosition(reactSwipeEl.getPos())
}
}
return (
<div>
<ReactSwipe
className="carousel"
swipeOptions={swipeOptions}
ref={el => (reactSwipeEl = el)}
>
<StyledBox>PANE 1</StyledBox>
<StyledBox>PANE 2</StyledBox>
<StyledBox>PANE 3</StyledBox>
</ReactSwipe>
<Circles>
<Circle isActive={0 === position} />
<Circle isActive={1 === position}/>
</Circles>
</div>
);
};
export default Carousel
The code that I added is the one related with useState. The library works fine, but every time I swipe, I use the callback transitionEnd, in order to update the position state. It is updated, but since a state variable changes, the whole Carousel component gets updated, setting automatically the init value, which is 0.
I don't understand how can I avoid this problem which is, updating the state without re-rendering the whole component. Should I split it in two components and use a provider?
I tried it, but when the state of the provider changes, both components are also re-render
The swipeOptions is recreated on each render, which causes the ReactSwipe to rerender as well. Wrap the swipeOptions with useMemo() (sandbox);
const swipeOptions = useMemo(() => ({
continuous: false,
transitionEnd() {
setPosition(reactSwipeEl.current.getPos())
}
}), []);
I have a Charities component that shows text "Sorry..." when the status prop is === "error":
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: ${props => (props.hide ? "none" : "block")};
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
<ErrorNotification hide={status !== "error"}>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
...
</Container>
);
export default Charities;
I'm trying to test this with jest like this:
import React from "react";
import { mount, shallow } from "enzyme";
import { Charities } from "./index";
describe("Charities", () => {
let props;
let mountedCharities;
const charities = () => {
if (!mountedCharities) {
mountedCharities = mount(<Charities {...props} />);
}
return mountedCharities;
};
beforeEach(() => {
props = {
status: undefined,
...
};
mountedCharities = undefined;
});
describe("when status is pending", () => {
beforeEach(() => {
props.status = "pending";
});
it("doesn't render error", () => {
expect(charities().text()).not.toMatch(/Sorry/); // <---------- FAILS
});
});
});
My test fails with:
Expected value not to match:
/Sorry/
Received:
"Sorry, something was wrong with your payment. Please try again.Congratulations! You have successfully made a donation."
It seems like it's loading the children of the styled components even when it doesn't meet the conditions. How would I test this?
Your code is working as expected, it's just that styled() works by putting class names on the elements to control the styling.
In the unit test ErrorNotification is still there but it has css classes on it that will give it display: none in the final rendered HTML.
To make your components easier to unit test I recommend doing the hiding within Charities like this:
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: block;
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
{status !== "error" ? null : (
<ErrorNotification>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
)}
...
</Container>
);
export default Charities;
That way if the status in the props for Charities is anything except 'error' then ErrorNotification won't be rendered at all.
You are using the attribute hide which maps to 'display: none' when true this still renders the component albeit invisibly you should do something like this instead:
{ status === "error" &&
<ErrorNotification >
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
}