React testing library doesn't re-render - javascript

I have tried to re-render on multiple tests suites but I'm always doing something wrong and I have no idea what.
There is ternary that controls within Dropdown component what should be rendered and what shouldn't.
import React, { useState, Fragment, useContext } from 'react';
import { library } from '#fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import useAuthContext from '../../hooks/useAuthContext/useAuthContext'
import { Link } from 'react-router-dom';
import {
faCog,
faMoon,
faCaretDown,
faUser,
faOm
} from '#fortawesome/free-solid-svg-icons';
library.add(faCaretDown, faCog, faMoon, faUser);
const Dropdown = props => {
const { isAuth, logoutReducer } = useAuthContext()
const [active, setActive] = useState(false);
let dropdownBody;
const dropDownHandler = () => {
setActive(!active);
};
if (active) {
dropdownBody = (
<ul className="dropdown__body">
{isAuth ? (
<>
<li>
<button onClick={logoutReducer}>Logout</button>
</li>
</>
) : (
<>
<li>
<Link data-testid="signup" to="/signup">
SignUp
</Link>
</li>
<li>
<Link data-testid="login" to="/login">
Login
</Link>
</li>
</>
)}
</ul>
);
}
return (
<div className="dropdown">
{dropdownBody}
</div>
);
};
export default Dropdown;
describe('<Dropdown/>',() => {
afterEach(cleanup)
it('calling render with the same component on the same container does not remount', () => {
const authState = {
isAuth:false,userId:null,token:null,userData:null
}
const { container,getByText,getByTestId,rerender } = render (<Dropdown />,{wrapper: ({ children }) => (
<AuthContextTestWrapper authState={authState} children={children} />
)})
let signUpButton = (waitForElement(() => getByText('SignUp')))
let loginButton = (waitForElement(() => getByText('Login')))
expect(signUpButton).toBeTruthy()
expect(loginButton).toBeTruthy()
rerender(<Dropdown />,{wrapper: ({ children }) => (
<AuthContextTestWrapper authState={authState} children={children} />
)})
signUpButton = (waitForElement(() => getByText('SignUp')))
loginButton = (waitForElement(() => getByText('Login')))
// This part comes as truthy
expect(signUpButton).toBeFalsy()
expect(loginButton).toBeFalsy()
})
})

Related

599Item.js:12 Uncaught TypeError: product.map is not a function in react

