Jest testing with props - reactjs

React, typescript, and jest are all new to me. I have a component, 'myContacts,' displaying tabs and tab panels with contacts; an address book.
Here is a portion of myContacts.
import ... as needed ...
export default function myContacts( props ){
const { data, link } = props;
const [limit, setLimit] = useState<number>(data.limit);
(handleTabsChange and various other logic)
useEffect(() => {
const offset = (page - 1) * limit;
setContactsPerPage(
entries[currentTab?.activeKey]?.slice(offset, offset + limit)
);
setLimit(viewData.limit);
}, [entries, currentTab?.activeKey, page, limit, data.limit]);
if (currentTab?.activeKey == null) {
return null;
}
return (
<div>
<Tabs
index={tabIndex}
onChange={handleTabsChange}
>
<TabList>
{tabs.map((item: string) => (
<Tab key={item}>{item}</Tab>
))}
</TabList>
<TabPanels>
{tabs.map((item: string) => (
<TabPanel key={item}>
... do some stuff ...
/>
))}
</TabPanel>
))}
</TabPanels>
</Tabs>
<Container>
<div className="w-full md:w-8/12">
<Pagination
totalEntries={entries[currentTab?.activeKey]?.length}
limit={data.limit}
page={page}
setPage={setPage}
/>
</div>
</Container>
</div>
);
}
Here is the test I am working on. Very simple. Check that myContacts has rendered.
import { render, screen } from "#testing-library/react";
import myContacts from "../myContacts";
test("render contacts", () => {
render(<myContacts />);
expect(screen.getByText(/component myContacts/i)).toBeInTheDocument();
});
However, I am getting the following error.
TypeError: Cannot read properties of undefined (reading 'limit')
23 | const { link, route, viewData } = props;
24 |
> 25 | const [limit, setLimit] = useState<number>(viewData.limit);
My initial thought was to do the following:
test("render contacts", () => {
const limit = 5;
render(<myContacts limit={limit} />);
expect(screen.getByText(/component myContacts/i)).toBeInTheDocument();
});
but this resulted in the same error.
I know this error is because 'limit' is not defined in the test, but I am at a loss on how to define and pass it in the test.

Related

I need to pass some data through some components React

