React createPortal not passing current context - reactjs

I am trying to pass context to an element manually rendered to the dom (because of a third-party library).
For some reason the context is not being passed correctly. I end up with:
Portal Context: false
Regular Context: true
import React, { useEffect, useContext, useRef } from "react";
import ReactDOM from "react-dom";
const AppContext = React.createContext(false);
function RenderAppContext() {
const context = useContext(AppContext);
return <>{JSON.stringify(context)}</>;
}
function App() {
const portalRef = useRef(null);
useEffect(() => {
ReactDOM.render(
ReactDOM.createPortal(<RenderAppContext />, portalRef.current),
document.createElement("div")
);
}, []);
return (
<AppContext.Provider value={true}>
<div style={{ display: "flex" }}>
<span>Portal Context:</span>
<div ref={portalRef} />
</div>
<div style={{ display: "flex" }}>
<span>Regular Context:</span>
<RenderAppContext />
</div>
</AppContext.Provider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
https://codesandbox.io/s/dawn-surf-wt0if4?file=/src/index.js

Related

Why the context variable failed to pass from componet Header.jsx to mainPage.js

I would like the wording used for the layout inside mainPage would change according to the language selected at component Header.jsx. However, change in Header.jsx could not pass to Header.jsx, therefore, noting is changed when clicking on the language selector.
import React, { useState, useEffect } from "react";
import Header from "../Resources/Components/Header";
import Footer from "../Resources/Components/Footer";
import {MainPageContext, useMainPageContext } from "../Resources/Hooks/mainPageContext";
import "./MainPage.css";
const MainPage = () => {
const context = useMainPageContext();
const {
language,
} = context;
useEffect(()=>{
console.log("is me hi!")
},[language])
const [introductionPage, setIntroductionPage] = useState(0);
console.log("language is", language)
//const [language, setLanguage]= useState(0);
//below is the script of the test description. 隨時可以加入新array做新language。
const renderLanguageSwitch= (language) => {
switch(language) {
case 0:
return ['測試開始','測試資料採集同意書'];
case 1:
return ['Test Start', 'Test Data Collection Agreement']
default:
return ['測試開始','測試資料採集同意書'];
}
};
const renderButtonSwitch= (language) => {
switch(language) {
case 0:
return ['我同意', '我拒絕'];
case 1:
return ['I agree', 'I disagree']
default:
return ['我同意', '我拒絕'];
}
};
return (
<div className="MainPage">
<Header />
<div
style={{
width: "100%",
height: "100vh",
backgroundColor: "#F5821F",
margin: "0",
}}
>
{introductionPage === 0 && (
<button
className="testStartBox"
onClick={() => {
setIntroductionPage(1);
}}
>
{renderLanguageSwitch(language)[0]}
</button>
)}
{introductionPage !== 0 && (
<div>
<div
className="testDescriptionBox"
onClick={() => {
setIntroductionPage(introductionPage + 1);
}}
>
{renderLanguageSwitch(language)[1]}
</div>
<div className="testAgreement">
</div>
</div>
)}
<div
className="buttonWrapper"
style={{ display: introductionPage === 1 ? "" : "none" }}
>
<button onClick={() => {
setIntroductionPage(introductionPage + 1);
}}> {renderButtonSwitch(language)[0]}</button>
<button onClick={() => {
setIntroductionPage(0);
}}>{renderButtonSwitch(language)[1]}</button>
</div>
</div>{" "}
<Footer />
</div>
);
};
export default MainPage;
There is a language selector on the component Header.jsx, I would like to change the language, then change the content of MainPage. However, it doesn't work.
import React from "react";
import { useMainPageContext } from "../Hooks/mainPageContext";
const Header = () => {
const context = useMainPageContext();
const {
language,
onSetLanguage,
} = context;
return (
<div className="header">
<h1
style={{
display: "flex",
flexFlow:"row",
alignItems:"center",
width: "calc(100% - 10%)",
height: "4vh",
}}
>
<div style={{display:"flex", color: "#F5821F",}}>
<img src={require("../Images/Logo.png")} style={{width:"50%", height:"7.5%", marginTop:"0vh"}} alt="image name"/>
<div style={{ top: "0", margin: "0vh", marginLeft:"2vw", width:" 100%", fontSize:"3vw"}}>中心</div>
</div>
<div><div style={{marginTop:"1vh", fontSize:"2vw"}} onClick={()=>{language===1? onSetLanguage(0):onSetLanguage(1);
}}>繁體/ English</div></div>
</h1>
</div>
);
};
export default Header;
I have changed the coding , so that it is a context variable passed to the mainPage.js, however, I have another problem, the missing transition key.
Here is my i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
backend: {
loadPath: `/locales/{{lng}}/translation.json`,
parse: data => data,
},
lng: 'en',
fallbackLng: 'en',
debug: true,
resources: {
'en': {
translation: 'en',
},
'tw': {
translation: 'tw',
},
},
interpolation: {
escapeValue: false
}
});
export default i18n;
Here is the App.js
import React , { Component, Suspense , useState, useCallback} from 'react';
import { useTranslation, withTranslation, Trans ,I18nextProvider} from 'react-i18next';
import ReactDOM from "react-dom/client";
import{
createBrowserRouter, RouterProvider,BrowserRouter as Router
} from "react-router-dom";
import logo from './logo.svg';
import './App.css';
//Import the pages
import MainPage from "./MainPage/MainPage";
import A1SubjectPage1 from "./ASubject/A1SubjectPage1";
import B1ResearcherPage1 from "./BResearcher/B1ResearcherPage1";
import Header from './Resources/Components/Header';
// use hoc for class based components
class LegacyWelcomeClass extends Component {
render() {
const { t } = this.props;
return <h2>{t('title')}</h2>;
}
}
const Welcome = withTranslation()(LegacyWelcomeClass);
// Component using the Trans component
/*function MyComponent() {
return (
<Trans i18nKey="description.part1">
To get started, edit <code>src/App.js</code> and save to reload.
</Trans>
);
}
*/
// page uses the hook
function App() {
const { t, i18n } = useTranslation();
const [language, setLanguage] = useState('en');
const onSetLanguage = useCallback((lng) => {
setLanguage(lng);
}, []);
const router = createBrowserRouter([
{
path: "/",
element: <div><MainPage/></div>,
},
{
path: "/mainPage",
element: <div>wowkdjfkdow<MainPage /></div>,
},
{
path: "/A1SubjectPage1",
element: <div>puripuri<A1SubjectPage1 /></div>,
},
{
path: "/B1ResearcherPage1",
element: <div>ReRe<B1ResearcherPage1 /></div>,
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<I18nextProvider i18n={i18n}>
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
</I18nextProvider>
);
let routes;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> This is a project
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Hello World Again and Again
</a>
</header>
<Router>
<div className="App-intro">
</div>
<main>{routes}</main>
</Router>
</div>
);
}
export default App;
Here is the mainPage.js
import React, { useState, useEffect, Component , Suspense, useCallback} from "react";
import Header from "../Resources/Components/Header";
import Footer from "../Resources/Components/Footer";
import { useTranslation, withTranslation, Trans } from 'react-i18next';
import {MainPageContext, useMainPageContext } from "../Resources/Hooks/mainPageContext";
import "./MainPage.css";
class LegacyWelcomeClass extends Component {
render() {
const { t } = this.props;
return <h2>{t('title')}</h2>;
}
}
const Welcome = withTranslation()(LegacyWelcomeClass);
const MainPage = () => {
const { t, i18n } = useTranslation();
const context = useMainPageContext();
const [language, setLanguage] = useState('tw');
const onSetLanguage = useCallback((lng) => {
setLanguage(lng);
}, []);
const [introductionPage, setIntroductionPage] = useState(0);
//const [language, setLanguage]= useState(0);
//below is the script of the test description. 隨時可以加入新array做新language。
const renderLanguageSwitch = () => {
return [t('testStart'), t('testAgreement')];
};
const renderButtonSwitch = () => {
return [t('agree'), t('disagree')];
};
return (
<div className="MainPage">
<Trans i18nKey="title">
<h2>{t('title')}</h2>
</Trans>
<Header onSetLanguage={onSetLanguage} />
<div
style={{
width: "100%",
height: "100vh",
backgroundColor: "#F5821F",
margin: "0",
}}
>
{introductionPage === 0 && (
<button
className="testStartBox"
onClick={() => {
setIntroductionPage(1);
}}
>
{t('agree')}, {t('disagree')}
</button>
)}
{introductionPage !== 0 && (
<div>
<div
className="testDescriptionBox"
onClick={() => {
setIntroductionPage(introductionPage + 1);
}}
>
{t('description')}
</div>
<div className="testAgreement">
</div>
</div>
)}
<div
className="buttonWrapper"
style={{ display: introductionPage === 1 ? "" : "none" }}
>
<button onClick={() => {
setIntroductionPage(introductionPage + 1);
}}> {t('description')}</button>
<button onClick={() => {
setIntroductionPage(0);
}}>{t('agreement')}</button>
</div>
</div>{" "}
<Footer />
</div>
);
};
export default MainPage;
Here is the Header.js
import React, { useCallback, useContext } from "react";
import { useMainPageContext } from "../Hooks/mainPageContext";
import { useTranslation } from 'react-i18next';
const Header = () => {
const { t, i18n } = useTranslation();
const context = useMainPageContext();
const { onSetLanguage } = context;
const toggleLanguage = useCallback((lng) => {
i18n.changeLanguage(lng);
onSetLanguage(lng);
}, [i18n, onSetLanguage]);
const currentLanguage = i18n.language;
return (
<div className="header">
<h1
style={{
display: "flex",
flexFlow:"row",
alignItems:"center",
width: "calc(100% - 10%)",
height: "4vh",
backgroundColor: "white",
paddingTop:"0",
padding: "2.5%",
paddingLeft: "5%",
paddingRight: "5%",
justifyContent:"space-between"
}}
>
<div style={{display:"flex", color: "#F5821F",}}>
<img src={require("../Images/cmghLogo.png")} style={{width:"50%", height:"7.5%", marginTop:"0vh"}} alt="logo"/>
<div style={{ top: "0", margin: "0vh", marginLeft:"2vw", width:" 100%", fontSize:"3vw"}}>中心</div>
</div>
<div>
<header>
<button onClick={() => toggleLanguage('en')} disabled={currentLanguage === 'en'}>English</button>
<button onClick={() => toggleLanguage('tw')} disabled={currentLanguage === 'tw'}>中文</button>
</header>
</div>
</h1>
</div>
);
};
export default Header;
The i18n.js did not set properly.
so that failed to read the translation key-value pair.
After I changed to install and using "i18next-http-backend" plugin, modified the i18n.js, the problem is fixed as the following:
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
i18n
// load translation using http -> see /public/locales
// learn more: https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
});
export default i18n;

