Next.js not able to pre-render error page - reactjs

I recently started to use Next.JS on a new project and it's working fine but I can't figure out how to keep my layout state active on client side when I throw a 404 error after clicking on a wrong link.
Thanks to Adam's Wathan article, I'm able to share my state between different pages :
On home page
On "about" page
But if i click on "error", it will render the _error.tsx without preserving data i wrote in the input.
This page seems to render the whole app tree on server side, despite the fact I provide the same layout. Is there anyway to prefetch this page, just like a regular one and avoid to lose some information without using a solution like Redux ? I'm note quite familiar with it and it seems a bit too much in this case.
Here is my code:
pages/_error.tsx
import { getLayout } from "../components/layout/mainLayout"
import { withTranslation } from "../i18n";
import { FlexDirectionProperty } from "csstype";
const imgStyle = {
maxWidth: "100%",
maxHeight: "100%"
};
const figureStyle = {
height: "80vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column" as FlexDirectionProperty
};
const CustomError: any = ({ status, t }: any) => (
<>
<figure style={figureStyle}>
<figcaption>
<h1>Statut:{t('WELCOME')}</h1>
</figcaption>
<img
alt="Showing a properly cat according the status code"
src={`https://http.cat/${status}`}
style={imgStyle}
/>
</figure>
</>
)
CustomError.getLayout = getLayout;
CustomError.getInitialProps = async ({ res, err }: any) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : null
return {
statusCode: statusCode,
namespacesRequired: ["common"]
}
};
export default withTranslation('common')(CustomError)
components/layout/header.tsx
import Link from "next/link";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import { withTranslation, i18n } from "../../i18n";
import { capitalize } from "../../helpers/utils";
import Modal from "react-bootstrap/Modal";
import { useState } from "react";
const loadStamp = +new Date();
const Header: any = ({ t }: any) => {
const [show, setShow] = useState(false);
const [active, setActive] = useState("home");
return (
<>
<Navbar fixed="top" bg="light">
<Nav className="mr-auto" activeKey={active}>
<Nav.Item>
<Link href="/">
<Navbar.Brand onClick={() => setActive("home")} href="/">Random Project</Navbar.Brand>
</Link>
</Nav.Item>
...
</Nav>
</Navbar>
</>);
};
export default withTranslation("common")(Header);
components/layout/mainLayout.tsx
import Header from "./header";
import "bootstrap/dist/css/bootstrap.min.css";
import "../../public/stylesheets/style.scss";
type LayoutProps = {
title?: string;
children: any;
};
const Layout: React.FunctionComponent<LayoutProps> = ({ children }) => (
<>
<Header />
<main role="main" className="main">
{children}
</main>
</>
);
export const getLayout: any = (page: any) => <Layout>{page}</Layout>
export default Layout
And my _app.tsx
import React from 'react'
import App from 'next/app'
import { appWithTranslation } from '../i18n'
class MyApp extends App<any> {
render() {
const { Component, pageProps } = this.props
const getLayout = Component.getLayout || ((page: any) => page)
return (
<>
{getLayout(
<Component {...pageProps}></Component>
)}
</>
)
}
}
export default appWithTranslation(MyApp)

Related

multiple fetch requests using List ( 6 times after compiling, multiple while scrolling )

