I made custom hook and used in interceptors which is not react component.it's javascript function so i got this error-
Invalid hook call. Hooks can only be called inside of the body of a function component.
And this is unfortunate because it prevents us from using newer hook-based modules in our older class-based components and regular javascript functions.
So now i want to be convert custom hook into HOC.
const useLoader = props => {
const [loading, setLoading] = useState(props);
return [
loading ? <Loader /> : null,
() => setLoading(true), // show loader
() => setLoading(false) // hideLoader
];
};
export default useLoader;
How can i do that i have never write HOC.
Here is the code
So the requirement is when i click on proceed button loader will be visible on the screen on api call after getting the response then only loader will disappear and navigate to the next page.
Here is updated code where i have updated with HOC but
loader seems works only first time when i load the page.
what i am missing here ?
Updated with HOC - codeSandboax
You can try like this.
const LoaderHOC = ({ Component }) => {
const [loading, setLoading] = useState(props);
const toggleLoading = () => {
setLoading(!loading)
}
return (
loading ? <Loader/> : <Component toggleLoading={toggleLoading}/>
)
};
export default useLoader;
I think you will get an idea.
Related
I'm trying to build a notification React component for my application that I can call from anywhere. I have the below code where I'm trying to do some kinda hack for exporting the showNotif function
function FloatNotification() {
const [show, setShow] = useState(false);
const showNotif = () => {
console.log(`showNotif is called`);
setShow(true);
};
if (!FloatNotification.showNotification) {
FloatNotification.showNotif = showNotif;
}
return <>{show ? `Showing notification` : `not showing notification`}</>
}
export default FloatNotification;
On another file I'm trying to call showNotif like below
import FloatNotification from "./FloatNotification";
function MyComponent() {
const {showNotif} = FloatNotification;
return <><button onClick={() => showNotif()}>Click Me</button></>
}
but the setState isn't getting called unexpectedly. I'm getting the showNotif is called message in the console. so logically the setState should also get called.
I kinda understand it's happening because of how javascript handles reference data type. but I'm not sure what's actually happening behind the scene and how to get my goal
Suggest me if you have any other ideas to build this notification component (something I can call from anywhere in my component tree). Any kind of help will be kudos to me
[NOTE: I'm actually using NextJS and I've added this FloatNotification in the _app.js. So it's available in all the pages
useState is a special function called "hooks". React hooks are only available when the component is rendered in the VDOM tree.
Since you dosen't render FloatNotification as a element, calling setState is unexpected and may has no effects.
There are several ways to achieve what you want without hacking.
First is, lift up notification state to the parent component and inject only the dispatch that changes the state is through the context.
const NotificationContext = React.createContext(() => {});
function FloatNotification({ children }) {
const [show, setShow] = useState(false);
return (
<NotificationContext.Provider value={setShow}>
{children}
<>{show ? `Showing notification` : `not showing notification`}</>
</NotificationContext.Provider>
);
}
function MyComponent() {
const setShow = useContext(NotificaitonContext);
return (
<button onClick={() => setShow(true)}>
Show Notification
</button>
);
}
function App() {
return (
<FloatNotification>
<MyComponent />
</FloatNotification>
);
}
Or, you can exposing the handler by React.useImperativeHandle (commonly not recommended)
I have a function called scheduleShortcut that gets used in a couple places throughout an application. Initially, this function was local to the specific components but since it is being used multiple times, I want to refactor this function into a global function.
At first, I tried to do the following:
const history = useHistory();
const dispatch = useDispatch();
export const scheduleShortcut = (jobId: number) => {
dispatch(jobGanttFocus(jobId));
dispatch(focusJobScheduleFilter(jobId));
dispatch(toggleScheduleView('jobs'));
history.push('/schedule');
};
However, when I do this, I get an error that says I can't use useHistory or useDispatch unless they are inside a React component or a custom hook. Then, I tried to convert scheduleShortcut into a custom hook in the following way:
export const useScheduleShortcut = (jobId: number) => {
const history = useHistory();
const dispatch = useDispatch();
dispatch(jobGanttFocus(jobId));
dispatch(focusJobScheduleFilter(jobId));
dispatch(toggleScheduleView('jobs'));
history.push('/schedule');
};
This allowed me to utilize useDispatch and useHistory. However, when I try to call this function inside the specific components I need it in, I get a similar error. Basically, it says that I cannot use my custom hook (i.e. useScheduleShortcut) inside a callback.
<Roster
jobId={job.id}
assignWorkers={canAssignWorker ? handleAssignWorkers : undefined}
scheduleShortcut={() => useScheduleShortcut(jobId)}
/>
Is there a way I can get around these errors and use scheduleShortcut as a recyclable function? Or is this in fact not possible since I am using the hooks?
Hooks in fact must be called on top level, you are breaking that rule
You could expose(return) a function from hook that could be called as callback afterwards.
i.e.
export const useScheduleShortcut = () => {
const history = useHistory()
const dispatch = useDispatch()
const dispatchScheduleShortcut = (jobId) => {
dispatch(jobGanttFocus(jobId))
dispatch(focusJobScheduleFilter(jobId))
dispatch(toggleScheduleView('jobs'))
history.push('/schedule')
}
return {
dispatchScheduleShortcut
}
};
and then use it as
const { dispatchScheduleShortcut } = useScheduleShortcut()
return (
<Roster
jobId={job.id}
assignWorkers={canAssignWorker ? handleAssignWorkers : undefined}
scheduleShortcut={() => dispatchScheduleShortcut(jobId)}
/>
)
I'm trying to simulate a link in react js clicking on a div.
This is my code:
function handleClick(myLink){
window.location.href=myLink;
}
and here where I call it:
<Col className="aslink" onClick={handleClick('/path/to/myUrl')}>
<div>...</div>
</Col>
But it goes directly to the URL without clicking, so it starts an infinite loop.
How can I solve it?
Many thanks in advance!
This is because you are calling the function in this part <Col className="aslink" onClick={handleClick('/path/to/myUrl')}> instead of providing reference to it to be used on users click action. What you can do is define it like this:
const handleClick = (myLink) => () => {
window.location.href=myLink;
}
then it will work as you want it.
handclick('your path')
is already running the code. Try
onClick = {() => handlick('your path')}
This will stop it from automatically running
First off, I would recommend using React Router's Link over history.location.href, as it uses the routers history api, using this declarative, accessible navigation instead of history means that you are safe to any changes to the history api in the future.
You can import it like this:
import { Link } from 'react-router-dom'
Secondly, you were calling the handleClick function instead of executing the function.
If you use react-router*(which is most possible - if not - then you should research value of this)* then you can get access to browser-history via react router provider
pass router api to your component
if you use modern react version - use hook useHistory -
const Comp = () => {
const history = useHistory()
const handleRedirect = useCallback((path) => {
return () => {
history.push(path);
}
}, [])
return <div onClick={handleRedirect('path-to-page')}>Navigate</div>
}
export default Comp;
or 2. extract history object from taken props in your component
you can wrap you component by HOC - withRouter.
const Comp = ({history}) => {
const handleRedirect = useCallback((path) => {
return () => {
history.push(path);
}
}, [])
return <div onClick={handleRedirect('path-to-page')}>Navigate</div>
}
export default withRouter(Comp)
I have a use-case where a page have to call the same fetch function on first render and on button click.
The code is similar to the below (ref: https://stackblitz.com/edit/stackoverflow-question-bink-62951987?file=index.tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
The problem for me is the fetch function always triggers on any input changed because eslint-plugin-react-hooks forces me to declare all dependencies (ex: selected state) in the useCallback hook. And I have to use useCallback in order to use it with useEffect.
I am aware that I can put the function outside of the component and passes all the arguments (props, setLoading, setError, ..etc.) in order for this to work but I wonder whether it is possible to archive the same effect while keeping the fetch function inside the component and comply to eslint-plugin-react-hooks?
[UPDATED]
For anyone who is interested in viewing the working example. Here is the updated code derived from the accepted answer.
https://stackblitz.com/edit/stackoverflow-question-bink-62951987-vxqtwm?file=index.tsx
Add all of your dependecies to useCallback as usual, but don't make another function in useEffect:
useEffect(query, [])
For async callbacks (like query in your case), you'll need to use the old-styled promise way with .then, .catch and .finally callbacks in order to have a void function passed to useCallback, which is required by useEffect.
Another approach can be found on React's docs, but it's not recommended according to the docs.
After all, inline functions passed to useEffect are re-declared on each re-render anyways. With the first approach, you'll be passing new function only when the deps of query change. The warnings should go away, too. ;)
There are a few models to achieve something where you need to call a fetch function when a component mounts and on a click on a button/other. Here I bring to you another model where you achieve both by using hooks only and without calling the fetch function directly based on a button click. It'll also help you to satisfy eslint rules for hook deps array and be safe about infinite loop easily. Actually, this will leverage the power of effect hook called useEffect and other being useState. But in case you have multiple functions to fetch different data, then you can consider many options, like useReducer approach. Well, look at this project where I tried to achieve something similar to what you wanted.
https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js
Let's talk about the model a bit
export default function App() {
const [data, setDate] = React.useState("");
const [id, setId] = React.useState(1);
const [url, setUrl] = React.useState(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
fetch(url)
.then(response => response.json())
.then(json => {
setDate(json);
setIsLoading(false);
});
}, [url]);
return (
<div className="App">
<h1>Fetch data from API in React Hooks</h1>
<input value={id} type="number" onChange={e => setId(e.target.value)} />
<button
onClick={() => {
setIsLoading(true);
setUrl(`https://jsonplaceholder.typicode.com/todos/${id}`);
}}
>
GO & FETCH
</button>
{isLoading ? (
<p>Loading</p>
) : (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
Here I fetched data in first rendering using the initial link, and on each button click instead of calling any method I updated a state that exists in the deps array of effect hook, useEffect, so that useEffect runs again.
I think you can achieve the desired behavior easily as
useEffect(() => {
query();
}, [data]) // Only re-run the effect if data changes
For details, navigate to the end of this official docs page.
Well, i have this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I tried alot of different options to fix this, but i failed.
Here is my code
export const DataInput = () => {
const Post = (testTitle, testText) => {
useFirestore().collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}
return(
<Button
variant="primary"
onClick={()=> Post(testTitle, testText)}>
POST data
</Button>
Deleted some of code that does not matter
Hooks can only be called while rendering a component, so they need to be in the body of your component.
export const DataInput = () => {
const firestore = useFirestore();
const Post = (testTitle, testText) => {
firestore.collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}
// etc
}
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, explanation available here)
According to you code samle I may suggest that testTitle, testText available in DataInput in some way, thus you may create onClick handler with useCallback. React will create callback for use as handler, and re-create only when testTitle, testText changed.
import {useCallback} from 'react';
export const DataInput = () => {
const makePost = useCallback(() => {
useFirestore().collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}, [testTitle, testText]);
return (
<Button
variant="primary"
onClick={makePost}
{/* Avoid inline callback declaration */}
>
POST data
</Button>
)
}