async custom hooks does't provide updated data - reactjs

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.

Related

Axios get request returns 404 when passed url path parameter

I have a React component "PostDetails" like this:
const PostDetails = () => {
const params = useParams();
const [post, setPost] = useState({});
const [fetchPostById, isLoading, error] = useFetching(async (id) => {
const response = await PostService.getById(id);
setPost(response.data);
})
useEffect(() => {
fetchPostById(params.id)
}, [])
return (
<div>
<h1>Post details page for ID = {params.id}</h1>
<div>{post.id}. {post.title}</div>
</div>
);
};
export default PostDetails;
Custom hook "useFetching" is implemented like this:
export const useFetching = (callback) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const fetching = async () => {
try {
setIsLoading(true);
await callback();
} catch (e) {
setError(e.message);
} finally {
setIsLoading(false);
}
}
return [fetching, isLoading, error];
}
Utility class "PostService" is implemented like this:
export default class PostService {
static async getById(id) {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts/" + id);
return response;
};
}
In browser console I get the error for "GET" request like this:
GET https://jsonplaceholder.typicode.com/posts/undefined 404
I tried to reformat my URL like this:
https://jsonplaceholder.typicode.com/posts/${id}
But still get the same error.
Why does "params.id" convert into undefined when I call my axios fetching request? What am I doing wrong here?
hope my code would be useful.
CodeSandBox
const [id, setId] = useState(1)
const [data, setData] = useState([]);
useEffect(() => {
const res = axios
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((res) => setData(res.data));}, [id]);
return (
<>
<div>Fetched Title of data</div>
<div>{data.title}</div>
<button onClick={() => setId(id + 1)}>Click to increase id</button>
<button onClick={() => setId(id - 1)}>Click to decrease id</button>
</>);
can you try plz
useEffect(() => {
params?.id && fetchPostById(params.id)
}, [])
Try this. I have earned.
const PostDetails = () => {
const params = useParams();
const [post, setPost] = useState({});
const [fetchPostById, isLoading, error] = useFetching(async () => {
const response = await PostService.getById(params.id);
setPost(response.data);
})
useEffect(() => {
fetchPostById(params)
}, [])
return (
<div>
<h1>Post details page for ID = {params.id}</h1>
<div>{post.id}. {post.title}</div>
</div>
);
};
export default PostDetails;

useEffect not seeing dependency change on single click

I am making a call to an Api using the following hook. It returns 10 pictures at a time.
export const useFetchData = (url, page) => {
const [error, setError] = useState(null)
const [apiData, setApiData] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
const res = await axios.get(*****);
const data = await res.data
setApiData(data)
} catch (e) {
setError(e)
} finally {
setLoading(false)
}
}
fetchData()
}, [page, url])
return { apiData, loading, error }
}
I am trying to do pagination in the following component by changing the state value of page by using the nextPage and backPage functions.
let [page, setPage] = useState(1);
let { apiData, loading, error } = useFetchData(url, page);
const nextPage = () => {
setPage(page ++);
};
const backPage = () => {
setPage(page --);
};
return (
<div className="photo-display__buttons-container">
<button onMouseDown={()=>backPage()}>Back</button>
<button onClick={()=>nextPage()}>Next</button>
</div>
<main className="photo-display">
<div className="photo-display__container">
{apiData?.photos.map((photo) => (
<Photo key={photo.id} photo={photo} />
))}
</div>
</div>
);
};
export default App;
By extensive console logging I am able to see that the state value is changed and the hook is called but the try catch does not execute on a single click.
Only if it is double clicked does the try catch execute. The state value is temporarily changed to reflect the double increase but after the hook is called in goes back to the correct value.
Why? and How do i get it to work on a single click?
When you do setPage you are using a postfix ++, which means the original value will be returned (and then incremented). You need to use a prefix ++ so that it is incremented first, then passed in to setState, or just skip the ++ entirely and do setState(i + 1).
Eg (postfix):
let i = 0;
console.log(i++);
Eg (prefix):
let i = 0;
console.log(++i);
Try changing setPage(page ++) to setPage(page+1).

React Hooks - refresh some data on page with new data from post

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>
)
}

Proper use of UseCallBack

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)}>

Too many re-renders with React Hooks

Similar questions have been asked but I haven't found a solution for this particular one. I have one component which renders all boards and I am using a custom useFetch hook to fetch all boards.
const BoardsDashboard = () => {
let [boards, setBoards] = useState([]);
const { response } = useFetch(routes.BOARDS_INDEX_URL, {});
setBoards(response);
return (
<main className="dashboard">
<section className="board-group">
<header>
<div className="board-section-logo">
<span className="person-logo"></span>
</div>
<h2>Personal Boards</h2>
</header>
<ul className="dashboard-board-tiles">
{boards.map(board => (
<BoardTile title={board.title} id={board.id} key={board.id} />
))}
<CreateBoardTile />
</ul>
</section>
</main>
);
};
const useFetch = (url, options) => {
const [response, setResponse] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
I am getting too many re-renders due to setBoards(response) line. What is the right way to handle this?
Thanks!
Sounds like you might want a useEffect hook to take action when response is updated.
useEffect(() => {
setBoards(response);
}, [response]);
Note: if you have no need to ever change the boards state, then maybe it doesn’t need to be stateful at all, you could just use the returned value from your useFetch hook and be done with it.
const { response: boards } = useFetch(...);

Resources