Currently, my code re-renders every time the query parameter is updated. Once I remove the query parameter; however, I get a warning stating "React Hook useCallback has a missing dependency: 'query'. Either include it or remove the dependency array react-hooks/exhaustive-deps". I have tried just defining my getData function within the useEffect, but I am using getData as on onclick function outside of the useEffect. What I am trying to accomplish is to initially fetch articles on react hooks and then only fetch new data on submit as opposed to when the query is updated and not have any warnings about query being a missing dependency as well. Any suggestions would help immensely. the code is as follows:
import React, { useState, useEffect, useCallback } from "react"
import axios from "axios"
const Home = () => {
const [data, setData] = useState(null)
const [query, setQuery] = useState("react hooks")
const getData = useCallback(async () => {
const response = await axios.get(
`http://hn.algolia.com/api/v1/search?query=${query}`
)
setData(response.data)
}, [query])
useEffect(() => {
getData()
}, [getData])
const handleChange = event => {
event.preventDefault()
setQuery(event.target.value)
}
return (
<div>
<input type='text' onChange={handleChange} value={query} />
<button type='button' onClick={getData}>
Submit
</button>
{data &&
data.hits.map(item => (
<div key={item.objectID}>
{item.url && (
<>
<a href={item.url}>{item.title}</a>
<div>{item.author}</div>
</>
)}
</div>
))}
</div>
)
}
export default Home
Add a submitting state as a condition for triggering your axios request
const [submitting, setSubmitting] = useState(true)
const [data, setData] = useState(null)
const [query, setQuery] = useState("react hooks")
useEffect(() => {
const getData = async () => {
const response = await axios.get(
`http://hn.algolia.com/api/v1/search?query=${query}`
)
setData(response.data)
setSubmitting(false) // call is finished, set to false
}
// query can change, but don't actually trigger
// request unless submitting is true
if (submitting) { // is true initially, and again when button is clicked
getData()
}
}, [submitting, query])
const handleChange = event => {
event.preventDefault()
setQuery(event.target.value)
}
const getData = () => setSubmitting(true)
If you wanted to useCallback, it could be refactored as such:
const [submitting, setSubmitting] = useState(true)
const [data, setData] = useState(null)
const [query, setQuery] = useState("react hooks")
const getData = useCallback(async () => {
const response = await axios.get(
`http://hn.algolia.com/api/v1/search?query=${query}`
)
setData(response.data)
}, [query])
useEffect(() => {
if (submitting) { // is true initially, and again when button is clicked
getData().then(() => setSubmitting(false))
}
}, [submitting, getData])
const handleChange = event => {
event.preventDefault()
setQuery(event.target.value)
}
and in render
<button type='button' onClick={() => setSubmitting(true)}>
Related
I have a component that fetches data only when mounted. I ideally only want to make this call once as it is fetching a lot of data. When I make a post request, I receive new data which I want to display on the page and optimistically update the UI. I don't want to refetch the data again as it's an expensive call and would instead just like to update the changed data. I could create an API endpoint that I call to fetch the necessary data on updates but why not update the data with what I receive from the post request?
example code:
const App = () => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [alert, setAlert] = useState(null);
// data states
const [user, setUser] = useState(null);
const [account, setAccount] = useState(null);
const [key, setKey] = useState(null);
const [externalAccount, setExternalAccount] = useState(null);
const [showModal, setShowModal] = useState(false);
// fetch data upon component mount
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// Get data for working with accounts
const res = await get(
'/api/v1/account'
);
setUser(res.data.user);
setAccount(res.data.account);
setKey(res.data.key);
setExternalAccount(res.data.external_account);
} catch (e) {
setError(e);
}
setLoading(false);
};
fetchData();
}, []);
const createAccount = async params => {
setLoading(true);
try {
// send request
const res = await post(
'/api/v1/account',
params
);
// set updated account data
setAccount(res.data.account); //doesn't update on the page. I could call a refetch in the above useEffect but not ideal. Any other options?
// set success alert
setAlert('Your account was created successfully.');
// update loading state
setLoading(false);
} catch (e) {
setAlert(e.message);
setLoading(false);
}
};
// Define page renders
if (error) return <ErrorComponent />;
if (loading) return <Loader />;
return (
<>
<h1>Account</h1>
{account &&
<div>
//display information using state information on user and account
</div>
}
{showModal &&
<CreateModal
toggleModal={setShowModal}
createAccount={createAccount}
user={user}
account={account}
/>
}
</>
);
}
const CreateModal = ({ toggleModal, createAccount }) => {
const handleSubmit = e => {
e.preventDetault();
const params = // set up params for post request
createAccount(params)
return (
<form onSubmit={e => handleSubmit(e)}>
//form code here
</form>
)
}
I have a simple hook to help me handle a POST request. With the following code, I expect unsub will be true after the POST is done. Can anyone point out anything I could have done wrong?
Custom Hook
const useUnsubscribeEmail = () => {
const [userId, setUserId] = useState(null);
const [unsub, setUnSub] = useState();
const UNSUB_URL = '/web-registry-api/v1/reviews/unsubscription';
useEffect(() => {
if (userId) {
// async POST call
(async () => {
try {
await ApiService.post(`${UNSUB_URL}/${userId}`);
// update unsub value
setUnSub(true);
} catch (error) {
console.error(error)
}
})();
}
}, [userId]);
return [unsub, setUserId];
};
export default useUnsubscribeEmail;
Component
const ReviewUnsubscription = () => {
const { userId } = useParams();
const [unsub, unsubscribeEmail] = useUnsubscribeEmail();
return (
<MinimumLayout>
<div className={styles.content}>
<h1>Unsubscribe from email reminders to review products you’ve received from Zola?{unsub}</h1>
{/* unsub here is still undefined */}
<Button disabled={unsub} onClick={() => { unsubscribeEmail(userId); }} variant="primary" className={styles.button}>Unsubscribe</Button>
</div>
</MinimumLayout>
);
};
unsub is still going to be undefined until you click the button as you have not set a default state for it in your hook.
change : const [unsub, setUnSub] = useState(); to const [unsub, setUnSub] = useState(false); is what I would recommend
I tested on my side and works just fine; However, I cannot test the APIService.post.
I am trying to use hooks and implement a custom hook for handling my data fetching after every update I send to the API.
My custom hook, however, doesn't fire on change like I want it too. Delete has to be clicked twice for it to rerender. Note: I removed some functions from this code as they don't pertain to the question.
import React, { useState, useEffect } from "react"
import {Trash} from 'react-bootstrap-icons'
import InlineEdit from 'react-ions/lib/components/InlineEdit'
function Board(){
const [render, setRender] = useState(false)
const [boards, setBoards] = useState([]);
const [isEditing, setEdit] = useState(false)
const [value, setValue] = useState("")
const[newValue, setNewValue] = useState("")
const [error, setError] = useState("")
function useAsyncHook(setState, trigger) {
const [result] = useState([]);
const [loading, setLoading] = useState("false");
useEffect(() => {
async function fetchList() {
try {
setLoading("true");
const response = await fetch(
`http://localhost:8080/api/boards`
);
const json = await response.json();
setState(json)
} catch (error) {
//console.log(error)
setLoading("null");
}
}
fetchList()
}, [trigger]);
return [result, loading];
}
useAsyncHook(setBoards, render)
const handleDelete = (id) => {
console.log("delete clicked")
setLoading(true);
fetch(`http://localhost:8080/api/boards/` + id, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
})
setRender (!render)
}
return(
<div>
<ul>
{boards.map(board => (
<li key={board.id}>
<InlineEdit value={board.size} isEditing={isEditing} changeCallback={(event)=>handleSave (event, board.id)} />
<Trash onClick={()=>handleDelete(board.id)}/>
</li>
</ul>
</div>
);
}
export default Board
OPTION 1:
Maybe you wanna have a hook that tells you when to fetch the board, right? For example:
const [auxToFetchBoard, setAuxToFetchBoard] = useState(false);
Then, in a useEffect you execute the function fetchBoard everytime that hook changes:
useEffect(fetchBoard, [auxToFetchBoard]);
Finally, in your handleDelete function, if your delete request returns correctly, you have to update auxToFetchBoard. Something like this:
const handleDelete = (id) => {
setIsLoading(true);
setError("");
fetch(yourURL, yourOptions)
.then(res => {
// check if response is correct and
setIsLoading(false);
setAuxToFetchBoard(!auxToFetchBoard);
})
.catch(() => {
setIsLoading(false);
setError("Error while deleting stuff");
});
};
Note: I changed the names of isLoading and setIsLoading because they are more explicit.
OPTION 2:
Instead of fetching the board again and again, you can update your board (in this case your code would be in 8th line inside the handleDeletefunction).
Hope it helps.
My component fetches data by calling an hook-file which contains logic for requesting via API.
By default it will call the API without any extra parameter.
In GUI I also show an input where use can enter text.
Each time he writes a letter I want to refetch data. But Im not really sure how to do this with react and hooks.
I declared "useEffect". And I see that the content of the input changes. But what more? I cannot call the hook-function from there because I then get this error:
"React Hook "useFetch" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks"
This is the code:
hooks.js
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
fetchUrl();
}, [url]);
return [data, loading];
}
export { useFetch };
mycomponent.js
import React, { useState, useEffect } from 'react';
import { useFetch } from "../hooks";
const MyComponent = () => {
useEffect(() => {
console.log('rendered!');
console.log('searchTerm!',searchTerm);
});
const [searchTerm, setSearchTerm] = useState('');
const [data, loading] = useFetch(
"http://localhost:8000/endpoint?${searchTerm}"
);
return (
<>
<h1>Users</h1>
<p>
<input type="text" placeholder="Search" id="searchQuery" onChange={(e) => setSearchTerm(e.target.value)} />
</p>
{loading ? (
"Loading..."
) : (
<div>
{data.users.map((obj) => (
<div key={`${obj.id}`}>
{`${obj.firstName}`} {`${obj.lastName}`}
</div>
))}
</div>
)}
</>
);
}
export default MyComponent;
Create a function to handle your onChange event and call your fetch function from it. Something like this:
mycomponent.js
import React, { useState, useEffect } from 'react';
import { useFetch } from "../hooks";
const MyComponent = () => {
useEffect(() => {
console.log('rendered!');
console.log('searchTerm!',searchTerm);
});
const [searchTerm, setSearchTerm] = useState('');
const handleChange = e => {
setSearchTerm(e.target.value)
useFetch(
"http://localhost:8000/endpoint?${searchTerm}"
);
}
const [data, loading] = useFetch(
"http://localhost:8000/endpoint?${searchTerm}"
);
return (
<>
<h1>Users</h1>
<p>
<input type="text" placeholder="Search" id="searchQuery" onChange={(e) => handleChange(e)} />
</p>
{loading ? (
"Loading..."
) : (
<div>
{data.users.map((obj) => (
<div key={`${obj.id}`}>
{`${obj.firstName}`} {`${obj.lastName}`}
</div>
))}
</div>
)}
</>
);
}
export default MyComponent;
Your code works for me as per your requirement, type 1 or 2 in text box you will have different results.
So basically API get called once with default value of "searchTerm" and then it get called for each time by onChange.
try this at your local -
import React, { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
fetchUrl();
}, [url]);
return [data, loading];
}
export { useFetch };
const MyComponent = () => {
useEffect(() => {
console.log("rendered!");
console.log("searchTerm!", searchTerm);
});
const [searchTerm, setSearchTerm] = useState("");
const [data, loading] = useFetch(
`https://reqres.in/api/users?page=${searchTerm}`
);
return (
<>
<h1>Users</h1>
<p>
<input
type="text"
placeholder="Search"
id="searchQuery"
onChange={e => setSearchTerm(e.target.value)}
/>
</p>
{loading ? (
"Loading..."
) : (
<div>
{data.data.map(obj => (
<div key={`${obj.id}`}>
{`${obj.first_name}`} {`${obj.last_name}`}
</div>
))}
</div>
)}
</>
);
};
export default MyComponent;
The way your useFetch hook is setup it will only run once on load. You need to have it setup in a way you can trigger it from an effect function that runs only when searchTerm changes.
this is how you handle searching in react properly. It is better to have default searchTerm defined when user lands on your page, because otherwise they will see empty page or seening "loading" text which is not a good user experience.
const [data, setData] = useState([]);
const [searchTerm, setSearchTerm] = useState("defaultTerm")
In the first render of page, we should be showing the results of "defaultTerm" search to the user. However, if you do not set up a guard, in each keystroke, your app is going to make api requests which will slow down your app.
To avoid fetching data in each keystroke, we set up "setTimeout" for maybe 500 ms. then each time user types in different search term we have to make sure we clean up previous setTimeout function, so our app will not have memory leak.
useEffect(() => {
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
}
// this is during initial rendering. we have default term but no data yet
if(searchTerm && !data){
fetchUrl();
}else{
//setTimeout returns an id
const timerId=setTimeout(()=>{
if(searchTerm){
fetchUrl}
},500)
// this where we do clean up
return ()=>{clearTimeout(timerId)}
}
}, [url]);
return [data, loading];
}
inside useEffect we are allowed to return only a function which is responsible for cleaning up. So right before we call useEffect again, we stop the last setTimeout.
I am needing to fetch data from the MovieDB API and I have my code setup to where I just want to return some data after I hit the search button. But when I hit the search button I get back NetworkError when attempting to fetch resource
My code so far consists of this
import React, {useEffect, useState} from 'react';
import './App.css';
const App = () => {
const API_KEY = '664e565dee7eaa6ef924c41133a22b63';
const [movies, setMovies] = useState([]);
const [query, setQuery] = useState("");
useEffect(() => {
async function getMovies(){
const response = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}`)
const data = await response.json()
console.log(data.results)
setMovies(data.results)
}
if(query !== "") getMovies();
}, [query])
return (
<div>
<form>
<button onClick={() => setQuery("Avengers")}type="submit">Search</button>
<p>{JSON.stringify(movies)}</p>
</form>
</div>
);
}
export default App;
If use (and query ='Avengers'):
${query}`
in API URL, you get this (Every record is corelated with Avengers movie)
Try this - It's not include more advanced functions, which you need.
But it's good fundamental for bulding next features:
import React, { useEffect, useState } from 'react';
const App2 = () => {
const API_KEY = '664e565dee7eaa6ef924c41133a22b63';
const [movies, setMovies] = useState([]);
const [query, setQuery] = useState('Avengers');
useEffect(() => {
async function getMovies(query) {
await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=$query`)
.then(data => data.json())
.then(data => {
console.log(data.results)
const result = data.results.map(obj => ({ popularity: obj.popularity, id: obj.id }));
console.log(result)
setMovies(result)
console.log(movies)
})
}
getMovies()
}, [query])
return (
<div>
{movies.map((movie, key) => (
<div key={key}>
<h1> {movie.popularity}</h1>
<h1>{movie.id}</h1>
</div>
))}
</div>
);
}
export default App2;
Here is your schema from API (only 1 object in array) (I used only id & popularity) - it's possible to use what you wish: