Custom hook with react - dont know whats wrong with this one? - reactjs

I'm trying been more simlicity but i still learn the power of custom hook.
i'm trully not understand whats wrong with my hook.
const UseMessage = (msg: string, delay: number, cb?: () => void): [setCB: () => void] => {
const [message, setMessage] = useState("");
const setCB = () => {
setMessage(() => msg);
setTimeout(() => {
setMessage(() => msg);
if (typeof cb === "function") {
cb();
}
}, delay);
};
return [setCB];
};
export default UseMessage;
i'm getting error :Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Custom hook names should always start with lower case use (see beta docs), so change to useMessage.

There are two things which can resolve your problem, here:
First, name your hook with lowercase use and not uppercase. The reason for that is it is wrong according to the React docs.
Here, your UseMessage is not a custom hook for React, just a javascript function. Inside it, you are using useState which is a hook and React won't allow that. The reason is the next point.
Second, according to the rules of React you can utilise any hook only inside the body of a function component or inside the body of another hook function.

Related

I receive an invalid hook call error in the search function

I was creating a function for my search bar but I receive this error:
Below is the function:
const HandleSearch = async (val) => {
useEffect(() => {
const fetchData = async () => {
const data = await db.collection('accounts').orderBy('date').get();
setAccounts(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
fetchData();
}, []);
useEffect(() => {
setAccounts(
accounts.filter(
(account) =>
account.name.toLowerCase().includes(search.toLowerCase())
)
);
}, [search, accounts]);
}
you might be using the hooks in a wrong way. as per rules of hooks, hooks can only be called from React function components and from a custom Hooks. Don’t call Hooks from regular JavaScript functions, inside loops, conditions, or nested functions.
As the error message tells hooks can only be called inside function components.
Try removing the async keyword preceding your component. This way React will understand it is a functional component and everything should work fine :)
At the moment it is not actually a component as components are supposed to return JSX. By preceding it with the async keyword it will implicitly return a Promise.
You can read more about async functions here. Promises are a pretty tricky concept and take time to fully understand.

How to use Async / Await with React hooks?

I am learning React and following a video tutorial. The instructor used class components and I'm using functional components to brush up on hooks concept.
I want to convert this code into a functional one:
return(
<div className={classes.editorContainer}>
<ReactQuill
value={this.state.text}
onChange={this.updateBody}>
</ReactQuill>
</div>
);
}
updateBody = async (val) => {
await this.setState({ text: val });
this.update();
};
I have tried to do this but async doesn't seem to work as I expected. Await is not working for setText.
const [text, setText] = useState('')
const updateBody = async(val) => {
await setText(val)
update()
}
First of all, setText function does not return a promise that you can await and make sure the data is set.
If you want to set the state and make sure you are calling the update function after the value is set, you need to use another hook called useEffect.
By using useEffect you can basically get the latest state when it is updated.
If you don't care about whether the state is set or not you can convert your async function to the following,
const updateBody = (val) => {
setTitle(val)
update()
}
You can use useEffect hook to trigger a function which you want to get triggered after the state has been updated.
const [text, setText] = useState('')
useEffect(() => {
update()
}, [text]); // This useEffect hook will only trigger whenever 'text' state changes
const updateBody = (val) => {
setText(val)
}
Basically you useEffect() hook accepts two arguments useEffect(callback, [dependencies]);
callback is the callback function containing side-effect logic. useEffect() executes the callback function after React has committed the changes to the screen.
Dependencies is an optional array of dependencies. useEffect() executes callback only if the dependencies have changed between renderings.
Put your side-effect logic into the callback function, then use the dependencies argument to control when you want the side-effect to run
you can find more info about useEffect() hook in this article

React useEffect hook missing dependencies linter warnings

