How do I render props for React Bootstrap Icons - javascript

I am running into a slight problem when trying to render a React Bootstrap Icon in Next.js.
I am using getStaticProps to make a call to the Notion API, to then use as props in my page, and everything is working fine.
However, I would like to define which icon to use in the Notion CMS.
According to the React Bootstrap Icons package on NPM you can do it this way, however I am not using TypeScript so I edited the code slightly (further below):
Icons.js
import * as icons from 'react-bootstrap-icons';
export const Icon = ({ iconName, ...props }) => {
const BootstrapIcon = icons[iconName];
return <BootstrapIcon {...props} />;
}
Services.js
import React from 'react';
import Card from 'react-bootstrap/Card';
import CardGroup from 'react-bootstrap/CardGroup';
import { Icon } from '../icons';
function Services({data}) {
const renderItems = data?.map((record) => {
// saving notion data as variables
let icon = record.properties.Icon.title[0].text.content
let title = record.properties.Service.rich_text[0].plain_text
let description = record.properties.Descrip.rich_text[0].text.content
return <Card key={record.id}>
<Icon
iconName={icon}
color="#96DBAE"
size={96}
/>
<Card.Body>
<Card.Title>{title}</Card.Title>
<Card.Text>{description}</Card.Text>
</Card.Body>
</Card>
})
return (
<>
<section >
<CardGroup>
{renderItems}
</CardGroup>
</section>
</>
);
}
export default Services;
I am running into this error. When I console.log(icon) it displays as a string, however when I pass the variable as a prop on the Icon, the error shows. If I type a regular string e.g iconName={"Globe"} everything works fine.
Any ideas how to solve this or where I might be going wrong? Any help is massively appreciated!

Related

Finding the buttons on the screen that have no text for the test

I am trying to write the tests for the NavBar component (using react-native-testing-library) that has several buttons that are basically just icons (using ui-kitten for react native). So I can't get these buttons by text (as there is none) but other methods didn't work for me either (like adding accesibilityLabel or testID and then getting by the label text / getting by test ID). Any ideas what I am doing wrong?
// NavBar.tsx
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {HomeBtn, SaveBtn} from '../components/buttons';
import UserSignOut from './UserSignOut';
const NavBar = ({
navigation,
pressHandlers,
}) => {
return (
<View style={styles.navBar}>
<View>
<HomeBtn navigation={navigation} />
<SaveBtn pressHandler={pressHandlers?.saveBtn ?? undefined} />
</View>
<UserSignOut />
</View>
);
};
export default NavBar;
// HomeBtn.tsx
import React from 'react';
import {Button} from '#ui-kitten/components';
import {HomeIcon} from '../shared/icons';
import styles from './Btn.style';
export const HomeBtn = ({navigation}: any) => {
return (
<Button
accesibilityLabel="home button"
style={styles.button}
accessoryLeft={props => HomeIcon(props, styles.icon)}
onPress={() => navigation.navigate('Home')}
/>
);
};
// NavBar.test.tsx
import React from 'react';
import {render, screen} from '#testing-library/react-native';
import * as eva from '#eva-design/eva';
import {RootSiblingParent} from 'react-native-root-siblings';
import {EvaIconsPack} from '#ui-kitten/eva-icons';
import {ApplicationProvider, IconRegistry} from '#ui-kitten/components';
import NavBar from '../../containers/NavBar';
describe('NavBar', () => {
const navBarContainer = (
<RootSiblingParent>
<IconRegistry icons={EvaIconsPack} />
<ApplicationProvider {...eva} theme={eva.light}>
<NavBar />
</ApplicationProvider>
</RootSiblingParent>
);
it('should render the buttons', async () => {
render(navBarContainer);
// this test fails (nothing is found with this accesibility label)
await screen.findByLabelText('home button');
});
});
Query predicate
The recommended solution would be to use:
getByRole('button', { name: "home button" })
As it will require both the button role, as well as check accessibilityLabel with name option.
Alternative, but slightly less expressive way would be to use:
getByLabelText('home button')
This query will only check accessibilityLabel prop, which also should work fine.
Why is query not matching
Since you're asking why the query is not working, that depends on your test setup. It seems that you should be able to use sync getBy* query and do not need to await findBy* query, as the HomeBtn should be rendered without waiting for any async action.
What might prevent that test from working could be incorrect mocking of any of the wrapping components: RootSiblingParent, ApplicationProvider, they might be "consuming" children prop without rendering it. In order to diagnose the issue you can use debug() function from RNTL to inspect the current state of rendered components. You can also run your tests on render(<NavBar />) to verify that.
Does await screen.findByA11yLabel('home button') work? It should match the accessibilityLabel prop.

