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>}
);
}
Related
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>
</>
);
I have an input field plus a button next to it. I want to fetch data whenever the client presses the button.
I used SWR (I'm not sure if I have to use SWR or getServerSideProps method)
the problem is it fetches data when I refresh the page.
here is my code:
const fetcher = async () => {
const res = await fetch(
'https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=dexter'
);
const data = await res.json();
return data;
};
const { data, error } = useSWR('uk', fetcher);
if (error) {
return 'there was an error';
}
if (!data) {
return 'Loading';
}
console.log(data);
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
console.log(searchTerm);
}}
/>
</div>
<button onClick={?????}>Search!</button>
</main>
</>
);
}
You should call mutate function that useSWR returns you.
const fetcher = async () => {
const res = await fetch(
'https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=dexter'
);
const data = await res.json();
return data;
};
const { data, mutate, error } = useSWR('uk', fetcher);
if (error) {
return 'there was an error';
}
if (!data) {
return 'Loading';
}
console.log(data);
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
console.log(searchTerm);
}}
/>
</div>
<button onClick={() => mutate()}>Search!</button>
</main>
</>
);
}
Check this link please https://swr.vercel.app/docs/mutation
You can do something like this:
import { useState } from 'react';
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const SearchResults = ({ keyword }) => {
const { data, error } = useSWR(
`https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=${keyword}`,
fetcher
);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
if (!data[0]) return <div>not found</div>;
return <div>found {data[0].name}</div>;
};
const Home = () => {
const [startFetching, setStartFetching] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const handleChange = (e) => {
setStartFetching(false);
setSearchTerm(e.target.value);
};
const handleClick = () => {
setStartFetching(true);
};
return (
<>
<label for="series">Series: </label>
<input type="text" value={searchTerm} onChange={handleChange} id="series" />{' '}
<input type="submit" value="Search" onClick={handleClick} />
<br />
{startFetching && <SearchResults keyword={searchTerm} />}
</>
);
};
export default Home;
Basically, create a separate component that handles fetching and displaying UI based on the fetched content. And, create a state in the parent that tells when to render that component (and start fetching).
Credits: https://github.com/vercel/swr/issues/254
I have this project where i want the user to type in the hotel based upon name (which i have fixed), and i also have three icons where the user clicks on it and then filters out all hotels, B&B or hospits. I am stuck and not sure how solve this.
Is there a way I can add the three icons and the filter out the different hotels etc?
function FetchHotels() {
const [apiData, setApiData] = useState([]);
const [loadData, setLoadData] = useState(true);
const [error, setError] = useState(null);
const [searchData, setSearchData] = useState('');
const [filteredHospits, setHospits] = useState([]);
function onSearch(e) {
e.preventDefault()
setSearchData(e.target.value)
}
function onFilteredHospits(e) {
e.preventDefault()
setHospits(filteredTypeOfHotels)
}
useEffect(function () {
async function getData() {
try {
const res = await fetch(hotelApi)
if (res.ok) {
const json = await res.json();
setApiData(json);
console.log(json)
} else {
setError("An error occurred");
console.log('error')
}
}
catch (error) {
setError(error.toString())
console.log(error)
}
finally {
setLoadData(false);
}
}
getData();
}, [])
if (loadData) {
return <Spinner animation="grow" />;
}
if (error) {
return <div>ERROR: An error occurred</div>;
}
const filteredHotels = apiData.filter(filteredHotel => {
return filteredHotel.Name.toLowerCase().includes(searchData.toLocaleLowerCase())
})
return (
<div>
<h1 className="heading__frontpage">Holidaze</h1>
<img
src={hero_front}
alt="image of fiskebryggen in bergen"
className="frontpage_heroimg"
/>
<div className="frontpage_search">
<input className="frontpage_input" type="search" placeholder="search hotels, B&B or hospits" onChange={onSearch} />
</div>
<h2 className="frontpage_icontxt">Click icons to filter</h2>
<div className="frontpage_flexcontainerimg">
<img
src={hotels}
alt="icons for hotels, click to search"
className="frontpage_icon"
/>
<img
src={bedandbreakfast}
alt="icons for bead and breakfast, click to search"
className="frontpage_icon"
/>
<img
src={hospits}
alt="icons for hospits, click to search"
className="frontpage_icon"
onClick={onFilteredHospits}
/>
</div>
{filteredHotels.map(hotel => {
const { id, Name, description, img } = hotel
return <div className="frontpage_flexcontainer" key={hotel.id}>
<img className="frontpage_img" src={`http://localhost:1337${hotel.frontpageimage.url}`} alt={`image of hotelroom from ${hotel.Name}`} />
<div className="frontpage_descheading">
<h2 className="frontpage_headings">{hotel.Name}</h2>
<p className="frontpage_desc">{hotel.description}</p>
</div>
<SingleHotel key={id} id={id} name={Name} description={description} images={img} />
</div>
})
}
</div>
)
};
export default FetchHotels
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've created my backend and it works. I tested different Axios requests in order to create a form.
In my React front project, I created a POST axios request, I console.log(response.data) and I got an object with the id, the title and questions.
I am stuck because I don't know how I could display the data of the object in my front.
Here is my front React code:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back-formnest-lereacteur.herokuapp.com/form/create",
{
title: "Your event",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
I am completely stuck here to display the data of my backend in my front
This is my backend code:
const express = require("express");
const router = express.Router();
const Form = require("../models/Form");
router.post("/form/create", async (req, res) => {
try {
if (req.fields.title) {
const newForm = new Form({
title: req.fields.title,
});
await newForm.save();
return res.json(newForm);
} else {
return res.status(400).json({ error: "Missing parameters" });
}
} catch (e) {
return res.status(400).json({ error: e.message });
}
});
This is my console.log(response.data) I want to display in my front React page:
I edited my code and I got an error:
import React, { useState, useEffect } from "react";
/* import { Link } from "react-router-dom"; */
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back.herokuapp.com/form/create",
{
title: "Nouveau formulaire",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.map((question, index) => (
<div> {question} </div>
))}
</>
)}
</div>
</>
);
};
export default NewForm;
Hi Guys,
I updated my code but I have still an error code (TypeError: Cannot read property 'length' of undefined)
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again my code, I succeeded only to display the title of my form but I did not succeed to display the data included in my question array. I have a "0" which appears instead of my data. Please help
return (
<>
<div>My forms </div>
<div>
{data && data.questions && (
<>
<div>{data.title} </div>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again, same error appears:
return (
<>
<div>My forms </div>
<div>
{data &&
data.questions &&
data.questions.length(
<>
<div>{data.title} </div>
{data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
you've done the hard part!
now just .map over the question array if you want to display them out?
<div>
{data.questions.map((question => (
<div> {question.title} </div>
))}
</div>
I've only done a simple example but of course you can display as much or as little as you want
of course anything in state you can render. so if you want to display title do:
{data.title} wherever pleases you
It looks like your backend responds with an object, so here is how you could go about it.
1) Change your initinal state to undefined like this.
const [data, setData] = useState([]);
to
const [data, setData] = useState(undefined);
Then you can use it in the display like this
return (
<div>
{data && (
<>
<p>{data._id}</p>
<p>{data.title}</p>
{data.question.length && data.question.map((question,idx) => {
// this is assuming that each question is just a string and not an object
return (<p key={idx}>{question}</p>)
})}
</>
)}
</div>
)