How to control react router using global JavaScript function like window object? - javascript

I know about the navigate hook but I need to control the route from window object that means it has to be a global function. In vuejs, it was easy like,
window.pushpath = function(path) { router.push("/"+path) }
how can i achieve this kind of behaviour in react? I cant use react hooks in js as it is forbidden to use react hooks without using it inside component.

A trivial solution could be to use a useEffect hook to set the window.pushpath function using the navigate function returned from the useNavigate hook.
Example:
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
...
const navigate = useNavigate();
useEffect(() => {
window.pushpath = function(path, options) {
navigate(path, options);
}
}, []);
This logic can only work so long as there is a router component higher in the ReactTree than the component running this effect, i.e. the Router must be in the parent component or higher.
Any linters may also complain about a missing dependency on the navigate function. It is, AFAIK, a stable reference, but should be ok to add.
useEffect(() => {
window.pushpath = function(path, options) {
navigate(path, options);
}
}, [navigate]);
Now that window.pushpath is updated, it can be called and passed the same arguments as the navigate function.
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}

Related

running hook inside _app.tsx Next.js

I am having issues with rendering a hook that I use to determine if my components are inside the screens view so that I can animate them. I run this hook in _app.tsx but the function does not run when routing to another page. If I put a console.log() in my _app.tsx it always runs but not mu custom hook.
If I refresh the page my hook runs as it should but I have to manually refresh the page in order for it to actually run and animate my animations.
What I have tried:
Is to run my hook in a component that I use on all of my pages it works but it takes a split second for it to load and the animations does not run smoothly. When my custom hook is in _app.tsx it does actually run smoothly when I refresh the page manually. That's why I feel like this is the place to have my hook or am I wrong? Maybe this isn't the most optimal way to do this.
The final goal is to have all may animations run smoothly. I want them to run when the component is in View and I want the animations to run again when I navigate to another page.
My _app.tsx looks like this:
import type { AppProps } from 'next/app'
import { ThemeProvider } from 'styled-components'
import GlobalStyle from '../components/GlobalStyles'
import { useInView } from '../hooks/useInView'
import { theme } from '../theme/theme'
export default function App({ Component, pageProps }: AppProps) {
useInView()
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Component {...pageProps} />
</ThemeProvider>
)
}
My custom hook (useInView()) looks like this:
import { useEffect } from 'react'
export const useInView = () => {
useEffect(() => {
function inView() {
const observer = new IntersectionObserver(
(intersections) => {
intersections.forEach(({ target, isIntersecting }) => {
if (isIntersecting) {
target.classList.add('in-view')
} else {
target.classList.remove('in-view')
}
})
},
{
threshold: 0,
}
)
document.querySelectorAll('.scroll-spy').forEach((element) => {
observer.observe(element)
})
}
inView()
}, [])
}
When you need to tell a hook to re-render when the path changes, implement a useEffect with the pathname as a dependency. Implementation is slightly different depending on if you use Next.js or React.
next.js
Take a look at the usePathname hook. Simply applied as such:
import { usePathname } from 'next/navigation';
Then inside your component you have access to the pathname:
const pathname = usePathname();
Then check when it changes before doing something:
useEffect(() => {
inView();
}, [pathname])
react.js
Take a look at the useLocation hook. It provides the pathname property.
import useLocation from 'react-router-dom'
Then inside your bring the location in:
const location = useLocation();
Then check when it changes before doing something:
useEffect(() => {
inView();
}, [location.pathname])
You'll need to slightly re-adjust where you define your inView() function but this is essentially how you do something when the view changes.
I also created a working sandbox that demonstrates IntersectionObserver observer and how to implement it using React components in case you're struggling with that. It shows an alert when the green box's visibility changes.

I have this React Invalid Hook Call Error that I'm getting and I wanted to see if I was breaking the rules of hooks or is it something else?