react/no-multi-comp is showing up as warnings

I am using hook router 1.2.5 and I have a very simple home page as below:
import { useRoutes } from "hookrouter";
import React from "react";
import Nav from "./pages/Nav";
import AboutPage from "./pages/About";
const HomePage = () => {
const routeResult = useRoutes({
"/about": () => <AboutPage />
});
return (
<div fluid>
<div xs={3} md={1} lg={1} className="nav-container">
<Nav />
</div>
<div xs={9} md={11} lg={11}>
{routeResult || <AboutPage />}
</div>
</div>
);
};
export default HomePage;
But when I run lint, I see below warnings show up.
8:10 warning Component definition is missing display name react/display-name
8:10 warning Declare only one React component per file react/no-multi-comp
I know I can disable these eslint warnings. But I would like to know how to fix them. For example, I don't have another component in my file. So why would it show react/no-multi-comp warning, or did I miss something? Any helps are appreciated.
UPDATE
I was able to fix react/display-name by replacing the arrow function as below:
const routeResult = useRoutes({
"/about"() {
return <AboutPage />;
}
});

Changing Background image using Create React App

Having trouble using Create React App to change a background image I feed to my component through props. The docs say use the import syntax. This works but it would mean I have to hard code every background image to each component. Anyway to do this dynamically?
I noticed it won't let me use template literals on the import syntax as well. That would have fixed my issue I think.
import '../css/Avatar.css';
import photo from "../images/joe_exotic.jpg";
const Avatar = (props) => {
return (
<div
style={{backgroundImage: `url("${photo}")`}}
className="avatar">
</div>
)
}
export default Avatar;
P.S: I checked out the other articles on StackOverflow regarding this and they didn't provide much help.
If you wanna avoid this way of doing, you can put your images in the public folder of your React app, et grab them like so :
import '../css/Avatar.css';
const Avatar = (props) => {
return (
<div
style={{backgroundImage: `url("/joe_exotic.jpg")`}}
className="avatar">
</div>
)
}
export default Avatar;
I hope it works for you. good luck.
import '../css/Avatar.css';
import photo from "../images/joe_exotic.jpg";
const Avatar = (props) => {
return (
<div
style={{backgroundImage:url(photo)}}
className="avatar">
</div>
) }
export default Avatar;

How can I call React useRef conditionally in a Portal wrapper and have better code?

