global React functions that utilize hooks - javascript

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

Related

why this setState is not firing?

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)

React js - div onClick link to page

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)

How do i convert custom hook into HOC?

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.

What is correct way to fix this 'Invalid Hook Call' error in react app?

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

Export object inside of function

I'm trying to write functional stateless components and in doing so have run into an issue. I wrap by Navigator in a function that takes props as an argument (these are arbitrary). The component that gets wrapped is currently defined as follows since it is used in the reducer for this component.
export const Navigator = StackNavigator(screens, navigatorConfig);
const NavigatorView = (props) => {
return (<Navigator screenProps={{ ...props }}/>);
};
This works, but I'd ideally like to be able to instantiate the Navigator as a local variable in the NavigatorView function, and export there so that I have flexibility towards what attributes I can set at instantiation time (like shown below).
const NavigatorView = (myArg, props) => {
const Navigator = StackNavigator(screens, myArg);
return (<Navigator screenProps={{ ...props }}/>);
};
The problem here is as follows, how can I export Navigator (that's used in the reducer) while keeping it wrapped in a function? I've tried module.exports = Navigator;, export const Navigator... but they do not work. Thanks.
How about making a function that returns a function?
export const generateNavigator = (screens, navigatorConfig) =>
StackNavigator(screens, navigatorConfig);
const NavigatorView = (myArg, props) => {
const Navigator = generateNavigator(screens, myArg);
return (<Navigator screenProps={{ ...props }}/>);
};

Categories