I have the following:
const useItemsF = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const user = firebase.auth().currentUser;
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("user")
.where("id", "==", `${user.uid}`)
.onSnapshot(snapshot => {
const listItemsUsers = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setItems(listItemsUsers);
setLoading(false);
});
console.log(loading);
return () => unsubscribe();
}, []);
return items;
};
I then use the following in my return:
const ItemList = () => {
const listItemF = useItemsF();
}
return (
<>
<div className="wrapper">
<h1><FontAwesomeIcon icon={faTachometer} size="lg" /></h1>
<Card elevation={3} className={classes.card}>
<CardContent className="cardoverride">
{listItemF?.length && listItemF[0]?.userProfilePic?.userPicURL !== 'undefined' ? <img className="dashimage" src={listItemF[0]?.userProfilePic?.userPicURL} /> : (
<img className="dashimage" src="/Image.jpg" />
)}
</CardContent>
</Card>
</div>
</>
);
export default App;
This works fine, but what I end up seeing in Image.jpg before the userProfilePic. How can I restructure this so you don't see the Image.jpg at all? So if the user has set a Profile Pic, this shows instantly (or at least does not show the Image.jpg)
so what is happening is, the component is rendering first (at this time, items.userProfilePik doesnt exist, so the /Image.jpg is rendered). Once the comp is done rendering, then your useEffect fires, where your firebase stuff is happening, which i'm guessing fetches the profilePik from the database and sets state using setItems. Only now, will your items.userProfilePik render.
One way to solve this problem is initiate another state as such:
const [loading, setLoading] = useState(true)
//then in your useEffect
//...
setItems(listItemsUsers);
setLoading(false); //add this line
//...
//then in your return statement do something like:
return (
<>
<div className="wrapper">
<h1><FontAwesomeIcon icon={faTachometer} size="lg" /></h1>
<Card elevation={3} className={classes.card}>
<CardContent className="cardoverride">
{ loading
? <div>Loading...</div>
: listItemF?.length && listItemF[0]?.userProfilePic?.userPicURL !== undefined
? <img className="dashimage" src={listItemF[0]?.userProfilePic?.userPicURL} />
: <img className="dashimage" src="/Image.jpg" /> }
</CardContent>
</Card>
</div>
</>
);
Related
When I click on the search button the first time API response is "undefined" (based on what console.log says) but the second time it has the response from API.
Why does this happen?
xport default function Home() {
const [searchTerm, setSearchTerm] = useState('');
const fetcher = (url) => fetch(url).then((res) => res.json());
const [shouldFetch, setShouldFetch] = useState(false);
const { data, error } = useSWR(
() =>
shouldFetch
? `https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=${searchTerm}`
: null,
fetcher
);
if (error) return 'An error has occurred.';
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<button
onClick={() => {
setShouldFetch(true);
console.log(searchTerm);
console.log(data);
}}
>
Search!
</button>
</div>
</main>
</>
);
}
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());
So here is my function code:
function Detail() {
const { id } = useParams();
const [movie, setMovie] = useState();
useEffect(() => {
// Grab the movie info from db
db.collection("movies")
.doc(id)
.get()
.then((doc) => {
if (doc.exists) {
// save the movie to the state
setMovie(doc.data());
} else {
// redirect to home page
console.log("no such document in firebase 🔥");
}
});
}, [id]);
return (
<Container>
<Background>
<img alt=" background" src={movie.backgroundImg} />
</Background>
<ImageTitle>
<img alt="title" src={movie.titleImg} />
</ImageTitle>
<SubTitle>{movie.subtitle}</SubTitle>
<Description>{movie.description}</Description>
</Container>
);
}
Everything should be good here, but when I run the app, I get TypeError: cannot read property 'backgroundImg' of undefined
After spending a good few hours debugging and researching, I found out that the issue is, the function looking for the data before it had time to get it from the database.
What is the solution here? I know about the async function but never used it before.
UseEffect is executed after render and when value id value changes
Note=> you need to render only if movie exists
function Detail() {
const { id } = useParams();
const [movie, setMovie] = useState();
useEffect(() => {
// Grab the movie info from db
const unsubscribe=db.collection("movies")
.doc(id)
.get()
.then((doc) => {
if (doc.exists) {
// save the movie to the state
setMovie(doc.data());
} else {
// redirect to home page
console.log("no such document in firebase 🔥");
}
});
return ()=>{
//clean up function to unsubscribe to firestore changes
unsubscribe();
}
}, [id]);
return (
<Container>
//render only if movie exists
{
movie ? <div>
<Background>
<img alt=" background" src={movie.backgroundImg} />
</Background>
<ImageTitle>
<img alt="title" src={movie.titleImg} />
</ImageTitle>
<SubTitle>{movie.subtitle}</SubTitle>
<Description>{movie.description}</Description>
</div>
: <p>...loading</p>
}
</Container>
);
}
you can use conditional rendering in react and render different component based on movie
return (
<Container>
{movie?
<React.Fragment>
<Background>
<img alt=" background" src={movie.backgroundImg} />
</Background>
<ImageTitle>
<img alt="title" src={movie.titleImg} />
</ImageTitle>
<SubTitle>{movie.subtitle}</SubTitle>
<Description>{movie.description}</Description>
</React.Fragment>
: <p>loading from firebase</p>}
</Container>
);
You might want some loading state that controls wether the content is available or not, this is the refactored code:
ps: If it doesn't work, check your logs so we can see the error
function Detail() {
const { id } = useParams();
const [loading, setLoading] = useState(false)
const [movie, setMovie] = useState();
useEffect(() => {
setLoading(true)
async function getMovie(id) {
try {
const doc = await db.collection("movies")
.doc(id)
.get()
if (doc.exists()) {
setMovie(doc)
}
}
catch (err) {
console.log(err)
}
}
getMovie(id)
setLoading(false)
}, []);
return (
<Container>
{!loading && (
<>
<Background>
<img alt=" background" src={movie.backgroundImg} />
</Background>
<ImageTitle>
<img alt="title" src={movie.titleImg} />
</ImageTitle>
<SubTitle>{movie.subtitle}</SubTitle>
<Description>{movie.description}</Description>
</>
)}
</Container>
);
}
In React never assume that a request will be processed and the information will exist or React would render before the request is finished being processed. So you always want to use a && operator
function Detail() {
const { id } = useParams();
const [movie, setMovie] = useState();
useEffect(() => {
// Grab the movie info from db
db.collection("movies")
.doc(id)
.get()
.then((doc) => {
if (doc.exists) {
// save the movie to the state
setMovie(doc.data());
} else {
// redirect to home page
console.log("no such document in firebase 🔥");
}
});
}, [id]);
return (
{movie &&
<Container>
<Background>
<img alt=" background" src={movie.backgroundImg} />
</Background>
<ImageTitle>
<img alt="title" src={movie.titleImg} />
</ImageTitle>
<SubTitle>{movie.subtitle}</SubTitle>
<Description>{movie.description}</Description>
</Container>}
);
}
I've created a static React website and hosted it on Github pages.
As you can see, whenever you click on a film or a TV series, a modal will appear with the film/tv poster(loaded from the OMDb API) and some metadata. The problem is that the content loads too slowly. It takes a second(sometimes more) before before the content appears.
I get that I can't expect it to load that much faster, but I would like to not show the modal at all before everything looks nice(i.e is perfectly loaded). Perhaps by having a "Loading.." appear while we wait. It doesn't have to be anything fancy, as it's only gonna be on the screen for 1-2 seconds at most.
Do you have any advice for a React beginner?
Relevant code:
function ImdbInfo(props) {
const [data, setData] = useState({ imdbData: [] });
useEffect(() => {
const imdbId = getImdbId(props.url);
const fetchData = async () => {
const result = await axios(
`https://www.omdbapi.com/?i=${imdbId}&apiKey=${apiKey}`,
);
setData(result.data);
};
fetchData();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div className="modal-content">
<div className="metadata" onClick={props.handleClose}>
<h1 className="modal-header">{data.Title}</h1>
<img className="modal-poster" src={data.Poster} alt="poster" />
<p className="modal-info">{getDirectorOrWriter(data)}</p>
<p className="modal-info">IMDb Rating: {getImdbScore(data.Ratings)}</p>
</div>
{createImdbLink(props.url)}
</div>
);
}
And:
const MediaModal = ({ handleClose, show, data }) => {
const showHideClassName = show ? 'modal display-block' : 'modal display-none';
const imdbData = show ? <ImdbInfo url={data.imdbLink} handleClose={handleClose} /> : <div />;
return (
<div className={showHideClassName} onClick={handleClose}>
<section className='modal-main'>
{imdbData}
</section>
</div>
);
};
export default MediaModal;
Set loading to true before making the api call and
set loading false after api returns success/failure.
Check updated code below
function ImdbInfo(props) {
const [data, setData] = useState({ imdbData: [] });
const [loading, setLoading] = useState(false);
useEffect(() => {
const imdbId = getImdbId(props.url);
const fetchData = async () => {
setLoading(true);
try {
const result = await axios(
`https://www.omdbapi.com/?i=${imdbId}&apiKey=${apiKey}`
);
setData(result.data);
setLoading(false);
} catch (err) {
setLoading(false);
}
};
fetchData();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div>
{loading ? (
<div>Loading...</div>
) : (
<div className="modal-content">
<div className="metadata" onClick={props.handleClose}>
<h1 className="modal-header">{data.Title}</h1>
<img className="modal-poster" src={data.Poster} alt="poster" />
<p className="modal-info">{getDirectorOrWriter(data)}</p>
<p className="modal-info">
IMDb Rating: {getImdbScore(data.Ratings)}
</p>
</div>
{createImdbLink(props.url)}
</div>
)}
</div>
);
}
If I understand your code correctly, you create a MediaModal component, that has a child component ImdbInfo, where you fetch some data. I guess this MediaModal has a parent component where you toggle and use your modal: you didn't provide the code, but let's call it MainComponent
Instead of fetching data inside ImdbInfo you could fetch them in MainComponent and pass the result as props:
Inside MainComponent:
// Onclick to toggle modal:
// - fetch data just like you did in ImdbInfo
// - then set show=true
// Then use your modal
<MediaModal handleClose={plop} show={plip} infos={fetchedData} url={imdbLink} />
MediaModal:
const MediaModal = ({ handleClose, show, infos, url}) => {
const showHideClassName = show ? 'modal display-block' : 'modal display-none';
const imdbData = show ? <ImdbInfo infos={infos} url={url} handleClose={handleClose} /> : <div />;
return (
<div className={showHideClassName} onClick={handleClose}>
<section className='modal-main'>
{imdbData}
</section>
</div>
);
};
export default MediaModal;
ImdbInfo:
function ImdbInfo({infos, handleClose}) {
return (
<div className="modal-content">
<div className="metadata" onClick={handleClose}>
<h1 className="modal-header">{infos.Title}</h1>
<img className="modal-poster" src={infos.Poster} alt="poster" />
<p className="modal-info">{getDirectorOrWriter(infos)}</p>
<p className="modal-info">IMDb Rating: {getImdbScore(infos.Ratings)}</p>
</div>
{createImdbLink(url)}
</div>
);
}
I have 2 onClick functions
function VisitGallery(name) {
const history = useHistory();
console.log("visitgallery", name)
history.push("/gallery")
}
function App() {
const accesstoken = "******************"
const [viewport, setviewport] = React.useState({
latitude: ******
longitude: *******
width: "100vw",
height: "100vh",
zoom: 11
})
const [details, setdetails] = React.useState([
])
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore()
const data = await db.collection("data").get()
setdetails(data.docs.map(doc => doc.data()))
}
fetchData();
}, [])
const [selectedpark, useselectedpark] = React.useState(null);
React.useEffect(() => {
const listener = e => {
if (e.key === "Escape") {
useselectedpark(null);
}
};
window.addEventListener("keydown", listener)
return () => {
window.removeEventListener("keydown", listener)
}
}, [])
return (
<div className="App">
<ReactMapGl {...viewport}
mapboxApiAccessToken={accesstoken}
mapStyle="mapbox://**************"
onViewportChange={viewport => {
setviewport(viewport)
}}>
{details.map((details) =>
<Marker key={details.name} latitude={details.lat} longitude={details.long}>
<button class="marker-btn" onClick={(e) => {
e.preventDefault();
useselectedpark(details);
}}>
<img src={icon} alt="icon" className="navbar-brand" />
</button>
</Marker>
)}
{selectedpark ?
(<Popup
latitude={selectedpark.lat}
longitude={selectedpark.long}
onClose={() => {
useselectedpark(null);
}}
>
<div>
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{selectedpark.name}</Card.Title>
<Card.Text>
{selectedpark.postalcode}
</Card.Text>
<Button variant="primary" onClick = VisitGallery() >Visit Gallery</Button>
</Card.Body>
</Card>
</div>
</Popup>)
: null}
{
console.log("in render", details)
}
</ReactMapGl>
</div>
);
}
export default App;
The outer onClick is assigned when the marker is first created, and when it is clicked the useselectedpark function is called, details is then assigned to selectedpark.
The inner onClick is assigned to the function VisitGallery(). When the inner onClick is triggered, i want to navigate to another page, hence the history.push().
Ideally, what i want for it to happen is, when the outer onClick is triggered, the cardview shows, and i have an option to visit the next page, which can be triggered by an onClick within the card. However, what is happening right now is both the onClicks are triggered when i click on the thumbnail. How do i fix it such that it is how i want it to be ideally?
ps: do let me know if my explanation is confusing and i will edit it accordingly
Try adding your second onClick into a callback function?
<Button variant="primary" onClick='()=>{ VisitGallery() }' >Visit Gallery</Button>
So that it doesn't automatically invoke the function until the click is triggered.