I'm getting the error below.
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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See (link I couldn't add) for tips about how to debug and fix this problem.
You might have mismatching versions of React and the renderer (such as React DOM)
Here is my code. Is it breaking rules of hooks or is the issue something else?
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useData } from 'useData';
export const checkReference = ({
refId,
}) => {
const data = useData(); //useContext hook
let refData = {};
if (refId) refData = data.getReference(refId);
useEffect(() => {
console.log('INITIAL LOGIC');
if (refData.parameter){
console.log('SECONDARY LOGIC', refData);
}
}, [])
checkReference.propTypes = {
refId: PropTypes.string,
}
checkReference.defaultProps = {
refId: null,
}
}
I am calling it from another file using
checkReference('page-name');
I've got the same issue in my Next.js app. It was a cache related issue. you can try these steps.
Delete node_modules and .next folders
Run npm install or yarn
Start your project again
React hooks are intended to be used inside Functional components like:
export const CustomComponent = () => {}
export function CustomComponent() {}
They cannot be used in normal function because it won't exist inside the context of a React component. If you want to use things like useState or useEffect inside functions defined outside a component, you have to create a custom hook. That is, create a function with the use prefix (e.g. useCheckReference) and then use it like:
export const MyComponent = () => {
const reference = useCheckReference()
}
In that way React knows that that function is presumably gonna be called inside a component and the use of hooks is reliable, also is going to make some optimizations related to hooks and components life cycle.

Can I use hooks inside a function (not a functional component)

Here is where I would like to use my hook so that I can change one state during the run of the app.
export const simpleFunction = () => (state) => {
// here is the hook
}
I know react hooks should be used in functional components, but what about the case stated above.
Yes you can! Those functions are then called custom hooks. But it is required to use those custom hooks inside of a functional component. So technically it is now a function using hooks outside of a react component, but they still need to be bound to a component later.
Building your own Hooks lets you extract component logic into reusable functions.
Docs: https://reactjs.org/docs/hooks-custom.html
Example from the docs
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
Because your descriptions is so short I don't know if this is what you are looking for. Hope I could help :)

How to programmatically navigate to other page in Next.js?

After a post request to an external API, I would like to redirect back to the homepage. I do have some knowledge with React and this is my first time using Next.js. Here is the code:
export default function New({genres}) {
const createMovie = (values) => {
console.log(values);
axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}/movies`, {
title: values.title,
description: values.description,
genres: values.genres,
release_date: values.release_date,
cover_url: values.cover_url
}).then(res => {
const router = useRouter();
router.push('/');
})
}
As you can see I used router.push() but I get this error:
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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
What is the most efficient way to redirect to other pages in Next.js after a function and/or requests?
You need to move where you call useRouter(). You can keep router.push() where it is.
export default function New({genres}) {
const router = useRouter();
const createMovie = (values) => {...}
}
If you look at the Rules of Hooks, you can only call the hook, useRouter() in this case, at the top level.
I also had my initialization of useRouter in my function. I fixed the same bug by placing that line into my function component instead of my function and calling router.push(...) in the function itself.

Pass react-redux store and dispatch functions via props?

The following React component is given:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { store, StoreState } from "../../redux/actions";
import { setBackgroundAction } from "../../redux/title.actions";
import "./Loader.scss";
interface ReduxProps {
bgClass: string;
}
interface Props extends ReduxProps {
bgChange?: boolean;
}
export default function Loader(props: Props) {
const [bgClassOld, setBgClassOld] = useState<string>("");
const dispatch = useDispatch();
useEffect(() => {
const { bgChange, bgClass } = props;
if (bgChange) {
setBgClassOld(bgClass);
dispatch(setBackgroundAction("bg-white"));
dispatch(setBackgroundAction(bgClassOld));
}
});
return (
<div className="d-flex">
<div className="loader">
<img src="/loadscreen.gif" />
</div>
</div>
);
}
// function mapping(state: StoreState): ReduxProps {
// return {
// bgClass: state.title.backgroundClass,
// };
// }
This is more a theoretical question to see how to actually do the following change:
The component Loader will be imported from another npm package (shared components).
My problem is that I have a redux state in the current implementation included (changed it from Class to Functional component, so thats mapping() is still in there).
As I only import the component in my "main" client, I will not have the whole redux setup in place. So I think I need to pass the store and the dispatch functions via props.
So should I create a prop store for my component, where I pass the redux store when I import the shared component?
Do I also create two props for each dispatch functions?
Does is make sense or would there be a better approach?
You generally shouldn't import the Redux store directly into components. The hooks allow your component to access whatever Redux store has been injected into the component tree by a <Provider>.
You also don't need to pass dispatch as a prop. Any component can call useDispatch(), and dispatch actions to whatever Redux store is actually being used.
If I understand your question, you're planning on importing this component into an existing app, and it sounds like that app is already configured to use (React-)Redux with a <Provider> at the top. If that's the case, then you don't have to do anything else special to make this work. Just call the React-Redux hooks in any of your components.

Categories