I cant seem to figure out why my MovieDetailCard component will not re-render when the movie state changes. I am passing in the movie object. When I update the state the log outputs correctly in the useEffect but the MovieDetailsCard never receives the updated object.
const MovieDetails = () => {
const [movie, setMovie] = useState({});
const { id } = useParams();
const { poster } = useParams();
useEffect(() => {
const fetchMovie = async () => {
const response = await fetch(
`http://www.randyconnolly.com/funwebdev/3rd/api/movie/movies.php?id=${id}`
);
const data = await response.json();
setMovie({ ...data });
};
fetchMovie();
}, []);
useEffect(() => {
console.log(movie); // this successfully outputs when movie updates
}, [movie]);
return (
<div className="row">
<div className="col s12 m6">
<MovieDetailsCard poster={poster} movie={movie} /> // this does not update
</div>
<div className="col s12 m6">
<CastCrewCard />
</div>
</div>
);
};
Below is the MovieDeatailsCard. In the useEffect the console.log(movie) always returns null.
const MovieDetailsCard = ({ poster, movie }) => {
useEffect(() => {
console.log("in details");
console.log(movie);
}, []);
return (
<div className="card">
<div className="card-content">
<div className="row">
<div className="col s6">
<span className="card-title">Movie Title</span>
</div>
<div className="col s6 favouriteButton">
<FavouriteButton className="waves-effect waves-light btn">
<i className="material-icons">favorite</i>
</FavouriteButton>
</div>
</div>
<div className="row">
<div className="col s12 m6">
<img src={`https://image.tmdb.org/t/p/w342/${poster}.jpg`} alt="" />
</div>
<div className="col s12 m6">
<p>{movie.title}</p>
</div>
</div>
</div>
</div>
);
};
export default MovieDetailsCard;
Thanks guys for the input. This seems to be resolved now. before I was setting data by setData(data) but when I changed to setData({...data}) that worked!
By default, effects run after every completed render, but you can choose to fire them only when certain values have changed.
refer: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
try updating useEffect dependendecy to include [id]:
useEffect(() => {
const fetchMovie = async () => {
const response = await fetch(
`http://www.randyconnolly.com/funwebdev/3rd/api/movie/movies.php?id=${id}`
);
const data = await response.json();
setMovie({ ...data });
};
fetchMovie();
}, [id]);
Are you sure that you are going receive data from API? The website does not shows up.
Try this to test the API
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json)
In the state declaration (useState) it is better to declare all the keys that you are going to receive all from the API
Related
I'm trying to generate a new quote when the button is clicked. Having trouble figuring out how to implement this. Googling around has led me to believe that the useCallback hook is the way to go, but I haven't any experience with it so I have yet to have any luck implementing it. Any help is appreciated! Thank you in advance.
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useEffect, useState, useCallback } from 'react'
const Main = () => {
const [quote, setQuote] = useState(null)
const [author, setAuthor] = useState(null)
const [newQuote, setNewQuote] = useState(false)
useEffect(() => {
fetch('https://type.fit/api/quotes')
.then(response => response.json())
.then((data) => {
let randomIndex = Math.floor((Math.random() * data.length));
setQuote(data[randomIndex].text)
setAuthor(data[randomIndex].author)
})
.catch(err => console.error(err));
}, [])
return (
<div id='main' className='grid place-items-center h-screen w-screen text-center'>
{/* Quote Gen Container */}
<div className='flex flex-col justify-start mx-auto bg-sky-300 w-3/4 h-3/4 text-black space-y-3 p-32 rounded-3xl relative'>
<h1 className='text-bold text-3xl absolute top-0 mx-auto'>Random Quote Generator</h1>
<div>
<h4 id="text">{`"${quote}"`}</h4>
</div>
<div>
<p id="author">{`- ${author}`}</p>
</div>
<div id="button">
<button onClick={() => setNewQuote(null)} className='bg-black text-white rounded-xl p-2 abs'>New Quote</button>
</div>
</div>
</div>
)
}
export default Main
Refactor the fetch logic into a callback that can be called from either the useEffect hook or directly in a button's onClick handler.
const Main = () => {
const [quote, setQuote] = useState({});
// Memoize a stable callback function reference
const fetchQuote = useCallback(() => {
fetch('https://type.fit/api/quotes')
.then(response => response.json())
.then((data) => {
const randomIndex = Math.floor((Math.random() * data.length));
setQuote(quotes[randomIndex]);
})
.catch(err => console.error(err));
}, []);
// Initial fetch of quotes
useEffect(() => {
fetchQuote();
}, [fetchQuote]);
if (!quote) return null;
return (
<div id='main' className='....'>
{/* Quote Gen Container */}
<div className='....'>
<h1 className='....'>Random Quote Generator</h1>
<div>
<h4 id="text">{`"${quote.text}"`}</h4>
</div>
<div>
<p id="author">{`- ${quote.author}`}</p>
</div>
<div id="button">
<button
onClick={fetchQuote} // <-- attach callback to fetch quote
className='....'>
New Quote
</button>
</div>
</div>
</div>
)
}
If the fetched data doesn't change then fetch it once and select a random quote.
const Main = () => {
const [quotes, setQuotes] = useState([]);
const [quote, setQuote] = useState(null);
// Memoize function to set single random quote
const getQuote = useCallback(() => {
const randomIndex = Math.floor((Math.random() * quotes.length));
setQuote(quotes[randomIndex]);
}, [quotes]);
// Mounting useEffect to fetch and save all quotes
// and set initial random quote
useEffect(() => {
fetch('https://type.fit/api/quotes')
.then(response => response.json())
.then((data) => {
setQuotes(data);
const randomIndex = Math.floor((Math.random() * data.length));
setQuote(data[randomIndex]);
})
.catch(err => console.error(err));
}, []);
if (!quote) return null;
return (
<div id='main' className='....'>
{/* Quote Gen Container */}
<div className='....'>
<h1 className='....'>Random Quote Generator</h1>
<div>
<h4 id="text">{`"${quote.text}"`}</h4>
</div>
<div>
<p id="author">{`- ${quote.author}`}</p>
</div>
<div id="button">
<button
onClick={getQuote}
className='....'
>
New Quote
</button>
</div>
</div>
</div>
)
}
Assuming you don’t need to re-fetch the data every time you could store the returned data in state, then have your onclick just choose a new random entry to set as the “current” quote.
I want to call api and generate div using data from api, but I don't know why this code is not working. It doesn't show anything on the page.
This is my code. countryArray is an object array, and it has property of population, name, continent, capital.
import React from 'react'
function Countries() {
fetch("https://restcountries.com/v3.1/all")
.then((response)=>response.json())
.then((countryArray)=>{
return (
<div>
{countryArray.map((country)=>(
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{country.name}</h2>
<p>Population: {country.population}</p>
<p>Region: {country.continents}</p>
<p>Capital: {country.capital}</p>
</div>
</div>
))}
</div>
)
},
(err)=>{
console.log(err);
})
}
export default Countries
Hello there first of all you need save the api data in a state and then fetch the api in useEffect then you can use the api data in your react app
import React , {useState , useEffect} from 'react';
function app() {
const [examples , setExamples] = useState([]);
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then((res) => res.json())
.then((data) => {
setExamples(data);
})
.catch((err) => console.log(err));
},[]);
return(
<>
<div>
{
examples.map((example) => (
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{example.name.official}</h2>
<p>Population: {example.population}</p>
<p>Region: {example.continents}</p>
<p>Capital: {example.capital}</p>
</div>
</div>
))
}
</div>
</>
);
}
export default app
this code is working
You need to return a jsx element. The usual way of doing data fetching inside react component is to do it inside an effect.
A minimal example would be like this.
function Countries() {
const [countryArray, setCountryArray] = useState([]);
useEffect(() => {
(async function () {
const res = await fetch("https://restcountries.com/v3.1/all");
const json = await res.json();
setCountryArray(json)
})()
}, [])
return (
<div>
{countryArray.map((country)=>(
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{country.name.common}</h2>
<p>Population: {country.population}</p>
<p>Region: {country.continents}</p>
<p>Capital: {country.capital}</p>
</div>
</div>
))}
</div>
)
}
Ofc you should also take care of race conditions, errors, loading states, or use a library that does all this stuff for you and more like react query.
Check the documentation for more information, fetching data
You can't return jsx from fetch, that won't be rendered.
Use useState inside a useEffect to save the data, then return from the functinon itself
const {useState, useEffect} = React;
function Countries() {
const [ data, setData ] = useState([])
useEffect(() => {
function getData() {
fetch("https://restcountries.com/v3.1/all")
.then((response) => response.json())
.then((countryArray) => setData(countryArray)
);
};
getData();
}, [ ]);
return (
<div>
{data.map((country)=>(
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{country.name.common}</h2>
<p>Population: {country.population}</p>
<p>Region: {country.continents}</p>
<p>Capital: {country.capital}</p>
</div>
</div>
))}
</div>
)
}
ReactDOM.render(<Countries />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Demo takes quite some time to load, so here's a pic:
I'm using firestore database to store my data in the collection "listings". So for each document in "listings", I need to render a <BookListing/> element in Home.js with the data from each document. From my research, there are a few other questions similar to this one out there, but they're outdated and use different react syntax. Here's my code:
function BookListing({id, ISBN, title, image, price}) {
return (
<div className="bookListing">
<div className='bookListing_info'>
<p className="bookListing_infoTitle">{title}</p>
<p className="bookListing_infoISBN"><span className="bookListing_infoISBNtag">ISBN: </span>{ISBN}</p>
<p className="bookListing_infoPrice">
<small>$</small>
{price}
</p>
</div>
<img className="bookListing_img" src={image} alt=""></img>
<button className="bookListing_addToCart">Add to Cart</button>
</div>
)
}
export default BookListing
function Home() {
document.title ="Home";
useEffect(() => {
getDocs(collection(db, 'listings'))
.then(queryCollection => {
queryCollection.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
const element = <BookListing id="456" ISBN="0101" title="sample_title" image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg" price="25"/>;
ReactDOM.render(
element,
document.getElementById('home-contents-main')
);
})
});
}, []);
return (
<div className="home">
<div className="home_container">
<div id="home-contents-main" className="home_contents">
</div>
</div>
</div>
)
}
export default Home
It's best (and most common) to separate the task into two: asynchronously fetching data (in your case from firestore), and mapping that data to React components which are to be displayed on the screen.
An example:
function Home() {
// A list of objects, each with `id` and `data` fields.
const [listings, setListings] = useState([]) // [] is the initial data.
// 1. Fetching the data
useEffect(() => {
getDocs(collection(db, 'listings'))
.then(queryCollection => {
const docs = [];
queryCollection.forEach((doc) => {
docs.push({
id: doc.id,
data: doc.data()
});
// Update the listings with the new data; this triggers a re-render
setListings(docs);
});
});
}, []);
// 2. Rendering the data
return (
<div className="home">
<div className="home_container">
<div className="home_contents">
{
listings.map(listing => (
<BookListing
id={listing.id}
ISBN={listing.data.ISBN}
title={listing.data.title}
image={listing.data.image}
price={listing.data.price}
/>
))
}
</div>
</div>
</div>
);
}
Some tips:
Fetching data from other web servers or services can be, and typically is, done in the same manner.
This example could be improved a lot in terms of elegance with modern JS syntax, I was trying to keep it simple.
In most cases, you don't want to use ReactDOM directly (only for the entry point of your app), or mess with the DOM manually; React handles this for you!
If you're not familiar with the useState hook, read Using the State Hook on React's documentation. It's important!
You can create a reusable component, and pass the data to it, and iterate over it using map() . define a state, and use it within the useEffect instead of creating elements and handling the process with the state as a data prop.
function BookListing({ id, ISBN, title, image, price }) {
return (
<div className="bookListing">
<div className="bookListing_info">
<p className="bookListing_infoTitle">{title}</p>
<p className="bookListing_infoISBN">
<span className="bookListing_infoISBNtag">ISBN: </span>
{ISBN}
</p>
<p className="bookListing_infoPrice">
<small>$</small>
{price}
</p>
</div>
<img className="bookListing_img" src={image} alt=""></img>
<button className="bookListing_addToCart">Add to Cart</button>
</div>
);
}
function Home() {
const [data, setData] = useState([]);
useEffect(() => {
document.title = 'College Reseller';
getDocs(collection(db, 'listings')).then((queryCollection) => setData(queryCollection));
}, []);
return (
<div className="home">
<div className="home_container">
<div id="home-contents-main" className="home_contents">
{data.map((doc) => (
<BookListing
id="456"
ISBN="0101"
title="sample_title"
image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg"
price="25"
/>
))}
</div>
</div>
</div>
);
}
export default Home;
I am trying to load data into my component for it to be displayed. I thought the issue was that I wasn't using async/await for the fetch, but even after adding that it still is not loading. I am logging out the "offerings" and it is just showing the empty array. How do I keep the component from rendering until after the data is loaded??
Thanks in advance!
const [offerings, setOfferings] = useState([]);
const loadData = async () => {
const res = await fetch(`http://52.207.83.69` + `${CRUD_OFFERING}`);
setOfferings(await res.json());
console.log(offerings, 'offerings')
};
useEffect(async () => {
navbarToggle();
await loadData();
}, []);
const dispatch = useDispatch();
const modalState = useSelector((state) => state.modal);
const modalToggle = () => {
dispatch({
type: MODAL_TOGGLE,
payload: !modalState.show,
});
ga.event("navbar_requestdemo_clicked");
};
const navbarOpenState = useSelector((state) => state.navbar);
const navbarToggle = () => {
if (!navbarOpenState.open) return;
dispatch({
type: NAVBAR_OPEN,
payload: false,
});
};
return (
<div
className="d-flex justify-content-center align-items-center bg-color-white fc-px-15"
onClick={navbarToggle}
>
<div className={homeStyles["padded-body"] + " col-11 p-0"}>
<div className=" position-relative bg-color-white">
<div className={homeStyles["img-holder"]}></div>
<div className="col-12 column position-absolute top-0 d-flex justify-content-center">
<div className="col-lg-6 col-12 fc-mt-2">
<SearchBar />
</div>
</div>
<div className="position-absolute top-50 translateY-middle">
<div className="position-relative">
<h1 className={`${homeStyles["hero-text"]} font-weight-bolder`}>
Building
<br />
Meaningful
<br />
Engagement
</h1>
<button
className="btn btn-primary-round mt-3 px-3 py-2"
onClick={() => {
modalToggle();
}}
>
Request access
</button>
</div>
</div>
</div>
<div
id={homeStyles["discover-section"]}
className="d-flex justify-content-center align-items-center"
>
<div className="col-12 column">
<h4 className="font-weight-bold">Discover</h4>
<div
id={homeStyles["offer-section"]}
className="row justify-content-center align-items-center"
>
{!offerings?.length &&
<h4 className="text-center">There are no active offerings.</h4>
}
</div>
<OfferingCarousal
offeringsList={offerings}
name={"Offerings"}
/>
<div id={homeStyles["consultancy-section"]} className="">
<div className="row">
<div
className="d-flex justify-content-center align-items-center col-lg-6 col-12 px-0 mt-3 mb-4"
id={homeStyles["consultancy-div"]}
>
<div className="col-12 column p-5">
<h1 className="font-weight-bold">Add your consultancy</h1>
<h5 className="mt-4">
Reach more people and organizations
</h5>
<Link href="/consultancies">
<button className="btn btn-primary-round mt-4">
Learn more
</button>
</Link>
</div>
</div>
<div className="col-lg-6 col-12 px-0">
<img
src="/images/Rachael_glasses_home_page.jpg"
id={homeStyles["consultant-img"]}
className="mt-3"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default HomeNew;
You can check if the data is present before you consume the data.
const [offerings, setOfferings] = useState([]);
const loadData = async () => {
const res = await fetch(`http://52.207.83.69` + `${CRUD_OFFERING}`);
setOfferings(res.json());
console.log(res, 'offerings')
};
useEffect(() => {
navbarToggle();
loadData(); // await has no affect inside useEffect for top level functions
}, []);
// removed internal code for clarity
const dispatch = useDispatch(...yourCode);
const modalState = useSelector(...yourCode);
const modalToggle = () => {...yourCode};
const navbarOpenState = useSelector(...yourCode);
const navbarToggle = () => {...yourCode};
// check after the hooks and before the consuming the data
if(!offerings && !offerings.length) return <>No offerings</>;
return <div>your side nav</div>;
};
It's also good practice to catch asynchronous errors as they occur to prevent your a single component form breaking your whole app. You can also take advantage of the try...catch and put in loading and error states too.
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [offerings, setOfferings] = useState([]);
const loadData = async () => {
try {
setError(false);
setLoading(true);
const res = await fetch(`http://52.207.83.69` + `${CRUD_OFFERING}`);
setOfferings(res.json());
} catch (e){
setError(true)
} finally {
setLoading(false);
}
};
// other code from above
if(error) return <>error</>;
if(loading) return <>loading</>;
if(!offerings && !offerings.length) return <>No offerings</>;
return <div>your side nav</div>;
};
async function can't be put in the useEffect hook directly.
https://prnt.sc/1lu7vdc
It can be like this.
useEffect(() => {
...
(async ()=>{
await loadData()
})();
}, []);
But in your case, I think you don't need to wait until loadData function is executed.
Just make sure you handle exceptions on the rendering for Empty data.
i am getting Parsing error: Unexpected token, expected "(".
I have no idea where i'm getting this unexpected error. anyways i'm probably new to reactJS. It would be great if anybody could figure out where i'm getting this unexpected error. thank you so much in advance.
./src/components/listing/Search.js :
function PostListPageByUser() {
const [posts, setPost] = useState([]);
const [userId, setUserId] = useState([]);
let signal = axios.CancelToken.source();
function handleChange(event) {
setUserId(event.target.value);
}
function handleClick = (event) => {
axios.get("http://localhost:8000/api/car/p_list?search=" + event, {
cancelToken: signal.token,
})
.then(res => {
const posts = res.data;
setPost(posts);
}).catch(err => {
console.log(err);
});
}
return (
<React.Fragment>
<section class="product_list section_padding">
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="product_sidebar">
<div class="single_sedebar">
<form>
<input type="text" name="search" onChange={handleChange} placeholder="Search keyword"/>
<i class="ti-search" onClick={handleClick}></i>
</form>
</div>
</div>
</div>
<div class="col-sm-9">
<div class="product_list">
<div class="row"> <br/><br/><br/>
{
posts.map((post) => {<ul key={post.id}>
<div class="col-lg-8 col-xl-9">
<img src={post.product_image} alt="" class="img-fluid" />
<h3>{post.title}</h3>
</div>
</ul>})
}
</div>
</div>
</div>
</div>
</div>
</section>
</React.Fragment>
);
}
I see 2 issues with your snippet.
Firstly, since you are using an arrow function for handleClick, you need to change it to:
const handleClick = (event) => {
axios.get("http://localhost:8000/api/car/p_list?search=" + event, {
cancelToken: signal.token,
})
.then(res => {
const posts = res.data;
setPost(posts);
}).catch(err => {
console.log(err);
});
Secondly,
{
posts.map((post) => {
return(
<ul key={post.id}>
<div class="col-lg-8 col-xl-9">
<img src={post.product_image} alt="" class="img-fluid" />
<h3>{post.title}</h3>
</div>
</ul>
)
})
}
As an aside, the ul tag is misused here. You should use a div instead. That should not stop your code from working though but for the sake of knowledge and working in a production environment, it's important you always use the right tags. You can learn more here
you need to change this part
const handleClick = (event) => {
axios.get("http://localhost:8000/api/car/p_list?search=" + event, {
cancelToken: signal.token,
})
.then(res => {
const posts = res.data;
setPost(posts);
}).catch(err => {
console.log(err);
});
}
you cannot use the function and arrow function syntax simultaneously!