im new to react and im building an app where i encountered this problem, i have a component that fetches data and displays it in a list ,but on the page, it fetches multiple times each time i scroll the list.`
import axios from 'axios';
import React, {useState, useEffect} from 'react'
import Box from '#mui/material/Box';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import { FixedSizeList} from 'react-window';
import ListItemText from '#mui/material/ListItemText';
import { useNavigate } from "react-router-dom";
const JobsListData = (props) => {
const navigate = useNavigate();
var { index, style } = props;
const url = 'http://localhost:5000/api/v1/jobs'
const [jobsList,setJobs] = useState(null)
const jobsfunction = async () => {
try{
const data = await axios.get(url)
.then(response => {
setJobs(response.data)
})
}catch(e){
console.log(e);
}
}
useEffect(()=>{
jobsfunction();
},[]);
if(jobsList){
const maxlength = jobsList.total_results;
if(index<maxlength){return (
<ListItem key={index} component="div" disablePadding>
<ListItem >
<p><h3>{jobsList.jobs[index].name}</h3><br></br>
<b>Payrate</b> :{jobsList.jobs[index].payrate}<br></br>
<b>Adress</b> :{jobsList.jobs[index].adress}<br></br>
<b>Job ID</b> :{jobsList.jobs[index]._id}</p>
</ListItem>
</ListItem>
);}}
return ([])
}
function VirtualizedList() {
return (
<div className='VirtualizedList' id='VirtualizedList'>
<div className='container'>
<div className='col-2'>
<Box
sx={{ width: '100%', height: 400, maxWidth: 360, bgcolor: 'background.paper' }}
>
<FixedSizeList
height={400}
width={650}
itemSize={100}
itemCount={200}
>
{JobsListData}
</FixedSizeList>
</Box>
</div>
</div>
</div>
);
}
export default VirtualizedList;
`
when i remove the FixedSizeList the fetch request happens only once but when it is there, it fetches (6 times to be exact) in the beginning except for the scroll thing.
this problem causes the list to glitch out and sometimes disappear
try to fetch the data in the parent component
Something like that:
import axios from 'axios';
import React, {useState, useEffect} from 'react'
import Box from '#mui/material/Box';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import { FixedSizeList} from 'react-window';
import ListItemText from '#mui/material/ListItemText';
import { useNavigate } from "react-router-dom";
const JobsListData = (props) => {
const navigate = useNavigate();
var { index, style, jobsList } = props;
if(jobsList){
const maxlength = jobsList.total_results;
if(index<maxlength){return (
<ListItem key={index} component="div" disablePadding>
<ListItem >
<p><h3>{jobsList.jobs[index].name}</h3><br></br>
<b>Payrate</b> :{jobsList.jobs[index].payrate}<br></br>
<b>Adress</b> :{jobsList.jobs[index].adress}<br></br>
<b>Job ID</b> :{jobsList.jobs[index]._id}</p>
</ListItem>
</ListItem>
);}}
return ([])
}
function VirtualizedList() {
const url = 'http://localhost:5000/api/v1/jobs'
const [jobsList,setJobs] = useState(null)
const jobsfunction = async () => {
try{
const data = await axios.get(url)
.then(response => {
setJobs(response.data)
})
}catch(e){
console.log(e);
}
}
useEffect(()=>{
jobsfunction();
},[]);
return (
<div className='VirtualizedList' id='VirtualizedList'>
<div className='container'>
<div className='col-2'>
<Box
sx={{ width: '100%', height: 400, maxWidth: 360, bgcolor: 'background.paper' }}
>
<FixedSizeList
height={400}
width={650}
itemSize={100}
itemCount={200}
>
<JobsListData jobsList={jobsList}/>
</FixedSizeList>
</Box>
</div>
</div>
</div>
);
}
export default VirtualizedList;

Prop `className` did not match Server and Client

