I’m playing around with React to build a simple little recipe keeper app and I’m coming across an issue I haven’t yet figured out.
import { useParams, useHistory, Link } from "react-router-dom";
import useFetch from "./useFetch";
const RecipeDetails = () => {
const { userId, id } = useParams();
const history = useHistory();
const { data: recipe, isPending, error } = useFetch('url to api' + customerId + '/' + id);
return (
<div className="recipe-details">
<div className="recipe-details-actions">
<Link to={`/my-recipes`}>
<span>Back to My Recipes</span>
</Link>
<span>Edit</span>
<a href='#' onClick={() => {
fetch('api delete endpoint'+ customerId + '/' + id, {
method: 'DELETE'
}).then(res => {
if (!res.ok) { // error coming back from server
throw Error('server level error');
}
else {
history.push('/my-recipes');
}
})
.catch(err => {
console.log(err.message);
})
}}>
Delete
</a>
</div>
<h4>Recipe Details - { id }</h4>
{isPending && <div>Loading...</div>}
{error && <div>{ error }</div>}
{recipe && (
<div>
<h2>{recipe.title}</h2>
<p>Written by {recipe.author}</p>
<p>Serves: {recipe.serves}</p>
<p>Prep time: {recipe.prep_time}</p>
<p>Cook time: {recipe.cook_time}</p>
<p>Dish: {recipe.dish}</p>
<br />
</div>
)}
</div>
);
}
export default RecipeDetails;
The above works. May not be perfect, but I’m learning. If I however take the code that’s executed when the delete link is pressed (to delete a recipe) and place that in a function, I get an error related to the use of the useHistory
hook. See below:
import { useParams, useHistory, Link } from "react-router-dom";
import useFetch from "./useFetch";
const handleDeleteClick = (customerId, recipeId) => {
console.log('delete -> ' + recipeId);
const history = useHistory();
fetch('api delete url' + customerId + '/' + id, {
method: 'DELETE'
}).then(res => {
if (!res.ok) { // error coming back from server
throw Error('server level error');
}
else {
history.push('/my-recipes');
}
})
.catch(err => {
console.log(err.message);
})
}
const RecipeDetails = () => {
const { customerId, id } = useParams();
const { data: recipe, isPending, error } = useFetch('api fetch url' + customerId + '/' + id);
return (
<div className="recipe-details">
<div className="recipe-details-actions">
<Link to={`/my-recipes`}>
<span>Back to My Recipes</span>
</Link>
<span>Edit</span>
<a href='#' onClick={() => handleDeleteClick(customerId, id)}>
Delete
</a>
</div>
<h4>Recipe Details - { id }</h4>
{isPending && <div>Loading...</div>}
{error && <div>{ error }</div>}
{recipe && ingredients && instructions && (
<div>
<h2>{recipe.title}</h2>
<p>Written by {recipe.author}</p>
<p>Serves: {recipe.serves}</p>
<p>Prep time: {recipe.prep_time}</p>
<p>Cook time: {recipe.cook_time}</p>
<p>Dish: {recipe.dish}</p>
</div>
)}
</div>
);
}
export default RecipeDetails;
I’ve also tried putting all the delete code in its own custom hook, which naturally results in the same error as it seems using hooks or custom hooks can’t be done outside the component. Just not sure how to do what I’m after, which is idealing putting the delete fetch in its own custom hook and redirecting after the delete operation is completed successfully. Seeing the following error: React Hook "useHistory" is called in function "handleDeleteClick" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
2
Answers
If you want to define event handler for the delete button outside the component, you can pass it the
history
object as another argument so thatuseHistory
will still be called at the top level of the component.The original code you have works because the
useHistory
hook is correctly called at the root of the React function and everything is in scope. The second code you have fails to work because you are trying to use a React hook outside a React function or custom React hook which breaks the rules of hooks.Typically you declare callback handlers inside the React component using it, e.g. within the body/scope of the React function. Move
handleDeleteClick
into theRecipeDetails
component and call the react hook in the component body.Example:
If you are really wanting to declare
handleDeleteClick
external to a React component then you can rewrite it to consume also thehistory
reference that would need to be passed to it from the calling scope. TheuseHistory
hook is still called from theRecipeDetails
React component scope.Example:
I don’t see much value in converting the specific callback instance into a React hook. Do you have some more general "fetch on click" use case that could benefit from some code reuse? If not then I wouldn’t chase down that rat hole.