I'm doing a broker simulator and I need to pass the data (side,price,volume,timestamp) into different tab, which is like grandparent's brother. The problem is I have no idea, how to transport data that far
Modalbuy.js
import React from "react";
import "./modal.css";
const Modalbuy = ({active, setActive,price}) => {
return (
<div className={active ? "modal active" : "modal"} onClick={() => setActive(false)}>
<div className="modal__content" onClick={e => e.stopPropagation()}>
<header>Make order</header>
<p>BUY {price}</p>
<input placeholder="Volume">{volume}</input>
<div>
<button onClick={() =>{this.props.addData("BUY", price, volume,)}}>Ok</button>
<button>Cancel</button>
</div>
</div>
</div>
)
}
export default Modalbuy;
Select.js
import React, {useState} from "react";
import './select.css'
import Modalbuy from "../popup/Modalbuy"
import Modalsell from "../popup/Modalsell"
import { useEffect } from "react";
const Select = () => {
const [value, setValue] = useState("");
const [usdrub, setUsdrub] = useState(Math.random() * (64-61) + 61);
useEffect(() => {
const interval = setInterval(() => {
setUsdrub(Math.random() * (64-61) + 61);
}, 10000);
return () => clearInterval(interval);
}, [])
const [rubusd, setRubusd] = useState(Math.random() * 2);
useEffect(() => {
const interval = setInterval(() => {
setRubusd(Math.random() * 2);
}, 10000);
return () => clearInterval(interval);
}, [])
function changeSelect(event) {
setValue(event.target.value)
}
const [modalBuyActive, setModalBuyActive] = useState(false)
const [modalSellActive, setModalSellActive] = useState(false)
function addData(side, price, volume, timestamp) {
this.setState({side:side, price:price, volume:volume, timestamp:timestamp})
}
return (
<div>
<select value = {value} onChange={changeSelect}>
<option>Choose instrument</option>
<option name="USD/RUB" value={usdrub}>USD/RUB</option>
<option name="RUB/USD" value={rubusd}>RUB/USD</option>
</select>
<div className="Curr">
<div className="Buy" name="buy"> <button className="Buy" type="btn" onClick={() => setModalBuyActive(true)}>BUY {value + 1} </button>
</div>
<div className="Sell" name="sell"><button className="Sell" type="btn" onClick={() => setModalSellActive(true)}>SELL {value}</button></div>
</div>
<Modalbuy active={modalBuyActive} setActive={setModalBuyActive} price={value + 1} addData={addData}/>
<Modalsell active={modalSellActive} setActive={setModalSellActive} price={value}/>
</div>
)
}
export default Select
Trading.js
import React from "react";
import Timer from "./Timer";
import Select from "./select_curr/Select";
const Trading = () => {
return (
<div>
<Timer/>
<Select/>
</div>
)
}
export default Trading;
Page.js
import React from 'react'
import { Tabs, TabList, TabPanel, Tab } from 'react-re-super-tabs'
import CustomTab from './customTab'
import Trading from '../trading/Trading.js'
import Table from '../archive/table'
const Page = () => {
return (
<div>
<Tabs activeTab='about'>
<TabList>
<Tab component={CustomTab} label='Trading' id='trading' />
<Tab component={CustomTab} label='Archive' id='archive' />
</TabList>
<TabList>
<TabPanel component={Trading} id='trading' />
<TabPanel component={Table} id='table' />
</TabList>
</Tabs>
</div>
)
}
export default Page;
So I need to make a table in Table.js and import there data I got from module dialogue
To summarize the way I understand your problem, The component Modalbuy need to share data with your component Table.
Here are some way I can advise you.
1. The common Parent manage the state
const Page = () => {
const [yourData, setYourData] = useState([])
return (
[...]
<TabPanel component={props=>(<Trading {...props} value={yourData} onChange={setYourData} />)} id='trading' />
<TabPanel component={props=>(<Table {...props} value={yourData} onChange={setYourData} />)} id='table' />
[...]
)
}
This implies intermediate component are in charge to follow those new props to Modalbuy
2. Using Redux librairy
Redux is basicly a librairy to help you to define a global state for your app. You define actions your components can call to transform this global state. On the other way, components can subscribe to that state to be refreshed when a change happen.
Don't hesitate to have a look to tutorials https://react-redux.js.org/tutorials/quick-start
3. Using React Context
export const YourDataContext = React.createContext({})
const Page = () => {
const [yourData, setYourData] = useState([])
return (
<YourDataContext.Provider value={{yourData, setYourData}}>
[...]
</YourDataContext.Provider>
)
}
const Modalbuy = ({active, setActive,price}) => {
const { yourData, setYourData } = React.useContext(YourDataContext)
[...]
})
You define a context YourDataContext. You share a value to this context, here we create a state and passing the getter/setter. All child of the Provider can access the attribute value with the hook React.useContext(YourDataContext)

Why is my function not working- pop up image gallery