React Native Error: Invalid hook call. Hooks can only be called inside of the body of a function component

I'm programming with some friends a Chess App and now we get an Error implementing the Chess itself.
we get the error in the first const of the function as well as at the Export of App.jsx
Our GitHub Repo: Chedu
App.jsx
import React, { useEffect, useState } from "react";
import "./App.css";
import { gameSubject, initGame, resetGame } from "./Game";
import Board from "./Board";
function App() {
const [board, setBoard] = useState([]); //get Error here
const [isGameOver, setIsGameOver] = useState();
const [result, setResult] = useState();
const [turn, setTurn] = useState();
useEffect(() => {
initGame();
const subscribe = gameSubject.subscribe((game) => {
setBoard(game.board);
setIsGameOver(game.isGameOver);
setResult(game.result);
setTurn(game.turn);
});
return () => subscribe.unsubscribe();
}, []);
return (
<div className="container">
{isGameOver && (
<h2 className="vertical-text">
GAME OVER
<button onClick={resetGame}>
<span className="vertical-text"> NEW GAME</span>
</button>
</h2>
)}
<div className="board-container">
<Board board={board} turn={turn} />
</div>
{result && <p className="vertical-text">{result}</p>}
</div>
);
}
export default App(); //Error Anonymous Function
in Index.js we are Rendering the function and export it.
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
export default ReactDOM.render(
<React.StrictMode>
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
</React.StrictMode>,
document.getElementById("root")
);
serviceWorker.unregister();
And at last we want to render the index.js in our ChessBoardPage
import React, { useState } from "react";
import {
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
Dimensions,
Switch,
} from "react-native"; //components
import ReactDOM from "react-dom";
import cheduLogo from "../Pictures/Logo.png";
import loginPictureBlack from "../Pictures/login.png";
import loginPictureWhite from "../Pictures/login_white.png";
import registerPictureBlack from "../Pictures/register.png";
import registerPictureWhite from "../Pictures/register_white.png";
import userPictureBlack from "../Pictures/user.png";
import userPictureWhite from "../Pictures/user_white.png";
import ChessGame from "./ChessBoard/index";
const windowWidth = Dimensions.get("window").width;
const windowHeight = Dimensions.get("window").height;
const { width } = Dimensions.get("window");
const x = 100;
const y = 200;
export default class TempPage extends React.Component {
state = {
switchValue: false,
backgroundColor: "white",
SwitchLogin: loginPictureBlack,
SwitchRegister: registerPictureBlack,
SwitchUser: userPictureBlack,
SunMoon: "☀️",
ShadowBackgroundColor: "white",
};
handleSwitchBackground = () => {
[...]
}
};
render() {
let { backgroundColor } = this.state;
return (
<View
style={{
windowWidth,
windowHeight,
backgroundColor: this.state.backgroundColor,
}}
>
[...]
{/*Content*/}
<View stlye={{ flex: 1 }}>
<ChessGame />
</View>
</View>
);
}
}
[...]
sometime we have issues in react when using anonymous functions. Since anonymous functions aren’t assigned an identifier (via const/let/var), they aren’t persistent whenever this functional component inevitably gets rendered again. This causes JavaScript to allocate new memory each time this component is re-rendered instead of allocating a single piece of memory only once when using “named functions”
consider refactoring your code as follows
import React, { useEffect, useState } from "react";
import "./App.css";
import { gameSubject, initGame, resetGame } from "./Game";
import Board from "./Board";
const App = () => {
const [board, setBoard] = useState([]); //get Error here
const [isGameOver, setIsGameOver] = useState();
const [result, setResult] = useState();
const [turn, setTurn] = useState();
useEffect(() => {
initGame();
const subscribe = gameSubject.subscribe((game) => {
setBoard(game.board);
setIsGameOver(game.isGameOver);
setResult(game.result);
setTurn(game.turn);
});
return () => subscribe.unsubscribe();
}, []);
return (
<div className="container">
{isGameOver && (
<h2 className="vertical-text">
GAME OVER
<button onClick={resetGame}>
<span className="vertical-text"> NEW GAME</span>
</button>
</h2>
)}
<div className="board-container">
<Board board={board} turn={turn} />
</div>
{result && <p className="vertical-text">{result}</p>}
</div>
);
};
export default App;
I am not sure why you are using HTML tags in react native, which think are not yet supported in App.jsx. You should return a <View/> tag instead of div.

