I need to test results in my ul from the movielistcomponent but i keep getting unbale to find element [data-testid=movieList]
My movielistComponent
</section>
{isLoaded ? (
items.length === 0 ? (
<div>No Results Found</div>
) : (
<ul className="mt-50 styled" data-testid='movieList'>
{items.map((item) => (
<li className="slide-up-fade-in py-10" key={item.id}>
{item.Title}
</li>
))}
</ul>
)
) : (
<div className="mt-50 slide-up-fade-in" data-testid="no-result"></div>
)}
</div>
My test
import React from 'react';
import App from './App';
import { render, fireEvent, cleanup, waitFor , screen} from '#testing-library/react';
import fetchMock from 'fetch-mock';
import '#testing-library/jest-dom/extend-expect';
const renderApp = () => render(<App/>);
afterEach(() => {
fetchMock.restore();
cleanup()
});
test('initial UI is rendered as expected', () => {
let { getByTestId, queryByTestId } = renderApp();
expect(getByTestId('app-input')).toHaveValue(null);
expect(getByTestId('submit-button')).toHaveTextContent("Search");
expect(screen.getByTestId('movieList').childNodes).toHaveLength(0);
expect(queryByTestId('no-result')).toBe(null);
});
Related
I have researched and tried to implement some of the solutions provided but I failed when trying to achieve this. I was able to make my dropdown menu and click so each submenu will open and close when its parent is clicked. I would like to have an opened submenu to be closed when a different menu is clicked, so I don´t have all of them stacked at the menu bar. Could someone point out how can I achieve this? Thank you for helping me.
Menu.js
import React from 'react'
import MenuItem from '../MenuItem';
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
const Menu = () => {
return (
<C.Container>
<C.MenuArea>
{SidebarData.map((item, index) => {
return <MenuItem item={item} key={index} />;
})}
</C.MenuArea>
</C.Container>
)
};
export default Menu
MenuItem.js
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import * as C from './styles';
const MenuItem = ({ item }) => {
const [opened, setOpened] = useState(false);
const showSubnav = () => setOpened(!opened);
return (
<C.NavUnlisted>
<NavLink to={item.path} onClick={item.subNav && showSubnav} activeClassName='current' exact={item.path === '/' ? true : false} >
<C.SidebarLink>
<div>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</div>
<div>
{item.subNav && opened
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</C.SidebarLink>
</NavLink>
{opened &&
item.subNav.map((item, index) => {
return (
<NavLink to={item.path} key={index} activeClassName='current' >
<C.DropdownLink>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</C.DropdownLink>
</NavLink>
);
})}
</C.NavUnlisted>
);
};
export default MenuItem;
I was able to find a solution. I added an ID for each Menu so I could change the state based upon it. I had all the menu in a single component. It didn't seem to be necessary to have a separate component for the Menu Item.
Here is my code:
import React, { useState } from 'react'
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
import { NavLink } from "react-router-dom";
const Menu = () => {
const [open, setOpen] = useState('');
const toggle = (id) => setOpen(id);
return (
<C.Container>
<C.MenuArea>
{SidebarData.map((item, index) => (
<C.NavUnlisted key={index}>
<NavLink to={item.path} onClick={() => toggle(item.navId)} activeClassName='current' exact={item.path === '/' ? true : false}>
<C.SidebarLink>
<div>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</div>
</C.SidebarLink>
</NavLink>
{open === item.navId && (
<div>
{item.subNav.map((item, index) => (
<NavLink to={item.path} key={index} activeClassName='current' >
<C.DropdownLink>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</C.DropdownLink>
</NavLink>
))}
</div>
)}
</C.NavUnlisted>
))}
</C.MenuArea>
</C.Container>
)
};
export default Menu
Try to close the menu when you click outside your menu component, if it's a solution you're interested in you can learn more about how to achieve this in react there :
https://stackoverflow.com/a/42234988/16956436
An elegant way to handle this would be to keep track of the currently opened submenu in the Menu component and displaying/hiding the submenus depending on a prop passed down from the parent component.
import React from 'react'
import MenuItem from '../MenuItem';
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
const Menu = () => {
const [currentlyOpen, setCurrentlyOpen] = useState(null);
return (
<C.Container>
<C.MenuArea>
{SidebarData.map((item, index) => {
return <MenuItem item={item} key={index} isOpen={index === currentlyOpen} onClick={() => setCurrentlyOpen(index)} />;
})}
</C.MenuArea>
</C.Container>
)
};
export default Menu
You would then call handleClick with the respective index in MenuItem.js.
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import * as C from './styles';
const MenuItem = ({ item, onClick: handleClick }) => {
const [opened, setOpened] = useState(false);
const showSubnav = () => setOpened(!opened);
return (
<C.NavUnlisted>
<NavLink to={item.path} onClick={item.subNav && handleClick} activeClassName='current' exact={item.path === '/' ? true : false} >
<C.SidebarLink>
<div>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</div>
<div>
{item.subNav && opened
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</C.SidebarLink>
</NavLink>
{opened &&
item.subNav.map((item, index) => {
return (
<NavLink to={item.path} key={index} activeClassName='current' >
<C.DropdownLink>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</C.DropdownLink>
</NavLink>
);
})}
</C.NavUnlisted>
);
};
export default MenuItem;
I've implemented infinite scroll in React app, however, I get Warning: Encountered two children with the same key <...>. Keys should be unique so that components maintain their identity across updates. <...>.
If I replace key={movie.id} by key={i}, infinite scroll stops working.
This is the code of my component:
import{ useContext, useRef, useEffect } from "react";
import { Card, Grid, CardActionArea, CardMedia } from '#material-ui/core';
import FavoriteBorderIcon from '#material-ui/icons/FavoriteBorder';
import { MoviesContext } from "../../services/context";
import { Movie } from "../../services/movies.service";
import '../../App.scss';
import './Catalog.scss';
import noImage from '../../images/no-image-available.png';
import loadingSpinner from '../../images/loading-spinner.gif';
import { NavLink } from 'react-router-dom';
import useIntersectionObserver from '../../services/useIntersectionObserver';
import { fetchMovies } from "../../services/movies.service";
const posterBaseUrl = "https://image.tmdb.org/t/p/w300";
const CatalogCards = () => {
const { movies, updateMovies, searchedMovie, moviesPage, setMoviesPage, setSelectedMovie, setIsMoviePageFirstTimeOpened } = useContext(MoviesContext);
const loadingRef = useRef<HTMLDivElement | null>(null);
const entry = useIntersectionObserver(loadingRef, {})
const isVisible = !!entry?.isIntersecting;
const SetSelectedMovieId = (id: number) => {
setIsMoviePageFirstTimeOpened(true);
setSelectedMovie(id);
}
useEffect (
() => {
if ( isVisible ) {
setMoviesPage(moviesPage+1);
fetchMovies(String(moviesPage))
.then(nextPage => {
updateMovies((movies: Movie[]) => movies.concat(nextPage));
})
.catch(() => updateMovies([]))
}
},
[isVisible]
);
return (
<div >
<Grid container spacing={1} className="container-content">
{
movies.length > 0
?
movies.map((movie, i) => (
<Grid item key={movie.id}>
<NavLink to={'/movie/' + movie.id}>
<Card className="card-list" onClick={() => SetSelectedMovieId(movie.id)} >
<CardActionArea>
<CardMedia
component="img"
alt={"Poster of " + movie.title}
image={movie.poster_path ? posterBaseUrl + movie.poster_path : noImage}
title={movie.title}
/>
</CardActionArea>
</Card>
</NavLink>
</Grid>
))
:
searchedMovie ?
<div className="">Try a different phrase...</div>
:
<CardMedia
component="img"
image={loadingSpinner}
className="loading-spinner"
/>
}
</Grid>
<div ref={loadingRef}>...</div>
</div>
);
}
export default CatalogCards;
fetchMovies() method fetches info about movies from an API.
useIntersectionObserver is a custom hook that helps to check if ref={loadingRef} appears in the screen and more movies should be fetched.
How can I solve it?
Thanks!
I do not understand the problem. I am trying to make an app where the user is given a list of reviews and when they click on one, they are redirected to a page that shows details of that single review. Here is my ReviewCard.js file:
import React from 'react';
import { reviews } from '../data';
import StarRatings from 'react-star-ratings';
import './Review.css';
const ReviewCard= ({ review }) => {
return (
<div class="card-deck">
{reviews.map((review) => {
return (
<div class="card">
<div key={review.id}>
<h4 class="card-title">{review.place}</h4>
<StarRatings
rating={review.rating}
starRatedColor="gold"
starDimension="20px"
/>
<div class="card-body">{review.content}</div>
<div class="card-footer">{review.author} - {review.published_at}</div>
</div>
</div>
);
})}
</div>
);
};
export default ReviewCard;
and my ReviewCollection.js file:
import React from 'react';
import ReviewCard from './ReviewCard';
class ReviewCollection extends React.Component {
goToDetails = (review) => {
localStorage.setReview('selectedReview', review);
this.props.history.push('/details');
};
render() {
return (
<div onClick={() => this.goToDetails(review)}>
<div className='card-collection'>
{this.props.reviews.data
.filter((review, idx) => idx < 24)
.map((review) => (
<ReviewCard key={review.id} review={review}
/>
))}
</div>
</div>
)
}
}
export default ReviewCollection;
I am receiving an error from the Review Collection component saying that "Review" is not defined. I do not understand why this is happening.
<div onClick={() => this.goToDetails(review)}> Here, you are sending review but its not defined anywhere.
I think you need to include your onClick function inside the .map() function
{this.props.reviews.data
.filter((review, idx) => idx < 24)
.map((review) => (
<div onClick={() => this.goToDetails(review)}>
<ReviewCard key={review.id} review={review} />
</div>
))}
import React from 'react';
import ReviewCard from './ReviewCard';
import { reviews } from '../data';
import {reactLocalStorage} from 'reactjs-localstorage';
import { browserHistory } from 'react-router';
class ReviewCollection extends React.Component {
goToDetails = (review) => {
reactLocalStorage.set('selectedReview', review);
browserHistory.push('/details');
};
render() {
return (
<div className='card-collection'>
{reviews
.filter((review, idx) => idx < 24)
.map((review) => (
<div onClick={() => this.goToDetails(review)}>
<ReviewCard key={review.id} review={review} />
</div>
))}
</div>
)
}
}
export default ReviewCollection;
I am in the progress of learning React. I want to show countries' information via the toggleable button. But I have some problem with that.
There is an input box that is triggered by entering letters. I send HTTP Get Request depends on this input and the response is being filtered. The value which is filtered appears on the screen.
Ass you see, I just want the country name and button to appear. After that, when I press the button, only information about that country should come.
My code:
App.js
import React from 'react'
import Countries from './components/Countries'
const App = () => {
return (
<div>
<Countries />
</div>
)
}
export default App
Countries.js
import React, { useState,useEffect} from 'react'
import ShowSection from './ShowSection'
import axios from 'axios'
const Countries = (props) => {
const [search,setSearch] = useState('')
const [countries,setCountries] = useState([])
useEffect(()=> {
axios
.get('https://restcountries.eu/rest/v2/all')
.then((response) => {
console.log("Burda")
const responseCountries = response.data
const filter = responseCountries.filter(el =>
el.name.toLowerCase()
.indexOf(search.toLocaleLowerCase()) > -1)
setCountries(filter)
})
},[search])
const handleInput = (event) => {
setSearch(event.target.value)
console.log(countries)
}
return(
<div>
find countries <input onChange={handleInput}/>
<div>
<ShowSection list={countries}/>
</div>
</div>
)
}
export default Countries
ShowSection.js
import React from 'react'
import InfoSection from './InfoSection'
const ShowSection = (props) => {
const {list} = props
var id = 0;
if(list.length === 1){
return(
<div>
{
list.map((item,index) =>
<div>
<h2>{item.name}</h2>
<p>capital {item.capital}</p>
<p>population {item.population}</p>
<h3>languages</h3>
<ul>
{item.languages.map(m =>
<li key={index.toString()}>{m.name}</li>)}
</ul>
<img alt="Flag" src={item.flag} width="150px" height="150px"/>
</div>
)
}
</div>
)
}
else if(list.length <= 10){
return(
list.map((item,i) =>
<div>
<InfoSection key={item.id} item={item} num={++id}/>
</div>
)
)
}
else{
return(
<div>Nothing to rendered</div>
)
}
}
export default ShowSection
InfoSection.js
import React,{useState} from 'react'
const InfoSection = (props) => {
const {item} = props
const [toggle,setToggle] = useState(false)
return(
<div>
{item.name}
<button onClick={() =>setToggle(!toggle)}>
{toggle ? 'Cancel' : 'Show'}
</button>
<p>capital {item.capital}</p>
<p>population {item.population}</p>
<h3>languages</h3>
<ul>
{item.languages.map(m =>
<li key={item.callingCodes}>{m.name}</li>)}
</ul>
<img alt="Flag" src={item.flag} width="150px" height="150px"/>
</div>
)
}
export default InfoSection
Like #GG mentioned in the comments, you can use conditional rendering to display the details of the country when toggle is true/false.
Like this
return(
<div>
{item.name}
<button onClick={() =>setToggle(!toggle)}>
{toggle ? 'Cancel' : 'Show'}
</button>
{toggle &&
<>
<p>capital {item.capital}</p>
<p>population {item.population}</p>
<h3>languages</h3>
<ul>
{item.languages.map(m =>
<li key={item.callingCodes}>{m.name}</li>)}
</ul>
<img alt="Flag" src={item.flag} width="150px" height="150px"/>
</>
}
</div>
)
It reads:
"Error: App(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null."
I have seen other thread with about this particular issue with JS React but the solutions didn't work for me. I was making a shopping cart app with React and it was working fine when everything was on one JSX page but when I start making files for every every compartment for example, "Products, "Cart". . I am aware others have had this problem but I believe there are different ways you can get this error message.
Index.js:
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
App.js
const PAGE_PRODUCTS = 'products';
const PAGE_CART = 'cart';
function App() {
const [cart, setCart] = useState([]);
const [page, setPage] = useState(PAGE_PRODUCTS);
const addToCart = (product) =>{
console.log('we are in fart i mean cart');
setCart([...cart, {...product}]);
const removeFromCart = (productToRemove) =>{
setCart(
cart.filter(product => product !== productToRemove ));
};
const navigateTo = (nextPage) => {setPage(nextPage);
};
const renderCart = () => (
<>
<h1>Cart</h1>
<div className="products">
{cart.map ((product, idx) => (
<div className="product" key={idx}>
<h3>{product.name}</h3>
<h4>{product.cost}</h4>
<img src={product.image} alt={product.name}/>
<button onClick = {() => removeFromCart(product)}>Remove</button>
</div>
))}
</div>
</>
);
return (
<div className="App">
<header>
<button onClick={() => navigateTo(PAGE_CART)}>Go to Cart ({cart.length})</button>
<button onClick={() => navigateTo(PAGE_PRODUCTS)}>View Products </button>
</header>
{page === PAGE_PRODUCTS && (
<Products addToCart={addToCart} />
)}
{page === PAGE_CART && renderCart()}
</div>
);
};
}
export default App;
Products.jsx
import React, { useState } from 'react';
export default function Products({ addToCart }){
const [products] = useState([
{
name: 'TWA FUCK 12 T-SHIRT',
cost: '$19.99',
image: 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/ba6f3a5b-075f-4fae-9efd-dd797e00931a/ddya15n-0c2ea56a-4735-470c-bee9-41dd09f9dfb9.png/v1/fill/w_250,h_250,strp/blue_lives_splatter_by_0r4lf1x4t10n_ddya15n-250t.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD0xMjgwIiwicGF0aCI6IlwvZlwvYmE2ZjNhNWItMDc1Zi00ZmFlLTllZmQtZGQ3OTdlMDA5MzFhXC9kZHlhMTVuLTBjMmVhNTZhLTQ3MzUtNDcwYy1iZWU5LTQxZGQwOWY5ZGZiOS5wbmciLCJ3aWR0aCI6Ijw9MTI4MCJ9XV0sImF1ZCI6WyJ1cm46c2VydmljZTppbWFnZS5vcGVyYXRpb25zIl19.bhFc4MR_BfROHMtp2C6Nl2GaQ1PeJS2piOioT3tyRgc'
},
{
name: 'TWA THE WORLD IS YOURS T-SHIRT',
cost: '$19.99',
image: 'https://ih1.redbubble.net/image.974235379.7506/poster,504x498,f8f8f8-pad,600x600,f8f8f8.jpg'
}
]);
return (
<>
<h1>Products</h1>
<div className="products">
{products.map ((product, idx) => (
<div className="product" key={idx}>
<h3>{product.name}</h3>
<h4>{product.cost}</h4>
<img src={product.image} alt={product.name}/>
<button onClick = {() => addToCart(product)}>Add To Cart</button>
</div>
))}
</div>
</>
);
}