I am doing a Netflix Clone and I need to change the Banner in every page refresh. I integrated movie details from TMDb. So I want to choose a random number between 0 and 19. I need that random number to display the movie on banner by the number in an array. This array contains movie details. I used Math.random() function and an error came that response is not defined. How Do I solve This. Please Help Me.
Here Is My Code:
import React, { useState } from 'react'
import './Banner.css'
import {useEffect} from 'react'
import {API_KEY,imageUrl} from '../../Constants/Constants'
import axios from '../../Axios'
function Banner() {
const [movie, setMovie] = useState()
const results = response.data.results
const newIndex = Math.floor(Math.rand() * results.length)
useEffect(() => {
axios.get(`trending/all/week?api_key=${API_KEY}&language=en-US`).then((response)=>{
console.log(response.data);
setMovie(response.data.results[newIndex])
})
}, [])
return (
<div className="banner" style={{backgroundImage:`url(${movie ? imageUrl+movie.backdrop_path : ""})`}}>
<div className="content">
<h1 className="title">{movie ? movie.title : ""}</h1>
<div className="banner-buttons">
<button className="button">Play</button>
<button className="button">My List</button>
</div>
<h1 className="description">{movie ? movie.overview : ""}</h1>
</div>
<div className="fade-bottom"></div>
</div>
)
}
export default Banner
response is a block-scoped variable that you're attempting to access.
const [movie, setMovie] = useState()
useEffect(() => {
axios.get(`trending/all/week?api_key=${API_KEY}&language=en-US`).then((response)=>{
const newIndex = Math.floor(Math.rand() * response.data.results.length + 1)
setMovie(response.data.results[newIndex])
})
}, [])
or
const [movie, setMovie] = useState()
const generateRandomNum = (max) => {
Math.floor(Math.rand() * max + 1)
}
useEffect(() => {
axios.get(`trending/all/week?api_key=${API_KEY}&language=en-US`).then((response)=>{
const newIndex = generateRandomNum(response.data.results)
setMovie(response.data.results[newIndex])
})
}, [])
const results = response.data.results
This line, will obviously throw an error because at this point in the execution, response is not defined. You're only getting it later in the axios.get().then() callback. You'd wanna set results there, but using a variable will not work. You'd want this result to persist across renders, so store the results in state, not a constant. Instead of the above line,
const [results, setResults] = useState(null);
and then later in the .then callback,
setResults(response.data.results);
Give an initial placeholder value for your movie, maybe a loading animation, till you get the response from the axios call.
Also,
setMovie(response.data.results[newIndex])
putting the above in your useEffect will result in setting the movie only once,on mount, because the useEffect hook with an empty dependancy array functions as a ComponentDidMount().
If you want to randomly loop through the movies fetched, consider using a setInterval and generate a new random index with Math.random(), (not Math.rand() as used in the question code snippet), and render the result at that index.
Related
I have a navbar component in which there is an input search bar. Currently I am taking the value of the search bar and navigate to the Results component and access the input value useParams.
I have the let [ result, setResult ] = useState([]); in my Results component because the results can change after the search is entered with buttons on the page. The problem is that I cannot set the initial result while defining the useState because I am fetching from an API.
So every time I render, I first get an empty array and failed promise, after which I get the desired one. How to fix this? I need the search bar to be in the navbar.
This is the code. New to React.
const Navbar = () => {
let navigate = useNavigate();
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
let value = event.target.value;
navigate(`/results/${value}`);
}
}
return (
<nav className='navigation'>
<div className='left-slot'>
<button>runtime</button>
</div>
<div className="middle-slot">
<input className="after"
placeholder="get runtimes" onKeyDown={handleKeyDown}>
</input>
</div>
<div className='right-slot'>
<button>How It Works</button>
<button>Coming Soon</button>
</div>
</nav>
);
}
const Results = () => {
let { value } = useParams();
let [ result, setResult ] = useState();
useEffect(() => {
fetchArrayByPage(value, page, option).then(res => setResult(res))
}, [value])
console.log(value);
console.log(result);
return (<div></div>)
}
I'm not entirely sure why your code does not work, so I'll provide three options.
Option 1 - If your problem is value is undefined.
Change your useEffect in Results to this:
useEffect(() => {
fetchArrayByPage(value && value.length ? value : '', page, option).then(res => setResult(res))
}, [value]);
Option 2 - If you need to pass props and Navbar and Results are not on separate routes.
Just pass value as props from Navbar to Results.
Option 3 - Passing components without props.
Use the Context API. This enables you to share data across components without needing to manually pass props down from parent to child.
Initialize variables in context.
Create separate file containing context.
import React, { createContext } from 'react';
const NavbarContext = createContext(null);
export default NavbarContext;
Import said context to App.js or App.tsx if you're using Typescript.
Declare variables and store them in an object for later reference.
const [value, setValue] = useState('');
...
const variables = {
value,
setValue,
...,
};
Wrap with Provider. Pass context variables to the Provider, enabling components to consume variables.
return (
<NavbarContext.Provider value={variables}>
...
</NavbarContext.Provider>
);
Import and use all your variables in Navbar and Results.
const { value, setValue, ... } = useContext(NavbarContext);
try a wrapping function for fetching and setting.
i would suggest something like this:
async function handleResults(){
const res = await fetchArrayByPage(value, page, option)
setResult(res)
}
then you can call it inside useEffect
Trying to create a small app as part of a university assignment using React.
The basic assignment is to create a page which has a question and then has one of 5 answers. I have the answers now stored in a firestore document.
I have created (the start of) a custom Button component.
So the code I have does contact the firestore and I get the data back. The examples I have tried in uni have been for getting 1 bit of data - not like this. What I'm trying to do is to create an answers "array" which I can then iterate over and create my custom buttons. However, I can't quit figure out how to create the array of answers.
Can anyone give me a hint?
import React, {useEffect, useState} from 'react';
import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
import Button from '../components/Button';
function AnswerComponent() {
const firestore = firebase.firestore();
//const storage = firebase.storage();
const collectionId = "Balances";
const documentId = "Answers"
const [answers, setAnwsers] = useState([]);
useEffect(() => {
const getFirebase = async () => {
const snapshot = await firestore.collection(collectionId).doc(documentId).get();
const questionData = snapshot.data();
// now we add the answers and correct flag to our answers
const answerArr = [];
Object.keys(questionData).forEach(key => {
answerArr.push(questionData[key]);
setAnwsers(answerArr)
});
};
getFirebase();
},[answers, firestore])
console.log(">>", answers)
return (
<div className="col-12">
<h3 className="text-center">Answer</h3>
<div className="p-3 mb-2 bg-light">
<div className="row">
</div>
{/* {btns} */}
</div>
</div>
)
}
export default AnswerComponent;
Need to push new answers onto the array, this solution saves each answer as an object with a key (preserving the key).
const answerArr = [];
Object.keys(questionData).forEach(key => {
answerArr.push({ [key]: questionData[key] });
setAnswers(newAnswerArr);
});
If you don't need the key you could save a step and use Object.values:
const answerArr = [];
Object.values(questionData).forEach(questionValue => {
answerArr.push(questionValue);
});
setAnwsers(answerArr);
Or with either solution you could reduce instead of setting the anwserArr separately:
const answerArr = Object.values(questionData).reduce((acc, questionValue) => {
acc.push(questionValue)
return acc;
}, []);
setAnswers(answerArr)
It looks like you're trying to setAnswer several times in a row (which will change the answer) when you do this:
Object.keys(questionData).forEach(key => {
console.log(key, questionData[key]);
setAnwsers(key, questionData[key])
});
Instead, I would try to create a singe array, then setAnswers to that array
const answerArray = []
Object.keys(questionData).forEach(key => {
console.log(key, questionData[key]);
answerArray.push(questionData[key]);
});
setAnswers(answerArray)
Turns out either piece code works (thanks to both #chris-bradshaw and #radicalturnip. The issue was I forgot useEffect changes on every change. So useEffect was triggered, getting data, that was triggering a change, which ran useEffect, which got more data and triggered another change, etc etc x infinitity.
I am trying to access the useState variable query in my function inside useEffect. I get the error React Hook useEffect has a missing dependency: 'query'. Either include it or remove the dependency array.
I think the problem is that im setting the (useState) query variable when the user types in the search bar and then I am trying to access this new query variable in the useEffect hook.
I want to fetch the api after I join the api url and the contents of query but after setQuery is executed which is after the user types in the search bar. How can I do this?
Thanks
Heres the code; notice the query variable.
import React, { useState, useEffect } from "react";
import Grid from '#mui/material/Grid';
import PaperCard from '../components/ResearchPaperCard';
const apiUrl = "http://127.0.0.1:8000/api/search/?search=";
function SearchedPapers(){
const [query, setQuery] = useState("");
const [apiData, setApiData] = useState([])
useEffect(() => {
const getFilteredItems = async (query) => {
let response = await fetch(apiUrl+query);
let papers = await response.json();
setApiData(papers);
if (!query) {
return papers
}
return papers;
}
getFilteredItems(query);
},[]);
console.log(apiData)
return (
<div className='SearchedPapers'>
<label>
Search
</label>
<input type='text' onChange={e => setQuery(e.target.value)}/>
<div>
{apiData.map((paper) => {
return (
<Grid key={paper.title}>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</Grid>
)
})}
</div>
</div>
)
}
export default SearchedPapers;
Right now your useEffect is triggered each time your component mount so basically when someone reaches this screen.
At this time your query state is empty.
Try adding this :
useEffect(() => {
const getFilteredItems = async (query) => {
let response = await fetch(apiUrl+query);
let papers = await response.json();
setApiData(papers);
if (!query) {
return papers
}
return papers;
}
getFilteredItems(query);
},[query]) - - - - > here
By doing so, your useEffect will be triggered only and each time your query state changes
You should either include query in your dependency array i.e., second argument of your UseEffect hook or either remove. When it's(array) empty useEffect will only render once i.e., when your page render for first time but when removed useEffect runs both after the first render and after every update. When specified like you put 'query' in dependency array it will only run whenever there is change in 'query' state.
Here is the code for infinite scroll load
Mainly two components MainComponent and a custom hook component
everytime i entered something on search item it sends the request and display the data to screen and inside main component i am using lastELementRef to set observer Api on that to send the request again when i scrolled at the end .
Not able to understand when does function passed inside useCallBack(()=>{}) runs
to check how many times it runs i did console.log at line no 21 inside MainComponent.
It will be very nice of folks on this community if anybody can explain me when does it runs.
I have googled and watched some Youtube videos on useCallback and all I can come up with is that it gives the function object only when the dependency inside its dependency array changes else on it memoizes the function on each re-render if dependency does not change.?
i am sharing the code here
have used axios to send request.
//MainComponent
import React,{useState,useRef,useCallback} from 'react'
import useBookSearch from './useBookSearch';
export default function MainComponent() {
//these 2 stataes are here because
//we want them to be used in this component only
//meaning we dont want them to be part
//of any custom logic
const[query,setQuery] = useState('');
const[pageNumber,setPageNumber] = useState(1);
const observer = useRef();
const {books,loading,hasMore,error} = useBookSearch(query,pageNumber);
const lastElementRef = useCallback(node=>{
console.log("How many times did i run ?");
if(loading) return ;
if(observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries=>{
if(entries[0].isIntersecting && hasMore){
setPageNumber(prevPage => prevPage + 1);
}
})
if(node) observer.current.observe(node);
console.log(node);
},[loading,hasMore])
const handleSearch=(e)=>{
setQuery(e.target.value);
setPageNumber(1);
}
return (
<div>
<input value={query} type="text" onChange={handleSearch} ></input>
{books.map((book,index) =>{
if(books.length === index + 1)
{
return <div ref={lastElementRef}key={book}>{book}</div>
}
else{
return <div key={book}>{book}</div>
}
})}
{loading && 'Loading.....................'}
{error && 'Error........'}
</div>
)
}
//custom hook component-useBookSearch
import { useEffect,useState } from 'react';
import axios from 'axios'
export default function useBookSearch(query,pageNumber) {
const[loading,setLoading] = useState('true');
const[error,setError] = useState('false');
const[books,setBooks] = useState([]);
const[hasMore,setHasMore] = useState(false);
//second useEffect which clear the books first
//and then make an api request
useEffect(()=>{
setBooks([]);
},[query])
useEffect(()=>{
setLoading(true);
setError(false);
let cancel ;
axios({
method:'GET',
url:'http://openlibrary.org/search.json',
params:{q:query,page:pageNumber},
cancelToken:new axios.CancelToken(c=>cancel=c)
}).then(res=>{
setBooks(prevBooks=>{
return [...new Set([...prevBooks,...res.data.docs.map(b=>b.title)])]
})
setHasMore(res.data.docs.length > 0);
setLoading(false);
console.log(res.data);
}).catch(e=>{
if(axios.isCancel(e)) return
setError(true);
})
return ()=> cancel();
},[query,pageNumber])
return {loading,error,books,hasMore};
}
screenshot of how the code looks when i entered the string test to fetch data
Screenshot of the console window when entering test into input box
My component relies on local state (useState), but the initial value should come from an http response.
Can I pass an async function to set the initial state? How can I set the initial state from the response?
This is my code
const fcads = () => {
let good;
Axios.get(`/admin/getallads`).then((res) => {
good = res.data.map((item) => item._id);
});
return good;
};
const [allads, setAllads] = useState(() => fcads());
But when I try console.log(allads) I got result undefined.
If you use a function as an argument for useState it has to be synchronous.
The code your example shows is asynchronous - it uses a promise that sets the value only after the request is completed
You are trying to load data when a component is rendered for the first time - this is a very common use case and there are many libraries that handle it, like these popular choices: https://www.npmjs.com/package/react-async-hook and https://www.npmjs.com/package/#react-hook/async. They would not only set the data to display, but provide you a flag to use and show a loader or display an error if such has happened
This is basically how you would set initial state when you have to set it asynchronously
const [allads, setAllads] = useState([]);
const [loading, setLoading] = useState(false);
React.useEffect(() => {
// Show a loading animation/message while loading
setLoading(true);
// Invoke async request
Axios.get(`/admin/getallads`).then((res) => {
const ads = res.data.map((item) => item._id);
// Set some items after a successful response
setAllAds(ads):
})
.catch(e => alert(`Getting data failed: ${e.message}`))
.finally(() => setLoading(false))
// No variable dependencies means this would run only once after the first render
}, []);
Think of the initial value of useState as something raw that you can set immediately. You know you would be display handling a list (array) of items, then the initial value should be an empty array. useState only accept a function to cover a bit more expensive cases that would otherwise get evaluated on each render pass. Like reading from local/session storage
const [allads, setAllads] = useState(() => {
const asText = localStorage.getItem('myStoredList');
const ads = asText ? JSON.parse(asText) : [];
return ads;
});
You can use the custom hook to include a callback function for useState with use-state-with-callback npm package.
npm install use-state-with-callback
For your case:
import React from "react";
import Axios from "axios";
import useStateWithCallback from "use-state-with-callback";
export default function App() {
const [allads, setAllads] = useStateWithCallback([], (allads) => {
let good;
Axios.get("https://fakestoreapi.com/products").then((res) => {
good = res.data.map((item) => item.id);
console.log(good);
setAllads(good);
});
});
return (
<div className="App">
<h1> {allads} </h1>
</div>
);
}
Demo & Code: https://codesandbox.io/s/distracted-torvalds-s5c8c?file=/src/App.js