I'm trying to make a standard effect, where clicking on an image in a gallery will enlarge the image, put it at the center of the screen (in front of everything else), and darken the background. I haven't set up the slideshow part yet (so it won't change images), but the aim is to create an index so that I can do that in the future. I'm following a tutorial and trying to adapt it to my backend, but I'm missing a beat. It's not registering which image has been clicked (and I'm getting two errors in the console- 404, and 500). I'm using Nextjs as my frontend, Sanity for my backend.
import React, { useState } from 'react';
import { client, urlFor } from '../lib/client';
import { Header, Footer, Modal } from '../components';
const sets = ({setData, imagesData}) => {
const [clickedImage, setClickedImage] = useState(null);
const [currentIndex, setCurrentIndex] = useState(null);
const handleClick = (imagesData, index) => {
setCurrentIndex(index);
setClickedImage(imagesData.image);
};
const handleRotationRight = () => {
const totalLength = imagesData.imageItems.length;
if(currentIndex + 1 >= totalLength){
setCurrentIndex(0);
const newData = imagesData.imageItems[0];
setClickedImage(newData);
return;
}
const newIndex = currentIndex + 1;
const newData = imagesData.imageItems.filter((image) => {
return imagesData.imageItems.indexOf(image) === newIndex;
});
const newItem = newData[0].image;
setClickedImage(newItem);
setCurrentIndex(newIndex);
};
return (
<div>
<Header />
<main className="slug-gallery">
<div className="title">
<div className="title-line-left"></div>
<h2>{setData.set_name}</h2>
<div className="title-line-right"></div>
</div>
<div className="images-container">
<ul className="overall-images">
{imagesData.imageItems && imagesData.imageItems.map((imagesData, index) => (
<li key={index}>
<img
src={urlFor(imagesData.image).auto('format').url()}
className="the_image"
alt='test a'
onClick={() => handleClick(imagesData, index)}
/>
</li>
))}
</ul>
</div>
{clickedImage && (
<Modal
clickedImage={clickedImage}
handleRotationRight={handleRotationRight}
setClickedImage={setClickedImage}
/>
)}
</main>
<Footer />
</div>
)
}
export default sets
export const getServerSideProps = async (pageContext) => {
const setSlug = pageContext.query.slug;
const setQuery = `*[_type == 'set' && slug.current == $setSlug][0]`;
const imagesQuery = `*[_type == 'set' && slug.current == $setSlug][0]{'imageItems':set_images[]{image{
asset->{_id, url}, alt, name, date, size, materials}}}`;
const setData = await client.fetch(setQuery, {setSlug});
const imagesData = await client.fetch(imagesQuery, {setSlug});
return {
props: {setData, imagesData}
}
}
Heres the Modal component:
import React from 'react'
const Modal = ({clickedImage, handleRotationRight, setClickedImage}) => {
const handleClick = (e) => {
if(e.target.classList.contains("dismiss")){
setClickedImage(null);
}
}
return(
<>
<div className="overlay dismiss" onClick={handleClick}>
<img src={clickedImage} alt='test b'/>
<span className="dismiss" onClick={handleClick}>x</span>
</div>
<div onClick={handleRotationRight} className="overlay-arrows_left">
<img src="/next_portfolio/public/images/cart.png" alt='test c'/>
</div>
</>
)
};
export default Modal;

JSON strings not displaying in JSX

For some reason, I just can't get JSON strings to display in my component. I've tried various solutions, but no luck.
Below is my component where I map over my items:
UnitEventsItems.js
const GetEventsItems = (props) => {
const { parsed = {} } = props;
const getEventsItems = Object.entries(parsed).map((item, i) => ({
item_Id: i,
label: item[0],
info: item[1]
}));
return (
<div style={{
marginBottom: theme.spacing(4)
}}
>
{getEventsItems.map((eventsItems) => (
...
<Typography>
{`${eventsItems.info}`}
</Typography>
</Box>
Below is the component where I import getEventItems for display:
EventsBlock.js
import GetEventsItems from './UnitEventsItems';
...
const EventsBlock = (props) => {
const classes = useStyles(props);
const {
eventsData,
type
} = props;
return (
<>
{
...
<Paper className={clsx(classes.paper, classes.scrollBar)}>
<List component="nav" aria-label={`${type} Events`}>
{eventsData.map((item, i) => (
<ListItem key={`${i + 1}${item.type}_${item.trixid}`}>
<GetEventsItems
parsed={item.comment}
/>
</ListItem>
))}
</List>
</Paper>
And then this is the parent where I import EventBlock.js and pass the props:
index.js
import React from 'react';
import EventsBlock from './EventsBlock';
const UnitEvents = (props) => {
const { eventsData } = props;
const refueling = (eventsData?.Refueling ?? []).map((event) => (event?.comment ? JSON.parse(event.comment) : {}));
const periodic = (eventsData?.Periodic ?? []).map((event) => (event?.comment ? JSON.parse(event.comment) : {}));
const corrective = (eventsData?.Corrective ?? []).map((event) => (event?.comment ? JSON.parse(event.comment) : {}));
console.log('periodic:', periodic);
console.log('refueling:', refueling);
console.log('corrective:', corrective);
return (
<>
<EventsBlock type="Periodic" eventsData={periodic} />
<EventsBlock type="Refueling" eventsData={refueling} />
<EventsBlock type="Corrective" eventsData={corrective} />
</>
);
};
export default UnitEvents;
Below is an image of what data looks like coming through from the API:
Below is what I have logged in the console as shown in the code above:
This is live example of code Sandbox
What is wrong on your code
In the EventsBlock component you need to have a check Object.keys(eventsData).length after that Draw eventsData.map
Inside of eventsData.map you need also some check before draw GetEventsItems , something like
{ item.comment && (
<li>
<GetEventsItems parsed={item.comment } />
</li>
)
Check this example , I have wrote your logic with other json data , try to use that aproach with your json data. code
Problem solved by making the following the change.
Changed this:
<GetEventsItems parsed={item.comment} />
to this:
<GetEventsItems parsed={item}/>

Unable to drag React bootstrap tabs - React Beautiful dnd

I would like to drag drop tabs from react-bootstrap and I am using react-beautiful-dnd to achieve it. But for some reason, I am not able to get it. No tabs are appearing.
Here is my code:
import React from "react";
import { Tabs, Tab } from "react-bootstrap";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
export default function App() {
const getItems = count =>
Array.from({ length: count }, (v, k) => k).map(k => ({
id: `item-${k}`,
content: `item ${k}`
}));
const [selectedTab, setSelectedTab] = React.useState("item-0");
const [items, setItems] = React.useState(getItems(6));
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const onDragEnd = result => {
// dropped outside the list
if (!result.destination) {
return;
}
const items = reorder(
this.state.items,
result.source.index,
result.destination.index
);
setItems(items);
};
return (
<div className="App">
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="tab-drop" direction="horizontal">
{droppable => (
<React.Fragment>
<Tabs
ref={droppable.innerRef}
activeKey={selectedTab}
onSelect={chartId => {
if (chartId === this.state.selectedTab) {
return;
}
setSelectedTab(chartId);
}}
{...droppable.droppableProps}
>
{items.map((item, index) => (
<Draggable
key={`tab-${item.id}-order-${index}`}
draggableId={`${item.id}-order-${index}`}
index={index}
>
{(draggable, snapshot) => (
<Tab
ref={draggable.innerRef}
eventKey={item.id}
title={<div>{item.content}</div>}
{...draggable.draggableProps}
{...draggable.dragHandleProps}
style={getItemStyle(
draggable.draggableProps.style,
snapshot.isDragging
)}
/>
)}
</Draggable>
))}
<Tab title={<button>+</button>} />
<Tab
tabClassName="ml-auto active tabBottomBorder"
title={<button>Format</button>}
/>
<Tab
tabClassName="active tabBottomBorder"
title={<button>Edit</button>}
/>
</Tabs>
{droppable.placeholder}
</React.Fragment>
)}
</Droppable>
</DragDropContext>
</div>
);
}
This is the example which works without DND:
import React from "react";
import "./styles.css";
import { Tabs, Tab } from "react-bootstrap";
export default function App() {
const getItems = (count) =>
Array.from({ length: count }, (v, k) => k).map((k) => ({
id: `item-${k}`,
content: `item ${k}`
}));
const items = getItems(6);
const [selectedTab, setSelectedTab] = React.useState("item-0");
return (
<div className="App">
<Tabs
activeKey={selectedTab}
onSelect={(chartId) => {
if (chartId === selectedTab) {
return;
}
setSelectedTab(chartId);
}}
>
{items.map((item, index) => (
<Tab eventKey={item.id} title={<div>{item.content}</div>} />
))}
<Tab title={<button>+</button>} />
<Tab
tabClassName="ml-auto active tabBottomBorder"
title={<button>Format</button>}
/>
<Tab
tabClassName="active tabBottomBorder"
title={<button>Edit</button>}
/>
</Tabs>
</div>
);
}
Link with DND: https://stackblitz.com/edit/react-u1cts7?file=src%2FApp.js
Link without DND: https://codesandbox.io/s/pedantic-minsky-7zelb?file=/src/App.js:0-1045
I want to achieve a similar functionality to : https://codesandbox.io/s/mmrp44okvj?file=/index.js:0-2939
Please note only the tabs before the "+" button should be draggable. The + button tab and last two tabs shouldnt be.
Please advice on a way to fix this. Any help is appreciated.

React.memo isn't working - what am I missing?

I'm in the process of refactoring some of our components so I'm trying to incorporate memoization as some components may re-render with the same values (for example, hotlinked image URLs unless they are the same).
I have a simple component:
const CardHeader = props => {
// img is a stringand showAvatar is a boolean but it's always true
const { ..., showAvatar, img } = props;
return (
<CardHeader>
<ListItem>
// AvatarImage shouldn't re-render if img is the same as previous
{showAvatar && <AvatarImage img={img} />
</ListItem>
</CardHeader>
);
}
And then the AvatarImage:
const AvatarImage = React.memo(props => {
console.log("why is this still re-rendering when the img value hasn't changed?");
const { img } = props;
return (
<ListItemAvatar>
{img ?
<Avatar src={img} />
:
<Avatar>
Some initials
</Avatar>
}
</ListItemAvatar>
);
});
I have also tried passing in second argument of memo:
(prevProps, nextProps) => {
return true; // Don't re-render!
}
But the console.log still shows every time. I'm obviously missing something here or don't quite understand how this works. This component is a few levels down, but it passes in the img if it's available every time so I'd expect it to know that if the img was passed in the previous render and it's the same it knows not to re-render it again but for some reason it does?
Thanks all. It's much appreciated.
Well it is either showAvatar is not always true or CardHeader ListItem component magically decides whether show children or not
Example
const { useState, useEffect, memo, createContext, useContext } = React;
const getAvatars = () => Promise.resolve([
{
src: 'https://i.picsum.photos/id/614/50/50.jpg'
},
{
src: 'https://i.picsum.photos/id/613/50/50.jpg'
}
])
const Avatar = ({src}) => {
console.log('avatar render');
return <img src={src} alt="avatar"/>
}
const MemoAvatarToggle = memo(({src}) => {
console.log('memo avatar with \'expression &&\' render');
return <div>
{src ? <img src={src} alt="avatar"/> : <div>Test </div>}
</div>
})
const CardHeader = ({children}) => {
const luck = Boolean(Math.floor(Math.random() * 1.7));
return <div>
{luck && children}
</div>
}
const ListItem = ({children}) => {
return <div>
{children}
</div>
}
const ShowAvatarContext = createContext()
const App = (props) => {
const [avatars, setAvatars] = useState([]);
const [toggle, setToggle] = useState(false);
const [showAvatar, setShowAvatar] = useContext(ShowAvatarContext);
useEffect(() => {
let isUnmounted = false;
let handle = null;
setTimeout(() => {
if(isUnmounted) {
return;
}
setShowAvatar(true);
}, 500);
getAvatars()
.then(avatars => {
if(isUnmounted) {
return;
}
setAvatars(avatars)
})
const toggle = () => {
setToggle(prev => !prev);
handle = setTimeout(toggle, 1000);
//setShowAvatar(prev => !prev);
}
handle = setTimeout(toggle, 1000);
return () => {
isUnmounted = true;
clearTimeout(handle);
}
}, []);
return <div>
<CardHeader>
<ListItem>
{showAvatar && avatars.map((avatar, index) => <MemoAvatarToggle key={index} src={avatar.src}/>)}
</ListItem>
</CardHeader>
{toggle ? 1 : 0}
</div>
}
const ShowAvatarProvider = ({children}) => {
const state = useState(false);
return <ShowAvatarContext.Provider value={state}>
{children}
</ShowAvatarContext.Provider>
}
ReactDOM.render(
<ShowAvatarProvider>
<App/>
</ShowAvatarProvider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
Do you have StrictMode enabled? That will cause a component memoized with React.memo to render twice.
More information:
https://reactjs.org/docs/strict-mode.html
My React Component is rendering twice because of Strict Mode
memo will not block re-render if the component is actually referenced the changing props or functions.
In your scenario your AvatarImage referenced img, in this case if parent's state's img is changed, then your component will be re-rendered.
Alternatively, if your parent is just changed other props instead of img, then the AvatarImage will NOT be re-rendered.
Alternatively, if any props but you didn't add memo to AvatarImage, then AvatarImage will be re-rendered for each of parent's state updated.
You need to memorized img props too.
const CardHeader = props => {
const { showAvatar, img } = props;
const updatedIMG = React.useMemo(() => img, []);
return (
<CardHeader>
<ListItem>
{showAvatar && <AvatarImage img={updatedIMG} />
</ListItem>
</CardHeader>
);
}
Above one would work

Resources