How can I use a useState variable inside uesEffect? - javascript

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.

Related

React.js API for search bar

I'm very new to JavaScript and the world of React. I've learning hooks, and tried to fetch some API for searchbar and it doesn't work.
I'm trying to grab the data from the url (its array) and search bar to filter items by its title.
function Search() {
const [searchTerm, setSearchTerm] = useState([]);
const [text,setText] = useState([]);
const getAPI = async() => {
const response = await fetch("https://fakestoreapi.com/products")
const data = await response.json()
setText(data.Search)
}
useEffect( () => {
getAPI()
}, [])
return <div>
<input
placeholder="searching"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}/>
</div>
};
there is not any key with Search name inside response data you should setState all your data and then filter with input text value.
Please read useEffect documentation.
useEffect( () => {
getAPI()
}, [])
The above code only runs when the component is mounted. When the user changes the value of input, Search component rerenders, because its state changes. But useEffect will not be executed because you have provided an empty array of dependencies.
You have declared getAPI inside your component. So you should probably add it to the array of dependencies of useEffect.
You should call getAPI in onChange of the input. So it fetches the data from server based on the query parameters.
You have not used searchTerm inside getAPI function.
Be aware of infinite loop caused by useEffect.

Random Number Choosing On React.js

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.

When does the callback function inside the useCallback() hook runs exactly?

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

Can you use an async function to set initial state with useState

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

The component does not work asynchronously

I have a component that makes a request and displays a list of jobs.
import React, { useState, useEffect, Fragment } from 'react';
import { Jobs } from '../components/Jobs.component';
export const Fixed = () => {
const [jobs, setJobs] = useState([]);
useEffect(() => {
getItems();
}, []);
async function getItems() {
const url = 'http://localhost:8081/api/fixed/list';
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
});
const data = await res.json();
console.log(data);
setJobs(data.jobsList);
console.log(jobs);
}
return (
<Fragment>
{jobs.map(job => (
<div>
<Jobs job={job} />
</div>
))}
</Fragment>
);
};
My problem is that the first console outputs an array of jobs, but the second console displays an empty array. And an empty value is passed to the job component, which causes an error.
He does not have time to write the work in a state? Where am I wrong?
Method setJobs needs some time to change state so console.log runs faster than value changes.
You should render list if the array length is bigger than 0.
{jobs.length && jobs.map(job => <Jobs job={job} />)}
State updates are run asynchroniously
The reason your console.log shows an empty array is because setJobs runs asynchroniously and will update jobs value on next render. Looking at react setState documentation (same as useState react hooks) :
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.
And so
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
const ... jobs ... is a constant - it will be a different constant in 2 different renders, but it will not change value during a single render
The jobs inside getItems is a closure and will reference to the value from the first render, while setJobs will only change the value in second render.
It's similar to the following:
const rememberJobs = (jobs) => () => console.log(jobs)
const first = rememberJobs([])
const second = rememberJobs([1, 2, 3])
first()
second()

Categories

Resources