I am attempting to make a simple Portal wrapper that does the following.
Can render multiple portals on the same view
Can render multiple portals by targeting parent ids. This cannot be done by passing a ref into the component since the container may live anywhere.
Can render multiple portal without any parent targets
This is what I came up with this morning. The code works exactly how I want it to work. It creates portals with or without a parent target and cleans up completely. However React says not to call hooks from conditions, which you can see I have done here by calling useRef within a ternary. I have not seen any warnings or errors but I am also developing this code in a custom setup with Webpack and Typescript. I have not pushed this code to NPM to see what happens when I import the library into a project. My guess is there's going to be issues. Is there a better way to do this?
Thanks in advance.
Calling Component:
<div>
{open1 && (
<Portal parentId="panel-1">
<Panel title="Poopster" onClose={handleClose1}>
<div>Panel</div>
</Panel>
</Portal>
)}
{open2 && (
<Portal>
<Panel onClose={handleClose2}>
<div>Panel</div>
</Panel>
</Portal>
)}
<div>
Portal Component
import * as React from 'react';
import { createPortal } from 'react-dom';
export interface PortalProps {
children: React.ReactElement;
parentId?: string;
}
export const Portal = ({
children,
parentId
}: PortalProps): React.ReactElement => {
let ref = !parentId ?
React.useRef(document.createElement('div')) :
React.useRef(document.getElementById(parentId));
React.useEffect((): VoidFunction => {
return (): void => {
if (!parentId && ref.current) {
document.body.removeChild(ref.current);
}
ref = null;
};
}, []);
React.useEffect(() => {
if (!parentId && ref.current) {
document.body.appendChild(ref.current);
}
}, [ref, parentId]);
return createPortal(children, ref.current);
};
export default Portal;
You could make it like so, for short and more intuitive
let ref = React.useRef(
parentId ? document.getElementById(parentId) : document.createElement("div")
);
This will solve the hook rules error

React Testing Library, Component Unit Tests

I am trying to build a test unit for my simple React Application using React Testing Library. I readed all docs and get stuck in it.
API was created by create React app. One of the feature is that user can change theme. There is setTheme hook that going to change theme "dark" and "light".
App.js
const App = () => {
const [theme, setTheme] = useState('dark');
return ( <div>
<Header theme={theme} setTheme={setTheme} />
</div>)
};
Header.js
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
const Header = props => {
return (
<header data-testid="header">
<h1><span className="highlight">Github Users</span></h1>
{props.theme === "dark" ?
<FontAwesomeIcon data-testid="button" icon="sun" size="2x" color="#dcba31" onClick={ () => props.setTheme('light') }/>
: <FontAwesomeIcon icon="moon" size="2x" color="#1c132d" onClick={ () => props.setTheme('dark') }/>}
</header>
);
}
export default Header;
In Header component I added arrow function that changes color of theme.
Now I am trying to write a test that's gonna test Header Component.
Expected result is that after first render Header component shall render icon "sun".
After user click on it header shall return icon "moon".
There is something that i try but it's not working as I mention.
Header.test.js
import React from 'react';
import { render, cleanup } from "#testing-library/react"
import '#testing-library/jest-dom/extend-expect';
import { act } from "react-dom/test-utils";
import Header from '../components/Header';
afterEach(cleanup);
describe("Header Component", () => {
it("first render should return a sun icon", () => {
const {getByTestId } = render(<Header />)
expect(getByTestId("header"). // What method uses here? To check if it is icon sun or moon ? )
})
it("after mouse click event should return a moon icon", () => {
const button = document.querySelector("svg"); // it is correct way to add a svg element as a button ?
act( () => {
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
})
expect(getByTestId("header"). // again what to write here to test it after click ?
})
})
I am sure that there is some other way to check first render and then after click what's Header component rendering. I think that problem is that there is another Component that is rendered conditionaly. If it is text there is no problem, but after render there is svg element with some attributes like icon="sun" / icon="moon".
Live version of project
Github Repo Link
Questions:
How to properly test that Header component ?
How to pass props in test for example I want to use that setTheme hook in test how to do it ?
There's many ways to do this and I can recommend the articles here https://kentcdodds.com/blog/?q=test to get you started. As for your current set-up I'd change some stuff that I find helpful writing unit tests:
Use data-testid to find elements, e.g. "first render should return a sun icon" can be confirmed by expect(getByTestId('svg-sun')).toBeDefined(), which is a pattern I like
Structure your it's like stubbing-rendering-assertions and only test one thing in each test, for instance, in your second it you're lacking a rendering part of the test
Regarding your question regarding passing the props, you can pass it as render(<Header theme={theme} setTheme={setThemeStub}/>) where const setThemeStub = jest.fn(). This allows you to make assertions as expect(setThemeStub).toBeCalledWith(...)

Categories

Resources