how to test whether a Link from Router worked? Jest/RTL - reactjs

with RTL how do i check the to='/login' in Link to be true since RTL library mainly grabs by testid or text.
current page testing
import React from 'react';
import Proptypes from 'prop-types';
import { Link } from 'react-router-dom';
function Navigation(props) {
return (
<nav className="header__nav">
<Link to="/login">
<button type="button" className="header__login">LOGIN</button>
</Link>
</nav>
);
}
test
describe('Navigation Content', () => {
test('clicking login button', () => {
const props = jest.fn();
const { getByTestId, getByText } = render (
<Navigation popUpHandler={props}/>, { wrapper: MemoryRouter }
);
expect((<Link>).toHaveAttribute('to', '/login')
})
})
page i am trying to render to when clicked
function LoginForm() {
return (
<div className="login">
<h1 className="entryheader__header">Login</h1>
</div>
)
export default LoginForm;

Could you try rendering your component like this:
import { MemoryRouter } from 'react-router-dom'
...
...
const { getByTestId, getByText } = render(<YourComponent {...props}/>, { wrapper: MemoryRouter });
fireEvent.click(getByText('LOGIN'));
expect(getByText('some-text-on-login-page')).toBeInTheDocument();
This will wrap your component with the memory router rather than you doing it manually.
You can also try using BrowserRouter instead of MemoryRouter in your test file..

I wish I had an answer to this question but I do feel like I can say the following:
I wouldn't do
<Link to="/login">
<button type="button" className="header__login">LOGIN</button>
</Link>
because this will render non-valid HTML markup. It breaks accessibility rules to start

Related

React & Typescript Issue: trigger elements with InsertionObserver using props and manage them in other component

Small premise: I'm not a great Typescript expert
Hi everyone, I'm working on my personal site, I decided to develop it in Typescript to learn the language.
My component tree is composed, as usual, of App.tsx which render the sub-components, in this case Navbar.jsx and Home.jsx.
Below is the App.jsx code:
import './App.css';
import { BrowserRouter as Router, useRoutes } from 'react-router-dom';
import Home from './components/Home';
import Navbar from './components/Navbar';
import { useState } from 'react';
function App(){
const [navbarScroll,setNavbarScrool]=useState(Object)
const handleLocationChange = (navbarScroll : boolean) => {
setNavbarScrool(navbarScroll)
return navbarScroll
}
const AppRoutes = () => {
let routes = useRoutes([
{ path: "/", element: <Home handleLocationChange={handleLocationChange}/> },
{ path: "component2", element: <></> },
]);
return routes;
};
return (
<Router>
<Navbar navbarScroll={navbarScroll}/>
<AppRoutes/>
</Router>
);
}
export default App;
Here, instead, the Home.jsx code:
import { useInView } from 'react-intersection-observer';
import HomeCSS from "../styles/home.module.css"
import mePhoto from "../assets/me.png"
import { useEffect, useState } from 'react';
interface AppProps {
handleLocationChange: (values: any) => boolean;
}
export default function Home(props: AppProps){
const { ref: containerChange , inView: containerChangeIsVisible, entry} = useInView();
useEffect(()=>{
props.handleLocationChange(containerChangeIsVisible)
//returns false at first render as expected
console.log("Home "+containerChangeIsVisible)
},[])
return(
<>
<div className={`${ HomeCSS.container} ${containerChangeIsVisible? HomeCSS.container_variation: ''}`}>
<div className={HomeCSS.container__children}>
{/* when i scroll on the div the css change (this works)*/}
<h1 className={`${ HomeCSS.container__h1} ${containerChangeIsVisible? HomeCSS.container__h1_variation: ''}`}>My<br/> Name</h1>
<p>Computer Science student.</p>
</div>
<img src={mePhoto} className={HomeCSS.image_style}/>
</div>
<div ref={containerChange} style={{height:800,background:"orange"}}>
<p style={{marginTop:20}}>HIII</p>
</div>
</>
)
}
And Navbar.jsx:
import NavbarCSS from "../styles/navbar.module.css"
import acPhoto from "../assets/ac.png"
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
interface NavbarScroolProp{
navbarScroll:boolean
}
export default function Navbar(props:NavbarScroolProp){
const [scrollState,setScrollState]=useState(false)
const [pVisible,setpVisible] = useState('')
useEffect(()=>{
setTimeout(() => {
setpVisible("")
}, 3000)
setpVisible("100%")
},[])
//returns false also when should be true
console.log(props.navbarScroll)
return (
<>
{/*the props is undefined so the css doesn't change, i need to do this*/}
<nav className={`${props.navbarScroll?NavbarCSS.nav__variation:NavbarCSS.nav}`}>
<div className={NavbarCSS.nav_row}>
<div className={NavbarCSS.nav_row_container}>
<img src={acPhoto} className={NavbarCSS.image_style}/>
<p className={NavbarCSS.p_style} style={{maxWidth: pVisible}}>My name</p>
</div>
<div className={NavbarCSS.nav_row_tagcontainer}>
<Link className={NavbarCSS.nav_row_tag} to="/"> Home</Link>
<Link className={NavbarCSS.nav_row_tag} to="/"> About</Link>
<Link className={NavbarCSS.nav_row_tag} to="/"> Contact</Link>
</div>
</div>
</nav>
</>
);
}
In my application I want to change the background color whenever the div referring to the InsertionObserver ( I use "useInView" hook , from :https://github.com/thebuilder/react-intersection-observer) is displayed. The problem is that the div in question is in the Home.jsx component and I need to change the color of the divs in the navbar as well when the div in Home is triggered(or other components in case I need to in the future).
The question is: How can I dynamically trigger DOM elements of other components (to then perform certain operations) using the InsertionObserver ?
As you can see from the code I tried to create Props, but everything returns undefined and doesn't involve any changes.
I've tried without useEffect, without using the useInView hook, passing the object instead of the boolean value, but I can't find any solutions to this problem.
You would be of great help to me.
PS: I would like to leave the Navbar.jsx component where it is now, so that it is visible in all components.
Any advice or constructive criticism is welcome.

How to programatically create a Modal in React 18

I did a function that creates a modal programatically for React 17, where you just needed to call a function to create a new modal.
It was working fantastic before the ReactDOM.render was deprecated.
Is there a way to replace the render function with something else in React 18? Right now the createRoot function is only for the root component, I want to render simple components in a specified DOM element.
It worked like this:
app.jsx
<button onClick={() => createModal(<h1>I'm a component inside a modal</h1>)}>Open Modal</button>
It handles it's own state, very useful if you want to make a bunch of modals in seconds.
This is the code:
index.js => Here is the container.
import React from 'react'
import ReactDOM from 'react-dom'
import './index.scss'
import App from './App.jsx'
ReactDOM.render(
<React.StrictMode>
<div id="modal-container"></div> <- This is the container
<App />
</React.StrictMode>,
document.getElementById('root')
)
Modal/Modal.jsx => The modal component.
import { useState } from 'react'
import './Modal.scss'
const Modal = ({ content }) => {
const [isOpen, setIsOpen] = useState(true)
if (isOpen) {
return (<>
<div className="modal-background" onClick={() => setIsOpen(false)} />
<div className="modal-content" >
{content}
</div>
<button onClick={() => setIsOpen(false)} className="close-button" >Close</button>
</>)
} else return null
}
export default Modal
Modal/index.js => The call function:
import { render } from "react-dom"
import Modal from "./Modal"
const createModal = (content) => render(
<Modal key={Math.random()} content={content} />, document.getElementById("modal-container")
)
export default createModal
It worked using createRoot this way, instead of render:
Here is an example: CodeSandbox
Modal/index.js
import { createRoot } from 'react-dom/client'
import Modal from "./Modal"
const createModal = (content) => {
if (!window.modalContainer) {
window.modalContainer = createRoot(document.getElementById('modal-container'))
}
window.modalContainer.render(<Modal key={Math.random()} content={content} />)
}
export default createModal
It checks if createRoot on the specified component has been called before, so it only call createRoot once, and the render function any time a new modal is created.
If you have a better answer it would be awesome too. :)

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

Dynamic query params for React Native Navigation?

I have a phonebook app in which I'm implementing a search bar.
I am trying to use the useNavigate hook to take a string {term} and navigate to the search page along with the term the user typed in.
So for example if the user typed in David the address bar would be
http://localhost:3002/search?q=David
At the moment when I type David into search bar I get this result in address bar
http://localhost:3002/search
The parameter was not passed along to the search page.
I followed the instruction here which said I should do following
navigate('search', {term}) but this did not work for me
https://reactnavigation.org/docs/params/
Any help appreciated
Searchbar.js
import "./Searchbar.css"
import { useState } from "react"
import { useNavigate } from "react-router-dom"
export default function Searchbar() {
const [term, setTerm] = useState('')
const navigate = useNavigate()
const handleSubmit = function(e) {
e.preventDefault()
navigate('search', {term})
console.log(term)
}
return (
<div className="searchbar">
<form onSubmit={handleSubmit}>
<label htmlFor="search">Search Contacts</label>
<input
type="text"
onChange={(e) => setTerm(e.target.value)}
value={term}>
</input>
</form>
</div>
)
}
Navbar.js
import { Link } from "react-router-dom";
import "./Navbar.css"
import Searchbar from "./Searchbar";
export default function Navbar() {
return (
<div className="navbar">
<nav>
<Link to="/" className="brand"><h1>Phonebook</h1></Link>
<Searchbar></Searchbar>
<Link to="/create">Create Contact</Link>
</nav>
</div>
)
}
Some thing like this should work.
navigate({
pathname: "search",
search: {
q: term,
},
});
For more you can have a look here

Do not understand why duplicate data is being copied into my url in react component

In my App.js (or main component) I am rendering my Navbar component
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import Navbar from './components/layout/navbar/Navbar';
import './App.css';
const App = () => {
return (
<Router>
<Navbar />
</Router>
);
};
export default App;
In my Navbar I am rendering my NavLinks component and passing in as props the menu
import React from 'react';
import NavLinks from './NavLinks';
const menu = [
{ id: 1, label: 'Home', url: 'https://www.google.com/?client=safari' },
{ id: 2, label: 'Contact us', url: 'https://stackoverflow.com' },
];
const Navbar = () => {
return (
<nav>
<NavLinks items={menu} />
</nav>
);
};
export default Navbar;
In my NavLinks I bring in as props the items which is the menu we saw before and map through it and pass in as props url and label.
import React from 'react';
import NavLink from './NavLink';
const NavLinks = ({ items }) => {
const links = items.map((item) => (
<NavLink key={item.id} url={item.url} label={item.label} />
));
return <ul>{links}</ul>;
};
export default NavLinks;
In my NavLink component I am creating a Link to the url
import React from 'react';
import { Link } from 'react-router-dom';
const NavLink = ({ url, label }) => {
return (
<li className='nav-item'>
<Link to={url}>{label}</Link>
</li>
);
};
export default NavLink;
For some reason my Link has a path of multiple google urls. The url to the google homepage is duplicated many times. I do not know why this is happening.
Link component is to Provides declarative, accessible navigation around your application
If you use the Link component for the external URL, this will keep appending your URL to the previous ones.
For navigating to an external URL, I would suggest you to use native HTML tag instead:
const NavLink = ({ url, label }) => {
return (
<li className="nav-item">
<a href={url}>{label}</a>
</li>
);
};
Working example:

Resources