Add an element with React, Typescript and useContext

I'm new to Typescript and not very familiar with the useContext hook. Basically, I have two simple components. I would like to add the items from my left component to the list on the right when I click on the button under them. My items have a name and description property. I just watch to display item.name on the side div on the right.
I would like to try and do it with useContext but I'm not sure where to start even after reading the documentation and a bunch of examples. They all seem too complicated for my tiny little example.
From what I understand, I need to:
Create something like AppContext.tsx
Create a context with createContext() // not sure about the arguments I have to put it in here with Typescript
Create a provider? // not sure about that either
Wrap my two components with the context provider
So any hint on the procedure would be appreciated. Thank you!
function App() {
return (
<div className="App">
<ItemList />
<ItemContainer />
</div>
);
}
My item list component:
function ItemList() {
return (
<div className="itemlist">
{items.map((item, index) => (
<div key={index}>
<div>{item.name}</div>
<div>{item.description}</div>
<button>Add to sidebar</button>
</div>
))}
</div>
);
}
And finally, my container on the right side:
function ItemContainer() {
return (
<div>
<h1>List of items</h1>
<p>Number of items: {}</p>
</div>
);
}
You can do something like this:
First create a context file named for example ItemList.context.tsx :
import React, {
createContext,
Dispatch,
FunctionComponent,
useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
itemList: Item[]; // type of your items that I declare in data.ts
setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
return (
<ItemListContext.Provider
value={{ itemList: itemList, setItemList: setItemList }}
>
{children}
</ItemListContext.Provider>
);
};
You can then add the context provider in the parent component ( App.tsx in your example):
import "./styles.css";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { ItemListContextProvider } from "./ItemList.context";
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<ItemList />
<ItemContainer />
</ItemListContextProvider>
</div>
);
}
and you can finally access your Item List by using the hook useContext in your two components:
for ItemList.tsx where you need to set the list (and optionally get the list to avoid putting twice an item):
import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";
const items: Item[] = data;
export default function ItemList() {
const { itemList, setItemList } = useContext(ItemListContext); // here you get your list and the method to set the list
const addItemToItemList = (item: Item) => {
//you are using the itemList to see if item is already in the itemList
if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
};
return (
<div className="itemlist">
{items.map((item, index) => (
<div style={{ marginBottom: 15 }} key={index}>
<div style={{ fontWeight: 800 }}>{item.name}</div>
<div>{item.description}</div>
<button onClick={() => addItemToItemList(item)}>
Add to sidebar
</button>
</div>
))}
</div>
);
}
And in your ItemContainer.tsx you only need the list so you can import only the setItemList from the context with useContext:
import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
export default function ItemContainer() {
const { itemList } = useContext(ItemListContext);
return (
<div style={{ flexGrow: 4 }}>
<h1 style={{ textAlign: "center" }}>List of items</h1>
<p>Number of items: {itemList.length}</p>
{itemList.length > 0 && (
<ul>
{itemList.map((item, i) => (
<li key={i}>{item.name}</li>
))}
</ul>
)}
</div>
);
}
UPDATE with a Router
It's quite the same thing you only need to wrap your browser router in the context provider if you want it to be at the highest place in your app (for a them provider or a dark mode provider for example):
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<BrowserRouter>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/itemList" component={ItemListPage} />
</Switch>
</BrowserRouter>
</ItemListContextProvider>
</div>
);
}
but I suggest you to put your provider the nearest place where your subscribers components are.
You can for example create a page component that will be use in the browser router and put in it the provider, like this:
ItemListPage.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;
and of course you remove the context provider in your App.tsx and it should look like :
App.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;

