I have a div(card) as below. When I click on this card, a detail page opens and when I come back to this page, I want the jackson text to be grayed out. Makes handleClick detail page open.
like this
export default function ListComponent({ handleClick }) {
const [data, setData] = useState([]);
const [isSelected, setIsSelected] = useState(false);
useEffect(() => {
getMailbox()
.then((response) => {
if (response?.success) {
setData(response?.data?.mails);
}
})
.catch((err) => {
console.log("err", err);
});
}, []);
return (
<div className={styles.messageBoxContainer}>
{data.map((item, index) => (
<div
key={`item-container-${index}`}
className={styles.mailItemContainer}
onClick={() => {
handleClick(item);
}}>
<div className={styles.mailOwner}>
<p className={isSelected ? styles.userEmailTextSelected : styles.userEmailText}>{item?.senderName}</p>
</div>
</div>
))}
</div>
);
}
You can add a return field to your useEffect hook so that when the element is unmounted its color change.
useEffect(() => {
getMailbox()
//...
return () => {
document.querySelector('#yourElementID').style.color = 'gray'
}
}, []);
Related
function App() {
const [data, setData] = useState([]);
useEffect(() => {
getPic();
}, []);
const getPic = useCallback(async () => {
try {
const pic = await axios({
url: "https://api.thecatapi.com/v1/images/search",
});
const picUrl = pic.data[0];
if (picUrl) {
setData((prevData) => [...prevData, picUrl]);
}
} catch (error) {
console.log(`error = ${error}`);
}
}, []);
return (
<div className="App">
<p>test</p>
<div>
{data.map((d) => (
<div key={d.id}>
<S.CatImg src={d.url} alt="" />
</div>
))}
</div>
</div>
);
}
export default App;
I am practicing implementing infinite scrolling.
I think the getPic function should be executed only once because nothing is added to UseEffect's dependencies array.
But I don't know why the first render shows 2 or more pictures of cats.
Please help.
I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());
I am trying to organize my code order to handle feed as feed.* based on my endpoint API, but however react doesn't allow me to directly send functions into component, but I want something similar to feed.results, feed. count
const [initialized, setIntialized] = useState(false);
const [feed, setFeed] = useState([]);
const browserFeed = async () => {
const response = await browse();
setFeed(response.results);
setIntialized(true);
};
useEffect(() => {
if (!initialized) {
browserFeed();
}
});
export const browse = () => {
return api.get('xxxxxxxx')
.then(function(response){
return response.data // returns .count , .next, .previous, and .results
})
.catch(function(error){
console.log(error);
});
}
<div className="searched-jobs">
<div className="searched-bar">
<div className="searched-show">Showing {feed.count}</div>
<div className="searched-sort">Sort by: <span className="post-time">Newest Post </span><span className="menu-icon">▼</span></div>
</div>
<div className="job-overview">
<div className="job-overview-cards">
<FeedsList feeds={feed} />
<div class="job-card-buttons">
<button class="search-buttons card-buttons-msg">Back</button>
<button class="search-buttons card-buttons">Next</button>
</div>
</div>
</div>
</div>
If it is pagination you are trying to handle here is one solution:
async function fetchFeed(page) {
return api.get(`https://example.com/feed?page=${page}`);
}
const MyComponent = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
// Fetch on first render
useEffect(() => {
fetchFeed(1).then((data) => setFeed(data));
}, []);
// Update feed if the user changes the page
useEffect(() => {
fetchFeed(currentPage).then((data) => setFeed(data));
}, [currentPage]);
const isFirstPage = currentPage === 1;
return (
<>
<FeedsList feeds={feed} />
{isFirstPage && (
<button onClick={() => setCurrentPage(currentPage - 1)}>Back</button>
)}
<button Click={() => setCurrentPage(currentPage + 1)}>Next</button>
</>
);
};
I have a Dashboard component that is fetching all city data from an API and store it in the cities state.
Now I want that when a city name is clicked a new page opens having all the props of the clicked city.
function Dashboard() {
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
// Here I want to show a detail page of the clicked item //
// <DetailsPage city={e} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you like to show the details under the selected city you can keep it in your component state and render it conditionally:
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
{selectedCity === city ? <DetailsPage city={city} /> : null}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want to only show the selected city content (with probably a back button):
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
if (selectedCity) {
return <DetailsPage city={e} onBack={() => setSelectedCity(null)} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want a separate page with a different URL, it will be more complex than this.
You need to use a router like react-router.
const handleClick = (e) => {
history.push("/city", { id: e.id });
}
You have to read the data on both pages. So you may need to put your cities and the selected city values in a React Context so that you can use it on the details page. Alternatively, you can fetch the data on the parent component and move these states to it, so that you can pass the values to both pages.
If you fetch data on the Dashboard page, you should also handle the scenario in which a user refreshes the details page or enters the URL directly. You may need a different API to fetch a city by its ID. Alternatively, you can simply redirect to the Dashboard page if you are on the details page and you don't have the required data.
I have to create component which fetch data with pagination and filters.
Filters are passed by props and if they changed, component should reset data and fetch it from page 0.
I have this:
const PaginationComponent = ({minPrice, maxPrice}) => {
const[page, setPage] = useState(null);
const[items, setItems] = useState([]);
const fetchMore = useCallback(() => {
setPage(prevState => prevState + 1);
}, []);
useEffect(() => {
if (page === null) {
setPage(0);
setItems([]);
} else {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
}, [page, minPrice, maxPrice]);
useEffect(() => {
setPage(null);
},[minPrice, maxPrice]);
};
.. and there is a problem, because first useEffect depends on props, because I use them to filtering data and in second one I want to reset component. And as a result after changing props both useEffects run.
I don't have more ideas how to do it correctly.
In general the key here is to move page state up to the parent component and change the page to 0 whenever you change your filters. You can do it either with useState, or with useReducer.
The reason why it works with useState (i.e. there's only one rerender) is because React batches state changes in event handlers, if it didn't, you'd still end up with two API calls. CodeSandbox
const PaginationComponent = ({ page, minPrice, maxPrice, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [page, minPrice, maxPrice]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const App = () => {
const [page, setPage] = useState(0);
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<PaginationComponent minPrice={minPrice} maxPrice={maxPrice} page={page} setPage={setPage} />
</div>
);
};
export default App;
The other solution is to use useReducer, which is more transparent, but also, as usual with reducers, a bit heavy on the boilerplate. This example behaves a bit differently, because there is a "set filters" button that makes the change to the state that is passed to the pagination component, a bit more "real life" scenario IMO. CodeSandbox
const PaginationComponent = ({ tableConfig, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
const { page, minPrice, maxPrice } = tableConfig;
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [tableConfig]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {tableConfig.page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const tableStateReducer = (state, action) => {
if (action.type === "setPage") {
return { ...state, page: action.page };
}
if (action.type === "setFilters") {
return { page: 0, minPrice: action.minPrice, maxPrice: action.maxPrice };
}
return state;
};
const App = () => {
const [tableState, dispatch] = useReducer(tableStateReducer, {
page: 0,
minPrice: 25,
maxPrice: 50
});
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
const setPage = useCallback(
page => {
if (typeof page === "function") {
dispatch({ type: "setPage", page: page(tableState.page) });
} else {
dispatch({ type: "setPage", page });
}
},
[tableState]
);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
}}
/>
</div>
<button
onClick={() => {
dispatch({ type: "setFilters", minPrice, maxPrice });
}}
>
Set filters
</button>
<PaginationComponent tableConfig={tableState} setPage={setPage} />
</div>
);
};
export default App;
You can use following
const fetchData = () => {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
// Whenever page updated fetch new data
useEffect(() => {
fetchData();
}, [page]);
// Whenever filter updated reseting page
useEffect(() => {
const prevPage = page;
setPage(0);
if(prevPage === 0 ) {
fetchData();
}
},[minPrice, maxPrice]);