I followed many solutions like this https://stackoverflow.com/a/71081567/19460332 and the official solution for this https://github.com/mui/material-ui/blob/master/examples/nextjs but nothing worked for me. I am using nextjs , material ui and js-cookies
the issue I am facing is for the MUI Switch
have a look to my code here https://github.com/MaheshYadavGitHub/pizza-gallery:
Here is the code for the section that is breaking :
below is the Layout.js where the Switch to toggle darkMode is not working as expected. The darkTheme gets applied as it gets from the cookies but the switch button still stays in off position on the first render but after another render it works perfectly and when again manually refreshed it does the same behaviour :-
import { useState, useContext } from "react";
import {
Container,
AppBar,
Toolbar,
Typography,
Link,
Switch,
CssBaseline,
ThemeProvider,
createTheme,
} from "#material-ui/core";
import { red } from "#material-ui/core/colors";
import Head from "next/head";
import Image from "next/image";
import NextLink from "next/link";
import useStyles from "../utils/styles";
import { Store } from "../utils/Store";
import Cookies from "js-cookie";
const Layout = ({ title, children }) => {
const { state, dispatch } = useContext(Store);
const { darkMode } = state;
const classes = useStyles();
const theme = createTheme({
palette: {
type: darkMode ? "dark" : "light",
primary: {
main: "#556cd6",
},
secondary: {
main: "#19857b",
},
error: {
main: red.A400,
},
},
typography: {
fontFamily: ["Arial", "Roboto Condensed"].join(","),
fontWeight: 700,
h1: {
fontSize: "1.6rem",
margin: "1rem 0rem",
fontWeight: 600,
"#media (min-width:600px)": {
fontSize: "2.6rem",
},
},
h6: {
fontWeight: 700,
},
navLinks: {
color: "#fff",
},
},
});
const handleThemeChange = (event) => {
dispatch({ type: darkMode ? "DARK_MODE_OFF" : "DARK_MODE_ON" });
const newThemeMode = !darkMode;
Cookies.set("darkMode", newThemeMode ? "ON" : "OFF");
};
return (
<>
<Head>
<title>{title ? `${title} - Pizza Gallery` : "Pizza Gallery"}</title>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<NextLink href="/" passHref>
<Link>
<Typography
variant="h6"
className={classes.brand}
component="div"
>
Pizza Gallery
</Typography>
</Link>
</NextLink>
<div className={classes.grow}></div>
<Switch
id="switch"
checked={darkMode}
onChange={handleThemeChange}
color="primary"
></Switch>
<NextLink href="/login" passHref>
<Link>
<Typography>Login</Typography>
</Link>
</NextLink>
<NextLink href="/cart" passHref>
<Link>
<Typography>Cart</Typography>
</Link>
</NextLink>
</Toolbar>
</AppBar>
<Container className={classes.main}>{children}</Container>
<footer className={classes.footer}>
<Typography>all rights reserved © pizza gallery 2022</Typography>
</footer>
</ThemeProvider>
</>
);
};
export default Layout;
below is the Store :-
import Cookies from "js-cookie";
import { createContext, useReducer } from "react";
export const Store = createContext();
const initialState = {
darkMode: Cookies.get("darkMode") === "ON" ? true : false,
};
const reducer = (state, action) => {
switch (action.type) {
case "DARK_MODE_ON":
return { ...state, darkMode: true };
case "DARK_MODE_OFF":
return { ...state, darkMode: false };
default:
return state;
}
};
export const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return <Store.Provider value={value}>{children}</Store.Provider>;
};
/pages/_app.js
import { useEffect } from "react";
import "../styles/globals.css";
import { StoreProvider } from "../utils/Store";
function MyApp({ Component, pageProps }) {
useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<StoreProvider>
<Component {...pageProps} />
</StoreProvider>
);
}
export default MyApp;
/pages/_document.js
import { ServerStyleSheets } from "#material-ui/core/styles";
import Document, { Html, Head, Main, NextScript } from "next/document";
import React from "react";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head></Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () => {
return originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
};
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
If you're not using Server Side Rendering for that component, you may try importing the component, by turning off ssr:
import dynamic from 'next/dynamic';
const Component = dynamic(() =>
import('../components/path_to_component'), {
ssr: false,
});
Hope this helps!

State from redux store gets lost/ reset when changing route

