Enzyme `.find(selector)` does not seem to find a selector - javascript

element.find('span.active-nav-option') returns nothing whilst element.find('.active-nav-option') returns the span. The point of the test is to find out if a span was rendered instead of a Link.
Component is as follows:
const PageNav = ({
router,
slides,
}) => (
<nav className="PageNav">
<span className="chevron">
<MoreVerticalIcon
strokeWidth="0.5px"
size="3em"
/>
</span>
<ul className="nav-links">
{mapSlides(slides, router)}
</ul>
<style jsx>{styles}</style>
</nav>
)
function mapSlides(slides, router) {
return Object.entries(slides)
.sort((
[, { order: a }],
[, { order: b }],
) => a - b)
.map(([slidename, { altText, order }]) => {
const isActiveLink = router.query.slidename === slidename
const navItemClassnames = [
'nav-item',
isActiveLink && 'active',
]
.filter(Boolean)
.join(' ')
const Element = isActiveLink
? props => <span {...props} />
: Link
const liInternalElementProps = {
...(isActiveLink && { className: 'active-nav-option' }),
...(!isActiveLink && {
href: `/CVSlide?slidename=${slidename}`,
as: `/cv/${slidename}`,
}),
}
return (
<li
className={navItemClassnames}
key={order}
>
<Element {...liInternalElementProps}>
<a title={altText}>
<img
src={`/static/img/nav-icons/${slidename}.svg`}
alt={`An icon for the ${slidename} page, ${altText}`}
/>
</a>
</Element>
<style jsx>{styles}</style>
</li>
)
})
}
To Reproduce
run this line as a test:
const wrapperOne = shallow(
<PageNav
slides={mockSlides}
router={{
query: {
slidename: 'hmmm',
},
}}
/>
)
const spanExists = wrapperOne
.find('.active-nav-option')
.html() // outputs <span class="active-nav-option">...</span>
// so one would expect span.active-nav-option to work?
const spanDoesNotExist = wrapperOne
.find('span.active-nav-option')
.html() // throws an error `Method “html” is only meant to be run on a single node. 0 found instead.`
// subsequently if I use `.exists()` to test if the element exists, it returns nothing.
Expected behavior
element.find('span.active-nav-option') should return the span. I think? I initially thought this was to do with shallow vs mount but the same happens with mount. Am I being an idiot here? Is this something to do with the map function in the component?
OS: OSX
Jest 23.5
enzyme 3.5.0

Looks like that's because you don't use <span/> directly in JSX returned from render method but assign it to a variable and then add that variable to JSX.
If you execute console.log(wrapperOne.debug()) you will see the following result (I've removed styles and components that you didn't provided):
<nav className="PageNav">
<span className="chevron" />
<ul className="nav-links">
<li className="nav-item active">
<Component className="active-nav-option">
<a title={[undefined]}>
<img src="/static/img/nav-icons/0.svg" alt="An icon for the 0 page, undefined" />
</a>
</Component>
</li>
</ul>
</nav>
As you can see, you have <Component className="active-nav-option"> instead of <span className="active-nav-option"> thus span.active-nav-option can't find anything.

Related

Easy way to display internal and external links in gatsby

