The following code shows a list of 10 users (list-view) and if you click on Details button of any of those users, it shows only that particular user (user-view).
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me/?results=10'
)
console.log(response.data.results)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
return (
<ul className='card__wrapper'>
{resources.filter(user => (id) ? user.login.uuid === id : true)
.map(item => (
<li className='card' key={item.name.first}>
<div className='card__item'>
<img className='card__image' src={item.picture.large} alt={item.name.first} />
<h2 className='card__title'>{item.name.first} {item.name.last}</h2>
{
id
?
<button
className='card__cta'
onClick={() => setID(null)}
>
Back to overview
</button>
:
<button
className='card__cta'
onClick={() => setID(item.login.uuid)}
>
Details
</button>
}
</div>
</li>
))}
</ul>
)
}
export default UserList
While this is working fine, the code inside the return which builds both the list-view and also the user-view is a bit difficult to understand (at least for me) and also makes it hard for using different CSS classes for List- and User-view.
I'd like to simplify the code so that's easier to understand by splitting it to two different returns.
Basically, saying that if the condition is true, return the user-view otherwise the list-view
How can I do that?
I would put the rendering stuff into another function, and to make what is going to be clearer I would use two returns:
import React, { useState, useEffect } from "react";
import axios from "axios";
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([]);
const fetchResource = async () => {
const response = await axios.get("https://api.randomuser.me/?results=10");
console.log(response.data.results);
setResources(response.data.results);
};
useEffect(() => {
fetchResource();
}, []);
const renderItem = (item, isLoggedIn) => {
return (
<li className="card" key={item.name.first}>
<div className="card__item">
<img className="card__image" src={item.picture.large} alt={item.name.first} />
<h2 className="card__title">
{item.name.first} {item.name.last}
</h2>
{isLoggedIn ? (
<button className="card__cta" onClick={() => setID(null)}>
Back to overview
</button>
) : (
<button className="card__cta" onClick={() => setID(item.login.uuid)}>
Details
</button>
)}
</div>
</li>
);
};
const user = resources.find(user => user.login.uuid === id);
if (user) {
return <ul className="card__wrapper">{renderItem(user, true)}</ul>;
} else {
return <ul className="card__wrapper">{resources.map(user => renderItem(user, false))}</ul>;
}
};
export default UserList;
Looks like the question asked pertains to this React hooks - OnClick show only the clicked item
Please find my comment for the above post, as I guess this particular issue can be solved as mentioned in the comment! In case it doesn't fix, let me know.
Related
What I want is when I click on:
let Afeef = `/${category}`
<Link to={Afeef} className='products-categories'> <h4>{category}</h4></Link>
It should change products according to URL which could be "/electronics","/jewelry" etc but the problem I am facing is that it is changing my URL but the products are not changing. I can't understand what is the problem here. I tried different things but I cant understand it.
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom';
import './Allproducts.css'
import Categories from './categories.json'
import ForYouItem from './ForYouItem'
export default function Allproducts(props) {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch(`https://fakestoreapi.com/products/category/${props.category}`)
.then((res) => res.json())
.then((data) => setProducts(data))
}, [])
const [categories, setCategories] = useState([])
const updateCategory = async ()=> {
const url = "./categories.json"
let data = await fetch(url);
let parsedData = await data.json()
setCategories(parsedData.title)
}
useEffect(() => {
updateCategory();
}, [])
return (
<>
<div className="banner">
<h1>Afeef</h1>
<h4>Get best items in budget</h4>
</div>
<div className="main-grid">
<div className="left-grid">
<div className="left-categories">
<h1>Collections</h1>
{categories.map((category) => {
let Afeef = `/${category}`
return (
<Link to={Afeef} className='products-categories'> <h4>{category}</h4></Link>
)
}
)}
</div>
</div>
<div className="right-grid">
<div className="row ">
{products.map((product) => {
return (
<div className="col-md-4 my-2 Products-1">
<ForYouItem Title={product.title.slice(0, 50)} Price={product.price} Imageurl={product.image} rate={product.rating.rate} count={product.rating.count} />
</div>
)
}
)}
</div>
</div>
</div>
</>
)
}
im gonna try to explain what i understand from your code. So based on the flow of your code, the product can only be fetch once when the page loaded.
but i think in your useEffect that fetch product, you can add the state of Categories in the bracket "[categories]".
then add an onclick setstate product in your link.
so when you click your link, the categories state is updated. then because the bracket inside useEffect that have [categories] is updated the useEffect is run. hence fething new product.
When I click on the Delete button, my code does not work. There could be a problem in the function handleRemove.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
// API endPoint - Punk API
const API_URL = 'https://api.punkapi.com/v2/beers'
const List = () => {
const [drinks, setDrinks] = useState([])
const [searchTerm, setSearchTerm] = useState('')
const fetchData = async () => {
const { data } = await axios.get(API_URL)
setDrinks(data)
}
useEffect(() => {
fetchData()
}, [])
const handleRemove = (id) => {
let groupd = drinks
const newList = groupd.filter(group => group.id !== id)
setDrinks(newList)
}
return (
<div>
<div className="wrapper">
<div className="search__main">
<input type='text' placeholder="search..." onChange={e => {setSearchTerm(e.target.value)}}/>
</div>
</div>
<div className="wrapper">
<div className="search__box">
{drinks.filter((val) => {
if(searchTerm === ""){
return val
} else if(val.name.toLowerCase().includes(searchTerm.toLowerCase()) || val.description.toLowerCase().includes(searchTerm.toLowerCase())){
return val
}
}).map((drink, key) => {
return(
<div key={key} className="search__mini__box">
<div >
<img src={drink.image_url} alt="drink" className="search__img"/>
</div>
<h4>{drink.name}</h4>
<p>{drink.description}</p>
<button type="button" onClick={handleRemove(drink.id)}>
delete
</button>
</div>
)
})}
</div>
</div>
</div>
)
}
export default List
Since your handleRemove function call is within a return statement, you need to call the function like so:
onClick={() => handleRemove(drink.id)}
What happens is, the function is called immediately on render if done the way you've proposed in your question. We want the function to be called only when the button is clicked.
import React from 'react'
import { useState, useEffect } from 'react'
import axios from 'axios'
const Home = () => {
const getSongs = () => {
axios.get('http://localhost:8000/api/songs/')
.then(res => setSongs(res.data))
}
let [songs, setSongs] = useState([])
let [paused, setPause] = useState(true)
useEffect(() => {
getSongs()
}, [])
const toggleSong = (id) => {
const x = document.getElementById(id)
if (x.paused){
x.play()
setPause(false)
} else {
x.pause()
setPause(true)
}
}
// Got rid of the functions that are not needed
return (
<>
{
songs.map(song =>
(
<div className='music-controller' key={song.id}>
<div id={'songDiv'} style={{cursor: 'pointer'}} onClick={(e) => changeSongTime(e, song.id)}>
<div id={`songTime-${song.id}`}></div>
</div>
<div className="music-controller-body">
<div className="music-controller-header">
<h2>{song.title}</h2>
<p><small>{song.genre}</small></p>
</div>
<div className="controls">
// here <----------------------
<i unique={song.id} className={`fas fa-${paused ? 'play' : 'pause'}`} onClick={() => toggleSong(song.id)}></i>
<audio id={song.id} onTimeUpdate={() => songTime(song.id)}>
<source src={`http://localhost:8000/api/songs/audio/${song.id}`} />
</audio>
</div>
</div>
</div>
))}
</>
)
}
export default Home
Whenever I click on a specific i element all of the i elements that were not clicked on get changed too.. to put it simply when I click on the 1st i element only its className should change, but all of the i elements classNames are affected, what is causing this?
I think you should use event.target
const handlePlay = (song) => {
song.play();
};
const handlePause = (song) => {
song.pause();
};
...
<div className="controls">
<i
onMouseOver={(e) => handlePlay(e.target)}
onMouseLeave={(e) => handlePause(e.target)}
className={`fas fa-${paused ? 'play' : 'pause'}`}
onClick={() => toggleSong(song.id)}>
</i>
<audio id={song.id} onTimeUpdate={() => songTime(song.id)}>
<source src={`http://localhost:8000/api/songs/audio/${song.id}`} />
</audio>
</div>
I don't think Toggle would work in this case, an action should happen so it knows when it should stop.
Can you put console in toggleSong function at top and check if you are getting correct id. If you are not getting single Id then work is needed with onClick. So, after that also try passing id like this
onClick={(song?.id) => toggleSong(song?.id)}
then see console again and look for correct id if it is displayed or not. I think your className is not updating due to this issue.
One thing more you can try at end is replacing with this
const x = id; //without document.getElementById
const toggleSong = (e, id) => {
const x = document.getElementById(id)
const button = e.currentTarget
if (x.paused){
x.play()
button.className = 'fas fa-pause'
} else {
x.pause()
button.className = 'fas fa-play'
}
}
<i unique={song.id} className='fas fa-play' onClick={(e) => toggleSong(e, song.id)}></i>
I fixed this by just getting the current target with event.currentTarget and change its className accordingly!
What would be the best way to handle errors and display them in a React App using Hooks, at the moment if I try to break the app by mistyping the URL it shows the error but still the data sometimes also, however if I update the state to an empty array in the catch block setData([]);, then it works fine, I just wanted to check and see if this is the ideal way or is there another way?
App.js
import React, {useEffect} from 'react';
import './App.css';
import axios from 'axios';
const App = () => {
interface DataHolder {
userId: string;
id: string;
title: string;
body: string;
}
const [data, setData] = React.useState<DataHolder[]>([]);
const [isLoading, setIsLoading] = React.useState<Boolean>(false);
const [hasError, setHasError] = React.useState<Boolean>(false)
useEffect( () => {
const fetchData = async (): Promise<any> => {
setIsLoading(true);
setHasError(false);
try {
const result = await axios('https://jsonplaceholder.typicode.com/posts');
setData(result.data);
} catch (err) {
setHasError(true);
setData([]);
console.log(err);
}
setIsLoading(false);
}
fetchData()
return () => {
console.log('cleaned');
}
}, [setData]);
return (
<>
{hasError && <p >Something went wrong. problem with the data feed.</p>}
{isLoading ? (
<p >Loading ...</p>
) : (
<ul>
{data.map(item => (
<li key={item.id} >
<p>{item.title}</p>
</li>
))}
</ul>
)}
</>
);
}
export default App;
Conditional rendering should help when you are dealing with hooks.
Loading
Error
Data display part
You can order like this.
if (isLoading) {
return <p>Loading ...</p>;
}
if (hasError) {
return <p>Something went wrong. problem with the data feed.</p>;
}
return (
<>
<ul>
{data?.map((item) => (
<li key={item.id}>
<p>{item.title}</p>
</li>
))}
</ul>
</>
);
You can also give a condition like this, checking the length of the data
return (
<>
<ul>
{data.length > 0 ? data.map((item) => (
<li key={item.id}>
<p>{item.title}</p>
</li>
)) : null}
</ul>
</>
);
I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!