I have a simple app that so far only has a main login component, and a home page with a navbar which the user is supposed to be routed to once they login to the app. The user experience should be as follows: user selects name from blue dropdown menu, once that name is selected, they click the green login button which dispatches setLoggedInUser from loggedInSlice.js and sets the selected user as the authorize object For some reason , my state from my redux store is lost when I wrap my login button in a router link tag which leads to the home page , but it is not lost when that login button doesnt link anywhere. I want to understand why I am not retaining my redux authUser state once I navigate from the userLogin.js component to the Main.js Component.
Components:
App.js
import logo from "./logo.svg";
import "./App.css";
import TestComponent from "./components/TestComponent";
import Main from "./components/Main";
import { Routes, Route, Link } from "react-router-dom";
import { useState } from "react";
import { Card, Button, Accordion, Dropdown } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import UserLogin from "./components/UserLogin";
import Nav from "./components/Nav";
function App() {
return (
<div className="App">
<Routes>
<Route path="home" element={<Main />} />
<Route path="/" element={<UserLogin />} />
<Route path="testcomponent" element={<TestComponent />} />
</Routes>
</div>
);
}
export default App;
UserLogin.js
import React from "react";
import { Routes, Route, Link, useNavigate } from "react-router-dom";
import { Redirect } from "react-router-dom";
import { useState, useEffect } from "react";
import { Card, Button, Accordion, Dropdown } from "react-bootstrap";
import { useSelector, useDispatch } from "react-redux";
import "bootstrap/dist/css/bootstrap.min.css";
import { setLoggedInUser } from "../features/loggedInSlice";
export default function UserLogin() {
let navigate = useNavigate();
const users = useSelector((state) => state.users);
const state = useSelector((state) => state);
const dispatch = useDispatch();
const [user, setUser] = useState("Select A User");
const [authorizedUser, setauthorizedUser] = useState({});
const handleSelect = (e) => {
setUser(e.target.text);
};
function authorizeUser() {
setauthorizedUser(
users.filter((userName) => userName.firstName === user)[0]
);
}
useEffect(() => {
dispatch(setLoggedInUser(authorizedUser));
}, [authorizedUser]);
function authorizeLogin() {
console.log(state);
}
console.log(state);
return (
<div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Card style={{ width: "50%" }}>
<Card.Header>Would you Rather?</Card.Header>
<Card.Body>
<Card.Title>Special title treatment</Card.Title>
<Card.Text>
With supporting text below as a natural lead-in to additional
content.
</Card.Text>
</Card.Body>
<Dropdown className="d-inline mx-2" onClick={handleSelect}>
<Dropdown.Toggle
id="dropdown-autoclose-true"
style={{ width: "60%" }}
>
{user}
</Dropdown.Toggle>
<Dropdown.Menu style={{ width: "60%" }}>
{users.map((user, index) => (
<Dropdown.Item href="#" key={index}>
{user.firstName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
<div>
<Link to="home">
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
</Link>
</div>
</Card>
<button onClick={authorizeLogin}>click</button>
</div>
</div>
);
}
Main.js
import React from "react";
import NavBar from "./Nav";
import { Routes, Route, Link } from "react-router-dom";
export default function Main() {
return (
<div>
<NavBar />
</div>
);
}
Nav.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { Card, Button, Accordion, Dropdown, Nav } from "react-bootstrap";
export default function NavBar() {
const state = useSelector((state) => state);
console.log(state);
const loggedInFirstName = state.loggedIn.authUser.firstName;
return (
<div style={{ display: "flex" }}>
<Nav justify variant="tabs" defaultActiveKey="/home">
<Nav.Item>
<Nav.Link>Home</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link>New Poll </Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link>Leaderboard</Nav.Link>
</Nav.Item>
</Nav>
<h5 style={{ paddingLeft: "15px", paddingTop: "15px" }}>Welcome</h5>
</div>
);
}
Redux Store and Slices:
store.js
import { configureStore } from "#reduxjs/toolkit";
import usersReducer from "../features/usersSlice";
import loggedIn from "../features/loggedInSlice";
export const store = configureStore({
reducer: {
users: usersReducer,
loggedIn: loggedIn,
},
});
loggedInSlice.js
import React from "react";
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
authUser: [],
};
export const loggedIn = createSlice({
name: "loggedIn",
initialState,
reducers: {
setLoggedInUser: (state, action) => {
console.log(action.payload);
state.authUser = action.payload;
},
},
});
export default loggedIn.reducer;
export const { setLoggedInUser } = loggedIn.actions;
usersSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = [
{
firstName: "matt",
age: 30,
total: 0,
},
{
firstName: "mike",
age: 25,
total: 0,
},
{
firstName: "steve",
age: 22,
total: 0,
},
];
export const usersReducer = createSlice({
name: "UsersSlice",
initialState,
});
export default usersReducer.reducer;
To summarize I need to understand why my authUser is added to my redux store only when the login button does not route to another component
code that adds the authUser
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
code that does not add the authUser
<Link to="home">
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
</Link>
Issue
The issue here is that enqueued React state updates are asynchronously processed. The navigation action to "/home" when wrapping the login button with a Link occurs well before the enqueued state update is processed. React sees that the UserLogin component is no longer mounted and ignores (silently) the state update.
Solution
I suggest moving the "login" logic into an asynchronous action. This will return a Promise to the UserLogin component that can be waited on and upon successful authentication issue the navigation action to the "/home" path.
Example:
loggedInSlice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
const initialState = {
authUser: []
};
const loginUser = createAsyncThunk(
"loggedIn/loginHandler",
async (user, { dispatch }) => {
... asynchronous login logic ...
// if successfully logged in
dispatch(loggedIn.actions.setLoggedInUser(user));
return ... response object/success message/etc...;
}
);
export const loggedIn = createSlice({
name: "loggedIn",
initialState,
reducers: {
setLoggedInUser: (state, action) => {
state.authUser = action.payload;
}
}
});
export default loggedIn.reducer;
export const { setLoggedInUser } = loggedIn.actions;
export { loginUser };
UserLogin
Dispatch the login action directly in the button's onClick handler, wait for resolved Promise, then navigate.
...
import { loginUser } from "../features/loggedInSlice";
export default function UserLogin() {
const navigate = useNavigate();
const [user, setUser] = useState("Select A User");
const users = useSelector((state) => state.users);
const state = useSelector((state) => state);
const dispatch = useDispatch();
...
function authorizeUser() {
const selectedUser = users.find((userName) => userName.firstName === user);
dispatch(loginUser(selectedUser))
.unwrap()
.then((response) => {
console.log({ response });
navigate("/home");
});
}
...
return (
<div>
<div ...>
<Card ...>
...
<div>
<Button
...
onClick={authorizeUser}
>
Login
</Button>
</div>
</Card>
...
</div>
</div>
);
}

How to add a video to React Responsive Carousel Npm Package?

I am trying to create a Carousel of images and embedded Youtube videos (the first item is a youtube video and the rest are images)
I am using the React Responsive Carusel npm package but I can't find a way to create the carousel I want without bugs.
I saw a solution over here but it's only for videos.
Here is my code at the moment:
import React from 'react'
import 'react-responsive-carousel/lib/styles/carousel.min.css' // requires a loader
import { Carousel } from 'react-responsive-carousel'
import ReactPlayer from 'react-player'
const ModalImagesPreview = () => {
return (
<div>
<Carousel>
<div>
<ReactPlayer
url='https://www.youtube.com/watch?v=ysz5S6PUM-U'
volume='1'
muted
width='100%'
playing={true}
/>
</div>
<div>
<img src='https://cdn.dribbble.com/users/2146089/screenshots/12387473/media/bf9ffb522fe68ba57ebdc62bc3f16cc5.png' />
</div>
<div>
<img src='https://cdn.dribbble.com/users/427857/screenshots/12318706/media/f30f662dbf160022412812881b2afb43.jpg' />
</div>
</Carousel>
</div>
)
}
export default ModalImagesPreview
Thanks for the helpers!
Here is what I found on the react-responsive-carousel website
import 'react-responsive-carousel/lib/styles/carousel.min.css'; // requires a loader
import React from 'react';
import ReactPlayer from 'react-player';
import { Carousel } from 'react-responsive-carousel';
import { PropTypes } from 'prop-types';
import { Grid, makeStyles } from '#material-ui/core';
const DUMMY_VIDEOS = [
{
_id: '5fd025a181e2c80897c14ae1',
videoUrl: 'https://www.youtube.com/embed/AVn-Yjr7kDc'
}
];
const useStyles = makeStyles(theme => ({
carousel: {
margin: theme.spacing(2)
}
}));
const YoutubeSlide = ({ url, isSelected }) => (
<ReactPlayer width="100%" height="276px" url={url} playing={isSelected} />
);
const CarouselVideo = ({ data }) => {
const classes = useStyles();
const customRenderItem = (item, props) => (
<item.type {...item.props} {...props} />
);
const getVideoThumb = videoId =>`https://img.youtube.com/vi/${videoId}/default.jpg`;
const getVideoId = url =>url.substr('https://www.youtube.com/watch?v='.length, url.length);
const customRenderThumb = children =>
children.map(item => {
const videoId = getVideoId(item.props.url);
return <img key={videoId} src={getVideoThumb(videoId)} />;
});
return (
<Grid item md={6} xs={12}>
<Carousel
autoPlay={false}
className={classes.carousel}
emulateTouch={true}
showArrows={true}
showThumbs={true}
showStatus={false}
infiniteLoop={true}
renderItem={customRenderItem}
renderThumbs={customRenderThumb}
>
{data.map(v => (
<YoutubeSlide
url={v.videoUrl}
muted
playing={false}
key={v._id ? v._id : v.id}
/>
))}
</Carousel>
</Grid>
);
};
YoutubeSlide.propTypes = {
url: PropTypes.string,
isSelected: PropTypes.bool
};
CarouselVideo.propTypes = {
data: PropTypes.array
};
CarouselVideo.defaultProps = {
data: DUMMY_VIDEOS
};
export default CarouselVideo;
Last step is to call this CarouselVideo where you want to render with your data as props or just call it without passint anythind => this will display the dummy videos I added there.
So you can call it like the below:
// DisplayVideo.js file with default videos as shown below
const DisplayVideo = () => {
render( <CarouselVideo />)
}
// or with your data
const DisplayVideo = ({PASS_YOU_DATA_HERE}) => {
render( <CarouselVideo data={PASS_YOU_DATA_HERE} />)
}
Result:
(source: https://www.npmjs.com/package/react-responsive-carousel)

How to test react component with hooks using react testing library

I am trying to test a component which use useTheme hook provided by emotion.js. The theme is set during the app initialization.
Now when I write my test cases, the useTheme hook is unable to fetch the styles data as only header component is getting mounted for testing. How to mock the data provided by hooks.
app.js
import { theme } from '../src/utils/styles/themes/default';
const AppRoot = ({ path, Router }) => {
const routeRenderFunction = (props) => <RouteHandler route={props} />;
return (
<ThemeProvider theme={theme}>
<Router location={path} context={{}}>
<Switch>
{routePatterns.map((routePattern) => (
<Route key={routePattern} path={routePattern} render={routeRenderFunction} />
))}
</Switch>
</Router>
</ThemeProvider>
);
};
header.js
import React from "react";
import { css } from "emotion";
import { useTheme } from "emotion-theming";
import * as styles from "./Header.style";
const Header = ({userName = 'Becky Parsons', clinicName = 'The University of Southampton'}) => {
const theme = useTheme();
return (
<div className={css(styles.accountDetails(theme))}>
<div className={css(styles.accountContainer)}>
<div className={css(styles.accountSpecifics)}>
<h5 className={css(styles.accountDetailsH5(theme))} data-testid="user-name">{userName}</h5>
<h6 className={css(styles.accountDetailsH6(theme))} data-testid="clinic-name">
{clinicName}
</h6>
</div>
<div className={css(styles.avatar)} />
</div>
</div>
);
};
export default Header;
header.test.js
import React from 'react'
import {render} from '#testing-library/react';
import Header from './Header';
test('Check if header component loads', () => {
const { getByTestId } = render(<Header userName='Becky Parsons' clinicName='The University of Southampton'/>);
expect(getByTestId('user-name').textContent).toBe('Becky Parsons');
expect(getByTestId('clinic-name').textContent).toBe('The University of Southampton');
})
header.style.js
export const accountDetails = theme => ({
height: '70px',
backgroundColor: theme.header.backgroundColor,
textAlign: 'right',
padding: '10px 40px'
});
export const accountContainer = {
display: 'flex',
justifyContent: 'flex-end'
};
Error: Uncaught [TypeError: Cannot read property 'backgroundColor' of undefined]
You should wrap your component with ThemeProvider, try this;
test('Check if header component loads', () => {
const { getByText } = render(
<ThemeProvider theme={your theme object...}>
<Header userName='Becky Parsons' clinicName='The University of Southampton'/>
</ThemeProvider >
);
...
});
and you can look here: https://github.com/emotion-js/emotion/blob/master/packages/emotion-theming/tests/use-theme.js

Resources