HOC's and Render Props With Functional Components in React 16

I'm somewhat confused on the relationship between functional components in React and the Render Props and HOC patterns.
That is,
is it true that the only way to create Render Prop is with a class component?
is it true that the only way to create an HOC is with a class component?
And the same for usage.
I'm trying to find examples of Render Props and HOC's with functional components and all I find are class components. I get that React Hooks do a lot of the same, but I'm trying to understand how the Render Props and HOC patterns can apply to functional components (or if they do at all)
Edit Below:
Applying what #chaimFriedman suggested, this is what I came up with using no class or component for an HOC hoping it makes sense.
import React, { useState, useEffect } from 'react';
import useAxios from 'axios-hooks';
function withFetching(url) {
return function(Speakers) {
return () => {
const [speakerData, setSpeakerData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [{ data, loading }, refetch] = useAxios('http://localhost:4000/speakers');
useEffect(() => {
setSpeakerData(data);
setIsLoading(loading);
}, [loading]);
if (isLoading) return <div>loading..</div>;
return <Speakers data={speakerData}></Speakers>;
};
};
}
const Speakers = function(props) {
//debugger;
return (
<ul>
{props.data.map((speaker) => (
<li key={speaker.id}>
<span>
{speaker.firstName} {speaker.lastName}
</span>
</li>
))}
</ul>
);
};
const API = 'http://localhost:4000/speakers';
export default withFetching(API)(Speakers);
Both render props and HOC can absolutely apply to functional components. Let's think a little more about what each one really is to see why they do in fact work with functions as well as classes.
Render props is when you have a prop that is a function which returns JSX. This of course should work for function components because aside from life cycle methods there really isnt much that is different than class components. Here is an example with code.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(props) {
return (
props.children()
);
}
function App() {
return (
<div className="App">
<Renderer>
{() => {
return (
<h1>I am being rendered by Renderer</h1>
);
}}
</Renderer>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now for HOC.
A HOC really is just a higher order function, but because we use it in react we call it a higher order component. A higher order function is a function which either accepts a function as an argument, or returns a function. Now a functional component can absolutely do this. Here is an example.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(Wrapped) {
return function New(props) {
return <Wrapped {...props} />
}
}
function Child(props) {
return (
<h1>Hello {props.name}</h1>
);
}
function App() {
const C = Renderer(Child)
return (
<div className="App">
<C name="john" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
EDIT: I realized my HOC example was wrong so updated.
I hope this helps.
Here is the sample code converting example from React documentation into function component. https://reactjs.org/docs/render-props.html
import React from "react";
const Cat = ({mouse}) => {
return (
<img
src="/cat.png"
alt="cat"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
};
const Mouse = (props) => {
const [state, setState] = React.useState();
const handleMouseMove = (event) => {
setState({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
{props.render(state)}
</div>
);
};
const MouseTracker = () => {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={(mouse) => <Cat mouse={mouse} />} />
</div>
);
};
export const App = () => {
return (
<div className="App">
<MouseTracker />
</div>
);
}

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