I am making my website portfolio and I have created a Navbar component with links to home page, project page and blog page. The Home page and blog page are internal links while the projects link is an external link.
Now the way I have this setup is like this:
import * as React from 'react';
import { Link, useStaticQuery, graphql } from 'gatsby';
import Icon from '../images/icon.svg';
const Navbar = ({pageTitle}) => {
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
title
}
}
}
`);
const menu = [
{ title: 'HOME', path:'/' },
{ title: 'PRØJECTS', path: 'https://github.com/chukkyiii?tab=repositories' },
{ title: 'BLØG', path: '/blog' }
]
return (
<main>
<title>
{pageTitle} | {data.site.siteMetadata.title}
</title>
<div>
<div className="mx-auto mb-9 flex items-center justify-between max-w-2xl ">
<Icon className="w-12 h-12 p-1 rounded-full ring-2 ring-gray-300 dark:ring-gray-500" />
<nav>
<ul className="flex">
{menu.map((item) => (
<li key={item.title}>
<Link to={item.path} className="pl-8 hover:text-amber-500">
{item.title}
</Link>
</li>
))}
</ul>
</nav>
</div>
</div>
</main>
);
}
export default Navbar;
So I don't have to repeat the same line of code again, I just used a Link which works but it gives me a warning, is there a different way I can do this without the warning?
the warning it gives:
react_devtools_backend.js:4026 External link https://github.com/chukkyiii?tab=repositories was detected in a Link component. Use the Link component only for internal links. See: https://gatsby.dev/internal-links
...
You can easily customize your menu array to display a new property to base your loop on:
const menu = [
{ title: 'HOME', path:'/', isInternal: true },
{ title: 'PRØJECTS', path: 'https://github.com/chukkyiii?tab=repositories', isInternal: false },
{ title: 'BLØG', path: '/blog', true }
]
Then:
{
menu.map((item) => {
if (item.isInternal) {
return (
<li key={item.title}>
<Link to={item.path} className="pl-8 hover:text-amber-500">
{item.title}
</Link>
</li>
);
}
return (
<li key={item.title}>
<a href={item.path} target="_blank">
{item.title}
</a>
<li key={item.title}>
);
});
}
You can even do a ternary condition inside the return to take advantage of the <li> wrapper:
{
menu.map((item) => (
<li key={item.title}>
{item.isInternal ? (
<Link to={item.path} className="pl-8 hover:text-amber-500">
{item.title}
</Link>
) : (
<a href={item.path} target="_blank">
{item.title}
</a>
)}
</li>
));
}
Link component is intended to use only for internal navigation, somethign what happens in the scope of your application and React/Gatsby can be aware of. If the link points to any external source (like GitHub in this case), you need to use the standard anchor (which in fact is what it renders the Link component).
The same approach of adding the isInternal attribute (which personally I find it more succinct) can be done in the same way using a regular expression or similar, just checking how the path is.

I am trying to print array inside object using map function but I get :Cannot read property map of undefined

I'm trying to print the properties of Selectedproduct object inside Modal section and every thing works well until it reaches to "description" array property , it shows me "Cannot read property 'map' of undefined". eventhough when I use console.log(Selectedproduct) the description property appears normally,but when I code console.log(Selectedproduct.description) I dont know why it consider it as undefined .can you please tell me why it can't see the description as stand alone property ?
import React, { Component } from "react";
import FormatCurrency from "../Components/util";
import Slide from "react-reveal/Slide";
import Modal from "react-modal";
import Zoom from "react-reveal/Zoom";
import { connect } from "react-redux";
import { GetProducts } from "../Actions/ItemsActions";
import { AddToCart } from "../Actions/CartActions";
class Products extends Component {
constructor(props) {
super();
this.state = {
show: false,
Selectedproduct: {},
};
}
showModal = (product) => {
console.log(product);
this.setState({ show: true, Selectedproduct: product });
};
hideModal = () => {
this.setState({ show: false });
};
componentDidMount() {
this.props.GetProducts();
}
render() {
const { Selectedproduct } = this.state;
return (
<div>
<Slide left cascade={true}>
{!this.props.products ? (
<div> Loading..</div>
) : (
<ul className="products">
{this.props.products.map((product) => (
<li key={product._id}>
<div className="product">
<a href={"#" + product._id}>
<img
src={product.image}
alt={product.title}
onClick={() => this.showModal(product)}
></img>
<p>{product.title}</p>
</a>
<div className="product-price">
<div> {FormatCurrency(product.price)}</div>
<button
onClick={() => this.props.AddToCart(product)}
className="button primary overlay"
>
{" "}
Add to cart
</button>
</div>
</div>
</li>
))}
</ul>
)}
</Slide>
<Modal isOpen={this.state.show} onRequestClose={this.hideModal}>
<Zoom>
<button className="close-modal" onClick={this.hideModal}>
x
</button>
<div className="product-details">
<img
src={Selectedproduct.image}
alt={Selectedproduct.title}
></img>
<div className="product-details-description">
<p>{Selectedproduct.title}</p>
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
<div className="product-price">
<div>{FormatCurrency(Selectedproduct.price)}</div>
<button
className="button primary"
onClick={() => {
this.props.AddToCart(Selectedproduct);
this.hideModal();
}}
>
{" "}
Add to cart
</button>
</div>
</div>
</div>
</Zoom>
</Modal>
</div>
);
}
}
export default connect((state) => ({ products: state.products.filterdItems }), {
GetProducts,
AddToCart,
})(Products);
Try this as your state property seems still undefined at runtime.
{Selectedproduct.description.map((x)=>(<li>x</li>))}
replace with:
{Selectedproduct && Selectedproduct.description? Selectedproduct.description.map((x)=>(<li>x</li>)):null}
description is likely undefined. Instead of:
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
just put in this temporary code to try and see what your object really looks like:
<ul>
console.dir("### DESCRIPTION IS:", Selectedproduct.description)
</ul>
and the open your browser dev tools to see what this prints to the console.
UPDATE based on comment after using console.log:
If you are getting something like availableColors: Array(2) for Selectedproduct you cannot print an array out to your <li> tags. An array is not a string. You have to unnest the inner arrays first.
So if your structure is Selectedproduct.description.availableColors = ['blue', 'red'] just as an example, you will need code like:
const { availableColors, otherAttribute1, otherAttribute2 } = Selectedproduct.description // destructure all array attributes from description
...
and then later in the component, do:
<ul>
{ availableColors.map(_ => <li>_</li>)}
{ otherAttribute1.map(_ => <li>_</li>)}
{ otherAttribute2.map(_ => <li>_</li>)}
</ul>

TypeError: Cannot read property 'classList' of null react.js application

I've been struggling with this bug. I keep getting TypeError: Cannot read property 'classList' of null on my react application. I'm quite new to react so have been trying t figure out the issue with no avail. Here's the error and my component, anyone know what the problem is?
The TypeError Message as text:
←→1 of 8 errors on the page
TypeError: Cannot read property 'classList' of null
(anonymous function)
http://localhost:3003/static/js/main.chunk.js:8044:49
8041 | var value = window.scrollY;
8042 |
8043 | if (value > 100) {
> 8044 | document.querySelector('.header--fixed').classList.add('sticky');
| ^ 8045 | } else {
8046 | document.querySelector('.header--fixed').classList.remove('sticky');
8047 | }
View source
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error.
My Component/ code:
import React, { Component } from "react";
import {FaTwitter ,FaInstagram ,FaFacebookF , FaLinkedinIn } from "react-icons/fa";
import { FiX , FiMenu} from "react-icons/fi";
import Scrollspy from 'react-scrollspy'
import mypdf from "../../../public/assets/files/Sarah-Chosen-CV.pdf";
const SocialShare = [
// {Social: <FaFacebookF /> , link: 'https://www.facebook.com/'},
// {Social: <FaInstagram /> , link: 'https://www.instagram.com/'},
// {Social: <FaTwitter /> , link: 'https://twitter.com/'},
{Social: <FaLinkedinIn /> , link: 'https://www.linkedin.com/in/sarah-chosen/'},
]
class HeaderThree extends Component{
constructor(props) {
super(props);
this.menuTrigger = this.menuTrigger.bind(this);
this.CLoseMenuTrigger = this.CLoseMenuTrigger.bind(this);
this.stickyHeader = this.stickyHeader.bind(this);
// this.subMetuTrigger = this.subMetuTrigger.bind(this);
window.addEventListener('load', function() {
console.log('All assets are loaded');
})
}
menuTrigger() {
document.querySelector('.header-wrapper').classList.toggle('menu-open')
}
CLoseMenuTrigger() {
document.querySelector('.header-wrapper').classList.remove('menu-open')
}
stickyHeader () {}
render(){
window.addEventListener('scroll', function() {
var value = window.scrollY;
if (value > 100) {
document.querySelector('.header--fixed').classList.add('sticky')
}else{
document.querySelector('.header--fixed').classList.remove('sticky')
}
});
var elements = document.querySelectorAll('.has-droupdown > a');
for(var i in elements) {
if(elements.hasOwnProperty(i)) {
elements[i].onclick = function() {
this.parentElement.querySelector('.submenu').classList.toggle("active");
this.classList.toggle("open");
}
}
}
const { logo, color='default-color' } = this.props;
let logoUrl;
if(logo === 'light'){
logoUrl = <img src="/assets/images/logo/logo-light.png" alt="Digital Agency" />;
}else if(logo === 'dark'){
logoUrl = <img src="/assets/images/logo/logo-dark.png" alt="Digital Agency" />;
}else if(logo === 'symbol-dark'){
logoUrl = <img src="/assets/images/logo/logo-symbol-dark.png" alt="Digital Agency" />;
}else if(logo === 'symbol-light'){
logoUrl = <img src="/assets/images/logo/logo-symbol-light.png" alt="Digital Agency" />;
}else{
logoUrl = <img src="/assets/images/logo/logo.png" alt="Digital Agency" />;
}
return (
<header
className={`header-area header-style-two header--fixed ${color}`}
>
<div className="header-wrapper">
<div className="header-left d-flex align-items-center">
<div className="logo">
<a href={this.props.homeLink}>{logoUrl}</a>
</div>
<nav className="mainmenunav d-lg-block ml--50">
<Scrollspy
className="mainmenu"
items={[
"home",
"about",
"service",
"portfolio",
"blog",
"contact",
]}
currentClassName="is-current"
offset={-200}
>
<li>
Home
</li>
<li>
About
</li>
<li>
Skillset
</li>
<li>
Portfolio
</li>
<li>
Contact
</li>
</Scrollspy>
</nav>
</div>
<div className="header-right">
<div className="social-share-inner">
<ul className="social-share social-style--2 color-black d-flex justify-content-start liststyle">
{SocialShare.map((val, i) => (
<li key={i}>
<a href={`${val.link}`}>{val.Social}</a>
</li>
))}
</ul>
</div>
<div className="header-btn">
<a className="rn-btn" href={mypdf} download="Sarah-Chosen-CV.pdf">
<span>download CV</span>
</a>
</div>
{/* Start Humberger Menu */}
<div className="humberger-menu d-block d-lg-none pl--20">
<span
onClick={this.menuTrigger}
className="menutrigger text-white"
>
<FiMenu />
</span>
</div>
{/* End Humberger Menu */}
<div className="close-menu d-block d-lg-none">
<span
onClick={this.CLoseMenuTrigger}
className="closeTrigger"
>
<FiX />
</span>
</div>
</div>
</div>
</header>
);
}
}
export default HeaderThree;
Adding event listeners in react components should happen when the component has its DOM representation is ready. componentDidMount is called after the component is mounted and has a DOM representation. This is often a place where you would attach generic DOM events.
So try to move the code where you assign the listener for scroll event to componentDidMount lifecycle method. and also remove the event listener in componentWillUnmount. What makes this more important is that React components will often be re-rendered, and the code to set up event listeners will have the opportunity to run many times. This can create errors by setting up multiple listeners where we're actually expecting a single listener run per event occurrence.
Additionally, React-based UIs are often used in single-page apps that exist within long-lived browser sessions. This creates an environment where memory leaks can become serious more often.
Check out this article from react: https://react-cn.github.io/react/tips/dom-event-listeners.html
and also this answer describe how to wrap a component with scrollWrapper that handles the scroll event

Use conditional logic in an array map?

Ok so I am using express-react-views as a templating engine and I am currently trying to make a working breadcrumb. On each route I pass a prop called "crumb" that is an array of the current location on the app. That array looks like this:
[
{
text: "Home",
href:"/",
active: false
},
{
text: "Step2",
href:`/page`,
active: true
}
]
Obviously this can be multiple steps down. The last step is the page you are on, so active is set to true. This is where my problem is. To render this on the page I am mapping this array to JSX like this:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
This code works fine but what the active page should have the class "active" on it and should not have an "a" tag. So what I need to do it as it's mapping this array to check for the active:true value and then map a different element. I hope that makes sense.
Hi you can try this out if you want both active and inactive links to be shown:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
crumb.active ? <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li> : <li class="breadcrumb-item">{crumb.text}</li>
)}
</ol>
)
}
if you only want to show active links then you can use:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.filter(item => item.active).map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
Inside map you can check crumb.active, so it will either return true or false and based on that you can return respective element.
Is this what you want
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb => {
if(crumb.active)
return <li class="breadcrumb-item active"></li>
else
return <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
})}
</ol>
)
}

Rendering an array.map() in React

I am having a problem where I am trying to use array of data to render a <ul> element. In the code below the console logs are working fine, but the list items aren't appearing.
var Main = React.createClass({
getInitialState: function(){
return {
data: dataRecent
}
},
render: function(){
return (
<div>
<ul>
{
this.state.data.map(function(item, i){
console.log('test');
<li>Test</li>
})
}
</ul>
</div>
)
}
});
ReactDOM.render(<Main />, document.getElementById('app'));
What am I doing wrong? Please feel free to point out anything that isn't best practice.
Gosha Arinich is right, you should return your <li> element.
But, nevertheless, you should get nasty red warning in the browser console in this case
Each child in an array or iterator should have a unique "key" prop.
so, you need to add "key" to your list:
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
or drop the console.log() and do a beautiful oneliner, using es6 arrow functions:
this.state.data.map((item,i) => <li key={i}>Test</li>)
IMPORTANT UPDATE:
The answer above is solving the current problem, but as Sergey mentioned in the comments: using the key depending on the map index is BAD if you want to do some filtering and sorting. In that case use the item.id if id already there, or just generate unique ids for it.
You are not returning. Change to
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>;
})
let durationBody = duration.map((item, i) => {
return (
<option key={i} value={item}>
{item}
</option>
);
});
Using Stateless Functional Component We will not be using this.state. Like this
{data1.map((item,key)=>
{ return
<tr key={key}>
<td>{item.heading}</td>
<td>{item.date}</td>
<td>{item.status}</td>
</tr>
})}
You are implicitly returning undefined. You need to return the element.
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>
})
Best Answer:
import React, { useState } from 'react';
import './App.css';
function App() {
// Array of objects containing our fruit data
let fruits = [
{ label: "Apple", value: "🍎" },
{ label: "Banana", value: "🍌" },
{ label: "Orange", value: "🍊" }
]
// Using state to keep track of what the selected fruit is
let [fruit, setFruit] = useState("⬇️ Select a fruit ⬇️")
// Using this function to update the state of fruit
// whenever a new option is selected from the dropdown
let handleFruitChange = (e) => {
setFruit(e.target.value)
}
return (
<div className="App">
{/* Displaying the value of fruit */}
{fruit}
<br />
<select onChange={handleFruitChange}>
{
fruits.map((fruit) => <option value={fruit.value}>{fruit.label}</option>)
}
</select>
</div>
);
}
export default App;
Add up to Dmitry's answer, if you don't want to handle unique key IDs manually, you can use React.Children.toArray as proposed in the React documentation
React.Children.toArray
Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.
Note:
React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it.
<div>
<ul>
{
React.Children.toArray(
this.state.data.map((item, i) => <li>Test</li>)
)
}
</ul>
</div>
I've come cross an issue with the implementation of this solution.
If you have a custom component you want to iterate through and you want to share the state it will not be available as the .map() scope does not recognize the general state() scope.
I've come to this solution:
`
class RootComponent extends Component() {
constructor(props) {
....
this.customFunction.bind(this);
this.state = {thisWorks: false}
this.that = this;
}
componentDidMount() {
....
}
render() {
let array = this.thatstate.map(() => {
<CustomComponent that={this.that} customFunction={this.customFunction}/>
});
}
customFunction() {
this.that.setState({thisWorks: true})
}
}
class CustomComponent extend Component {
render() {
return <Button onClick={() => {this.props.customFunction()}}
}
}
In constructor bind without this.that
Every use of any function/method inside the root component should be used with this.that
Dmitry Brin's answer worked for me, with one caveat. In my case, I needed to run a function between the list tags, which requires nested JSX braces. Example JSX below, which worked for me:
{this.props.data().map(function (object, i) { return <li>{JSON.stringify(object)}</li> })}
If you don't use nested JSX braces, for example:
{this.props.data().map(function (object, i) { return <li>JSON.stringify(object)</li>})}
then you get a list of "JSON.stringify(object)" in your HTML output, which probably isn't what you want.
import React, { Component } from 'react';
class Result extends Component {
render() {
if(this.props.resultsfood.status=='found'){
var foodlist = this.props.resultsfood.items.map(name=>{
return (
<div className="row" key={name.id} >
<div className="list-group">
<a href="#" className="list-group-item list-group-item-action disabled">
<span className="badge badge-info"><h6> {name.item}</h6></span>
<span className="badge badge-danger"><h6> Rs.{name.price}/=</h6></span>
</a>
<a href="#" className="list-group-item list-group-item-action disabled">
<div className="alert alert-dismissible alert-secondary">
<strong>{name.description}</strong>
</div>
</a>
<div className="form-group">
<label className="col-form-label col-form-label-sm" htmlFor="inputSmall">Quantitiy</label>
<input className="form-control form-control-sm" placeholder="unit/kg" type="text" ref="qty"/>
<div> <button type="button" className="btn btn-success"
onClick={()=>{this.props.savelist(name.item,name.price);
this.props.pricelist(name.price);
this.props.quntylist(this.refs.qty.value);
}
}>ADD Cart</button>
</div>
<br/>
</div>
</div>
</div>
)
})
}
return (
<ul>
{foodlist}
</ul>
)
}
}
export default Result;

Categories

Resources