I'm putting together my first react as an exercise to understand but I get this error
599Item.js:12 Uncaught TypeError: product.map is not a function in component item.js on line 12
I copy the component item,js, it has a prop that is the product object
import React, {useState, useEffect} from "react";
import '../App.css';
import 'materialize-css/dist/css/materialize.css';
import Count from './ItemCount';
export const Item =(({product}) => {
const [sellProduct, setSellProduct] = useState(product);
useEffect(() => {
setSellProduct({product});
}, [product]);
const mensaje = () => {
alert ('Gracias por su compra');
}
return (
<>
{
sellProduct.map((item) => {
return(
<Count stock={item.stock} onAdd ={mensaje} >
<div id= {item.id}>
<h3>{item.name}</h3> - <small>{item.category}</small>
<img src= {item.picture} alt="Producto" className="itemImg" />
<p>{item.description}</p>
<p>{item.price}</p>
</div>
</Count>
)
})
}
</>
)
});
expor default Item;
I take the prop out in a fetch and pass it to the child component, which passes the prop to the items component and I copy the code of both
ItemListContainer(parent)
import React, { useEffect, useState } from "react";
import '../App.css';
import 'materialize-css/dist/css/materialize.css';
import ItemList from './ItemList';
export const ItemListContainer =() => {
const [swSell,setSwSell] = useState([])
useEffect(() => {
fetch('https://fakestoreapi.com/products')
.then((res) => res.json())
.then((data) => {
setSwSell(data);
})
.catch((err) => {
console.log(err);
});
}, []);
return(
<div className="parent">
{
swSell.map((item) =>(
<div className="child" key={item.id}>
<ItemList item={swSell} />
</div>
))
}
</div>
)}
export default ItemListContainer;
ItemList(child), this passes to the Item component
import React from "react";
import '../App.css';
import 'materialize-css/dist/css/materialize.css';
import Item from './Item';
export const ItemList = (({item}) => {
return (
<div className="child">
{
item.map((item) =>(
<Item product= {item} />
))
}
</div>
)
});
export default ItemList;
I appreciate your help
In Item component instead of
product.map((item)...
replace it with
product && product.map((item)...
The error:
product.map is not a function in react
arise since the product props that you set as sellProduct is not an array. It is actually an item (object) of swSell array.
So, first, change this:
const ComponentName = (({ ... }) => {
...
})
into this:
const ComponentName = ({ ... }) => {
...
}
Next, since the swSell state is a collection of item object where the item is the product itself, then you only need two component here: ItemList as parent and Item as the child that render the item details
const Item = ({item}) => {
return (
<div id= {item.id}>
<h3>{item.name}</h3> - <small>{item.category}</small>
<img src={item.image} width="100"/>
<p>{item.description}</p>
<p>{item.price}</p>
</div>
)
};
function ItemList() {
const [swSell,setSwSell] = React.useState([])
React.useEffect(() => {
fetch('https://fakestoreapi.com/products')
.then((res) => res.json())
.then((data) => {
setSwSell(data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div>
{swSell.map((item) => (
<div className="child" key={item.id}>
<Item item={item} />
</div>
))}
</div>
);
}
ReactDOM.render(<ItemList />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

Not able to display the details page for a product in React

On my products list page when i click on view icon underneath the product thumbnail it takes me to a blank product details page.the product detail page has the right id but doesn't display anything
This is my code for the product details page.
import { useState, useEffect } from 'react';
import {useParams} from 'react-router-dom';
const axios = require('axios');
const SingleProduct = () => {
const [data, setData] = useState([]);
const params = useParams();
useEffect(() => {
fetchProduct();
}, []);
const fetchProduct = () => {
axios
.get(
`http://localhost:5000/api/products/${params._id}`
)
.then((res) => {
setData(res.data);
console.log(res.data);
})
.catch((err) => console.log(err))
;
};
return (
<div>
{data.map((item) => {
return (
<div className='product-container' key={item._id}>
<div>
<img className='prod-image' src={item.imageUrl} alt='' />
</div>
<div>
<h1 className='brand'>{item.name}</h1>
<h2>{item.price}</h2>
<p>{item.description}</p>
<p>
<strong>Price:</strong> {item._id}
</p>
<p>
<strong>Color:</strong> {item.color}
</p>
</div>
</div>
);
})}
</div>
);
;
}
export default SingleProduct;
This is the code in App.js:
<Route path='/SingleProduct/:_id' element={SingleProduct} />
This the code in the products list page:
import React from 'react'
import {useEffect} from 'react';
import {Link} from 'react-router-dom'
const ProductsPage = (props) => {
const items = props.items;
const getItems = props.getItems;
useEffect(() => {
getItems();
}, [])
return (
<>
<section className="ProductsPage items-page" onload = {getItems}>
{items.filter (item => item.price < 1000).map(item => (
<ul items-style>
<img src={item.imageUrl} alt="product" />
<h2>{item.name}</h2>
<Link to={`/SingleProduct/${item._id}`}>View</Link>
</ul>
//
))}
</section>
</>
) }
export default ProductsPage

React toggle view functionality in the parent via child component

I am trying to toggle view between list of meals and meal details. I have placed a button in the child component Meal.js to the Meals.js which is meant to be the list and the details view.
Can you please help me fix this issue. Seems like its not working even with the conditional rendering method I've used in the code below.
Meal.js
import { useState } from 'react'
import './Meal.css'
const Meal = (props) => {
const [isToggled, setIsToggled] = useState(false);
const sendIdHandler = () => {
if (isToggled === true) {
setIsToggled(false);
}
else {
setIsToggled(true);
}
props.onSaveIdHandler(props.id, isToggled)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Meals.js
import Meal from './Meal/Meal'
const Meals = (props) => {
let toggleCondition = false;
const saveIdHandler = (data, isToggled) => {
toggleCondition = isToggled;
const mealDetails = props.mealsMenuData.findIndex(i =>
i.id === data
)
console.log(mealDetails, toggleCondition)
}
return (
<div>
{toggleCondition === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{toggleCondition === true &&
<div>Horray!</div>
}
</div>
);
}
export default Meals;
UPDATE
Finally figured how to do this properly. I put the condition true/false useState in the parent instead and have Meal.js only send the id I need to view the item
Code is below..
Meals.js
import { useState } from 'react'
import Meal from './Meal/Meal'
import MealDetails from './MealDetails/MealDetails'
const Meals = (props) => {
const [show, setShow] = useState(false);
const [mealId, setMealId] = useState(0);
const saveIdHandler = (data) => {
setShow(true);
setMealId(props.mealsMenuData.findIndex(i =>
i.id === data)
)
console.log(props.mealsMenuData[mealId].ingridients)
}
const backHandler = () => {
setShow(false)
}
return (
<div>
{show === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{show === true &&
<div>
<MealDetails data={props.mealsMenuData[mealId]} />
<button onClick={backHandler}>Back</button>
</div>
}
</div>
);
}
export default Meals;
Meal.js
import './Meal.css'
const Meal = (props) => {
const sendIdHandler = () => {
props.onSaveIdHandler(props.id)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Your problem in sendIdHandler: You can update like this:
const sendIdHandler = () => {
const newIsToggled = !isToggled;
setIsToggled(newIsToggled)
props.onSaveIdHandler(props.id, newIsToggled)
}

Why react loads images for many times? is it possible to save the first load?

to watch the problem you can vivsit the test site http://u100525.test-handyhost.ru/products
the problem appears if to click many times on category items, images of products start to bug becouse react loads image of one item over and over again, on every change of category - on every filter of products, so how to make one load and save somehow the loaded images?
so if i click on categories my code is filtering products array and update statement - visibleProducts then im doing visibleProducts.map((product)=>{});
and i`m getting bug problem, because every time when react renders my the component does request to the server for getting image by id and waits while the image will load, but if i click on an other category react(ProductItem) starts other request for new images then it is starting to bug they start blinking and changing ;c
im new in react and just stated to practice what i have to do guys?
is my code correct ?
here is my ProductItem component ->
import React, { useState, useEffect, memo, useCallback } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { setModalShow, onQuickViewed, addedToCart } from "../../actions";
import Checked from "../checked";
import "./product-item.css";
import Spinner from "../spinner";
const ProductItem = ({
product,
wpApi,
addedToCart,
onQuickViewed,
setModalShow,
}) => {
const [prodImg, setProdImg] = useState("");
const [animated, setAnimated] = useState(false);
const [checked, setChecked] = useState(false);
const [itemLoading, setItemLoading] = useState(true);
const checkedFn = useCallback(() => {
setChecked(true);
setTimeout(() => {
setChecked(false);
}, 800);
},[product]);
const onModalOpen = useCallback((e, id) => {
onQuickViewed(e, id);
setModalShow(true);
}, product);
const addHandle = useCallback((e, id) => {
e.preventDefault();
addedToCart(id);
checkedFn();
},[product]);
useEffect(()=>{
setItemLoading(false);
}, [prodImg]);
useEffect(() => {
wpApi.getImageUrl(product.imageId).then((res) => {
setProdImg(res);
});
});
return (
<div className="product foo">
<div
className='product__inner'}
>
{!itemLoading? <div
className="pro__thumb"
style={{
backgroundImage:prodImg
? `url(${prodImg})`
: "assets/images/product/6.png",
}}
>
<Link
to={`/product-details/${product.id}`}
style={{ display: `block`, width: `100%`, paddingBottom: `100%` }}
>
</Link>
</div>: <Spinner/>}
<div className="product__hover__info">
<ul className="product__action">
<li>
<a
onClick={(e) => {
onModalOpen(e, product.id);
}}
title="Quick View"
className="quick-view modal-view detail-link"
href="#"
>
<span ><i class="zmdi zmdi-eye"></i></span>
</a>
</li>
<li>
<a
title="Add TO Cart"
href="#"
onClick={(e) => {
addHandle(e, product.id);
}}
>
{checked ? (
<Checked />
) : (
<span className="ti-shopping-cart"></span>
)}
</a>
</li>
</ul>
</div>
</div>
<div className="product__details">
<h2>
<Link to={`/product-details/${product.id}`}>{product.title}</Link>
</h2>
<ul className="product__price">
<li className="old__price">${product.price}</li>
</ul>
</div>
</div>
);
};
const mapStateToProps = ({ options, cart, total, showModal }) => {
return {};
};
const mapDispatchToProps = {
onQuickViewed,
setModalShow,
addedToCart,
};
export default connect(mapStateToProps, mapDispatchToProps)(memo(ProductItem));
here is my parent component Products ->
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import ProductItem from "../product-item";
import { withWpApiService } from "../hoc";
import { onQuickViewed, addedToCart, categoriesLoaded } from "../../actions";
import CategoryFilter from "../category-filter";
import Spinner from "../spinner";
import "./products.css";
const Products = ({
maxProducts,
WpApiService,
categoriesLoaded,
addedToCart,
onQuickViewed,
products,
categories,
loading,
}) => {
const [activeIndex, setActiveIndex] = useState(0);
const [activeCategory, setActiveCategory] = useState(0);
const [visibleProducts, setVisibleProducts] = useState([]);
const wpApi = new WpApiService();
useEffect(() => {
updateVisibleProducts(activeCategory, products);
}, [products]);
useEffect(() => {
wpApi.getCategories().then((res) => {
categoriesLoaded(res);
});
}, []);
const getCatId = (cat) => {
setActiveCategory(cat);
updateVisibleProducts(cat, products);
setActiveIndex(cat);
};
const updateVisibleProducts = (category, products) => {
let updatedProducts = [];
switch (category) {
case 0:
updatedProducts = products;
setVisibleProducts(updatedProducts);
break;
default:
updatedProducts = products.filter(
(product) => product.categories.indexOf(category) >= 0
);
setVisibleProducts(updatedProducts);
}
};
let currentLocation = window.location.href.split("/");
if (!loading) {
return (
<section className="htc__product__area shop__page mb--60 mt--130 bg__white">
<div className={currentLocation[3] == "" ? `container` : ""}>
<div className="htc__product__container">
<CategoryFilter
activeIndex={activeIndex}
categories={categories}
getCatId={getCatId}
/>
<div
className="product__list another-product-style"
style={{ height: "auto" }}
>
{visibleProducts
.slice(0, maxProducts ? maxProducts : products.length)
.map((prod, id) => {
return (
<ProductItem
wpApi={wpApi}
key={id}
onQuickViewed={onQuickViewed}
addedToCart={addedToCart}
product={prod}
/>
);
})}
</div>
</div>
</div>
</section>
);
} else {
return <Spinner />;
}
};
const mapStateToProps = ({ products, loading, activeCategory, categories }) => {
return {
products,
activeCategory,
categories,
loading,
};
};
const mapDispatchToProps = {
addedToCart,
categoriesLoaded,
onQuickViewed,
};
export default withWpApiService()(
connect(mapStateToProps, mapDispatchToProps)(Products)
);
and if you need, here is my CategoryFilter component ->
import React from 'react'
const CategoryFilter = ({categories, getCatId, activeIndex}) => {
return (
<div className="row mb--60">
<div className="col-md-12">
<div className="filter__menu__container">
<div className="product__menu">
{categories.map((cat) => {
return (
<button key={cat.id}
className={activeIndex === cat.id? 'is-checked' : null}
onClick={() => getCatId(cat.id)}
data-filter=".cat--4"
>
{cat.name}
</button>
);
})}
</div>
</div>
</div>
</div>
)
}
export default CategoryFilter

React Router changing URL, but component not rendering

I have been trying to learn React over the past couple of weeks and started working on a site which displays art works.
I would like for the user to be able to click on one of the images displayed and for a new component to be loaded with information about the work.
I have the implementation below of the gallery view, but when I click on an image the URL changes, but the WorkPage component never loads.
Would anyone be able to spot what I am doing wrong? The links and images are generated in the renderItems() function.
import React, { Component } from "react";
import Masonry from 'react-masonry-css';
import WorkPage from "./WorkPage"
import axios from "axios";
import { Link, Route, Switch, useRouteMatch, useParams } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";
class Works extends Component {
constructor(props) {
super(props);
this.state = {
viewPaintings: true,
workList: []
};
axios
.get("http://localhost:8000/api/works/")
.then(res => this.setState({ workList: res.data }))
.catch(err => console.log(err))
};
displayPaintings = status => {
if (status) {
return this.setState({ viewPaintings: true })
}
return this.setState({ viewPaintings: false })
};
renderTabList = () => {
return (
<div>
<ul className="tab-list-buttons">
<li onClick={() => this.displayPaintings(true)}
className={this.state.viewPaintings ? "active" : "disabled"}
key="button1"
>
Paintings
</li>
<li onClick={() => this.displayPaintings(false)}
className={this.state.viewPaintings ? "disabled" : "active"}
key="button2"
>
Works on Paper
</li>
</ul>
</div>
);
};
renderItems = () => {
const { viewPaintings } = this.state;
const newItems = viewPaintings
? this.state.workList.filter(item => item.type === 1)
: this.state.workList.filter(item => item.type === 0);
const breakpointColumnsObj = {
default: 4,
1100: 3,
700: 2,
500: 1
};
const items = newItems.map(item => (
<div key = {item.slug}>
<Link to={`${item.slug}`}>
<img src={item.image} alt={item.name} width="300"/>
</Link>
<Switch>
<Route path=":item.slug" component={WorkPage} />
</Switch>
</div>
));
return (
<Masonry
breakpointCols={breakpointColumnsObj}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{items}
</Masonry>
);
}
render() {
return (
<Router>
<div>
{ this.renderTabList() }
{ this.renderItems() }
</div>
</Router>
)
};
}
export default Works;

Categories

Resources