I am using the React useEffect hook to obtain API data on component load, with the useAxios hook. The code is as below (simplified):
const [formData, setFormData] = useState<FormData>();
const [{ , executeGet] = useAxios('', {
manual: true,
});
const getFormData = async () => {
let r = await executeGet({ url: `http://blahblahblah/`});
return r.data;
};
useEffect(() => {
const getData = async () => {
try {
let response = await getAPIData();
if (response) {
setFormData(response);
} catch (e) {
setFormError(true);
}
};
getData();
}, []);
This pattern is used frequently in the codebase, but I am getting the linter warning:
React Hook useEffect has missing dependencies: 'getFormData'. Either include them or remove the dependency array react-hooks/exhaustive-deps
I can suppress the warning successfully with:
// eslint-disable-line react-hooks/exhaustive-deps
but it feels wrong to do this!
I can add constants to the dependency list without a problem, however when I add the getFormData function, I get an infinite loop. I have read around the area a lot and understand why the dependencies are needed. I am not sure if the useEffect hook is the best way to obtain the data, or whether there is a way to fetch data.
The problem is that you are defining getFormData within the component. In each render, it is reassigned. As is, this would mean that your initial useEffect would only be bound to to first getFormData, not the one from the most recent render. This causes a warning because often this is not what you intend, particularly if your getFormData depended on state or props that could change.
The simplest solution in this case is to move the definition of your getFormData outside of your component, and use Axios directly instead of using a hook. That way it wouldn't need to be defined on every render anyways.
you should initiate getFormData function using useCallback hook and then put it in useEffect dependency list.
const getFormData = useCallback(async () => {
let r = await executeGet({ url: `http://blahblahblah/`});
return r.data;
}, [executeGet]);
you can read more about useCallback in reactjs site:
https://reactjs.org/docs/hooks-reference.html#usecallback

Invalid hook call when trying to fetch data using useCallback

I'm trying to call useState inside an async function like:
const [searchParams, setSearchParams] = useState({});
const fetchData = () => useCallback(
() => {
if (!isEmpty(searchParams)) {
setIsLoading(true); // this is a state hook
fetchData(searchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
},
[],
);
There are two states I am trying to set inside this fetchData function (setIsLoading and setIds), but whenever this function is executed am getting the error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
What is this Rule of hooks I am breaking here?
Is there any way around to set these states from the function?
PS: I only used the useCallback hook here for calling this function with lodash/debounce
Edit: The function is called inside useEffect like:
const debouncedSearch = debounce(fetchSearchData, 1000); // Is this the right way to use debounce? I think this is created every render.
const handleFilter = (filterParams) => {
setSearchParams(filterParams);
};
useEffect(() => {
console.log('effect', searchParams); // {name: 'asd'}
debouncedSearch(searchParams); // Tried without passing arguments here as it is available in state.
// But new searchParams are not showing in the `fetchData`. so had to pass from here.
}, [searchParams]);
The hook rule you are breaking concerns useCallback because you are returning it as the result of your fetchData;
useCallback should be called on top level; not in a callback, like this:
const fetchData = useCallback(
() => {
if (!isEmpty(searchParams)) {
setIsLoading(true); // this is a state hook
fetchData(searchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
},
[],
);
The code you wrote is equivalent to
const fetchData = () => { return React.useCallback(...
or even
function fetchData() { return React.useCallback(...
To read more about why you can't do this, I highly recommend this blog post.
edit:
To use the debounced searchParams, you don't need to debounce the function that does the call, but rather debounce the searched value. (and you don't actually the fetchData function that calls React.useCallback at all, just use it directly in your useEffect)
I recommend using this useDebounce hook to debounce your search query
const [searchParams, setSearchParams] = React.useState('');
const debouncedSearchParams = useDebounce(searchParams, 300);// let's say you debounce using a delay of 300ms
React.useEffect(() => {
if (!isEmpty(debouncedSearchQuery)) {
setIsLoading(true); // this is a state hook
fetchData(debouncedSearchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
}, [debouncedSearchParams]); // only call this effect again if the debounced value changes

Custom react hook can not be called in useEffect with empty dependencies

Im new to react hook, Im doing a project with new feature "Hooks" of react.
I've faced a problem and I need an explain for it.
As document, to implement "componentDidMount", just pass empty array in dependencies argument.
useEffect(() => {
// some code here
}, []);
And I can call dispatch function to updateState inside this useEffect.
const [flag, setFlag] = useState(false);
useEffect(() => {
setFlag(true);
}, []);
Above code works perfectly without warning or any errors.
Now I have my custom hook, but I can not call my dispatch inside the effect.
const [customFlag, setCustomFlag] = useCustomHook();
useEffect(() => {
setCustomFlag(true);
}, []);
This is my custom hook.
function useCustomHook() {
const [success, setSuccess] = useState(false):
const component = <div>{ success ? "Success" : "Fail" }</div>;
const dispatch = useCallback(success => {
setSuccess(success);
}, []);
return [component, dispatch];
}
With above code, it requires me to put setCustomFlag inside the dependencies array.
I do not understand why. What is different between them?
Thanks for sharing.
Probably, your custom hook returns different instance of setCustomFlag on each call. It means, that useEffect() will always use first value (returned on first render). Try to memoize it by calling useCallback()/useMemo() hooks:
function useCustomHook() {
...
const setCustomFlag = useCallback(/* setCustomFlag body here */, []);
}
It would be nice to have your custom hook source to say more.
The reason is setFlag from useState is a known dependency, its value won't change between render, hence you don't have to declare it as a dependency
React eslint-plugin-react-hooks can't be sure about your custom hook, that's why you need to put that into the dependency list
This is taken from eslint-plugin-react-hooks
// Next we'll define a few helpers that helps us
// tell if some values don't have to be declared as deps.
// Some are known to be static based on Hook calls.
// const [state, setState] = useState() / React.useState()
// ^^^ true for this reference
// const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ true for this reference
// const ref = useRef()
// ^^^ true for this reference
// False for everything else.
function isStaticKnownHookValue(resolved) {
}

Resources