So Im receiving the following error when using a useDispatch inside a useEffect
Line 58:5: React Hook "useDispatch" cannot be called inside a
callback. React Hooks must be called in a React function component or
a custom React Hook function react-hooks/rules-of-hooks
I have a component like follows
import { useSelector, useDispatch } from 'react-redux';
import { myModules } from '#/redux/modules';
const Component = () => {
const images = useSelector(getImages)
useEffect( () => {
useDispatch(myModules.actions.setMaskVisible(true));
}, [images]);
}
setMaskVisible is pretty much
export const SET_MASK_VISIBLE = 'caps/SET_MASK_VISIBLE';
const setMaskVisible = payload => {
return {
type: SET_MASK_VISIBLE,
payload
};
};
export default { setMaskVisible }
Im basically trying to change a redux value, when images update. Why desnt it work?
Have a look at the documentation. You call useDispatch to get a function and call that function:
import { useSelector, useDispatch } from 'react-redux';
import { myModules } from '#/redux/modules';
const Component = () => {
const images = useSelector(getImages)
const dispatch = useDispatch();
useEffect( () => {
dispatch(myModules.actions.setMaskVisible(true));
}, [images]);
}
Related
What's the way to pass a useRef to a custom hook? I'm creating a custom hook to trigger an animation when the user reaches a section with react-intersection-observer and if the user passes more than one the animation doesn't happen again.
If i pass the ref like this:
useAnimation parameters: const useAnimation = (customRef) and the setView method from react-intersection-observer like this: const { ref: customRef, inView: elementIsVisible } = useInView(); i get Parsing error: Identifier 'customRef' has already been declared. If i delete the ref: it happens the same thing.
If i console.log the customRef parameter i get current: undefined
Here is the full code:
useAnimation:
import React, { useEffect, useState, useRef, useContext } from "react";
import { DisableAnimationContext } from "../context/disableAnimationContext";
import { useInView } from "react-intersection-observer";
const useAnimation = (customRef) => {
const { setdisableAnimation } = useContext(DisableAnimationContext);
const { ref: customRef, inView: elementIsVisible } = useInView();
const [animationHappened, setAnimationHappened] = useState(false);
const [animationCounter, setAnimationCounter] = useState({
counter: 0,
});
useEffect(() => {
if (elementIsVisible) {
setdisableAnimation(true);
var currentState = animationCounter.counter;
setAnimationCounter({
...animationCounter,
counter: (currentState += 1),
});
}
if (animationCounter.counter > 0) {
setAnimationHappened(true);
}
}, [elementIsVisible]);
return { elementIsVisible, animationHappened };
};
export default useAnimation;
Here is how i call the custom hook in a component:
import React, { useRef, useContext } from "react";
import useAnimation from "../../../hooks/useAnimation";
const aboutRef = useRef();
console.log(aboutRef);
const { elementIsVisible, animationHappened } = useAnimation(aboutRef);
I've worked a bit with React using JS, but now I'm creating a new project to learn React with Typescript. When I was using JS and needed to use dispatch, I just imported useDispatch from react-redux:
import { useDispatch, useSelector } from 'react-redux';
const AuthAppBar = () => {
const dispatch = useDispatch();
const isUserLogged = useSelector(authSelector.isUserLogged);
const { event } = useGoogleAnalytics();
const userLogout = () => {
const userManager = authManager.getUserManager();
dispatch(authActions.setLoggingOut(true));
userManager.signoutRedirect({ state: { callbackUrl: routes.home.path } });
event('Account', 'Logout');
};
return <></>;
};
But now in this Typescript project the docs says that I need to do like this:
// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
// useGetDeviceById.ts
import { useEffect } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from 'src/hooks';
const useGetDeviceById = () => {
const dispatch = useAppDispatch();
// ...
}
Why do I need to do it this way?
You aren't required to do this, but it's a nice convenience factor, and can prevent some errors later.
Normally, you'd have to do this in every component file:
// import the RootState type
import { RootState, AppDispatch } from "app/store";
import { useSelector, useDispatch } from "react-redux";
function MyComponent() {
// Specifically mark the `state` arg as being of type RootState
const todos = useSelector( (state: RootState) => state.todos);
// Specifically mark `dispatch` as being a type that understands thunks
const dispatch : AppDispatch = useDispatch();
}
Not a huge cost, but it can be annoying to repeat that. In addition, one of the most common problems we see is people not using the store-specific version of the Dispatch type, and having TS tell them they can't dispatch thunks because those aren't plain action objects.
So, for consistency, we recommend that users always create pre-typed versions of the hooks and use them, so they don't accidentally forget to use the right types:
import { useAppSelector, useAppDispatch } from "app/hooks";
function MyComponent() {
// Already knows the state is `RootState`
const todos = useAppSelector(state => state.todos);
// Already knows that `dispatch` can accept a thunk
const dispatch = useAppDispatch();
}
I currently have a component which builds its payload from the context stored in a redux store.
When completing the API call it then dispatches nextTask which updates the state of the context causing the effect to run again and be stuck in an infinite loop.
I need to be able to access the context when building the payload but only re-fire the fetch event when the url changes. I tried useCallback but may have misunderstood.
Here is the component:
import React, { useCallback, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import Axios from "../../config/AxiosConfig";
import { nextTask } from "../../redux/actions/flowActions";
import { RootState } from "../../redux/reducers/rootReducer";
import { JsonRPC } from "../../types/tasks";
const JsonRpc: React.FC<JsonRPC> = ({
url,
method,
payload,
payload_paths,
...rest
}) => {
const dispatch = useDispatch();
const context = useSelector((state: RootState) => state.context.present);
const getPayload = useCallback(() => {
console.log(context);
}, [context]);
useEffect(() => {
const fetch = async () => {
const payload = getPayload();
const { data } = await Axios({
method,
url,
// ...opts,
});
if (data.result) {
dispatch(nextTask());
}
};
try {
fetch();
} catch (e) {
console.log(e);
}
}, [url, dispatch, method, getPayload]);
return null;
};
export default JsonRpc;
I get this error when trying to dispatch an action from my react functional component:
Invalid hook call. Hooks can only be called inside of the body of a function component
This React component does not render anything just listen to keypress event on the page
import { useDispatch } from 'react-redux';
const dispatch = useDispatch();
const {
actions: { moveDown }
} = gameSlice;
type Props = {};
export const Keyboard = ({}: Props) => {
useEffect(() => document.addEventListener('keydown', ({ keyCode }) => dispatch(moveDown())));
return <></>;
};
You will need to use the TypeScript typings for functional components, and provide the props as part of the generic typings. And don't forget to import the required modules.
import React, { useEffect } from 'react';
type Props = {};
export const Keyboard: React.FC<Props> = () => {
useEffect(() => document.addEventListener('keydown', ({ keyCode }) => dispatch(moveDown())));
return <></>;
};
I am trying to play background music in a React Native/Expo app as soon as the homeScreen loads.
Using expo AV library, I set up a MusicContext with a method to start the music. Here is my MusicContext.js
import React, {
useState,
useCallback,
useMemo,
useContext,
useEffect,
} from "react"
import PropTypes from "prop-types"
import { Audio } from "expo-av"
const initialState = {
playMusic: false,
setPlayMusic: () => null,
}
export const MusicContext = React.createContext(initialState)
const startMusic = async () => {
let mainTheme = null
if (!mainTheme) {
mainTheme = new Audio.Sound()
try {
console.log("trying")
await mainTheme.loadAsync(require("../assets/sounds/MainTheme.mp3"))
mainTheme.setStatusAsync({ isLooping: true })
} catch (error) {
console.log("Couldnt load main theme")
return
}
}
}
export const MusicProvider = props => {
const [playMusic, setPlayMusic] = useState(initialState.playMusic)
return (
<MusicContext.Provider
value={{
playMusic,
setPlayMusic,
startMusic,
}}
>
{props.children}
</MusicContext.Provider>
)
}
MusicContext.propTypes = {
children: PropTypes.node.isRequired,
}
I then call the the startMusic method in useEffect on my home screen.
const HomeScreen = () => {
const { startMusic } = useContext(MusicContext)
useEffect(() => {
console.log("running")
startMusic()
}, [])
I see all of the console.log output so I know it is running, but the music never plays. What am I missing here?
Is there a reason you aren't putting the startMusic in initialState?
When I put the function in the initalState, it gets called properly.
To answer your question more generally, you either need to wrap an aysnc function in an IIFE or have it as a separate function that is called.
I usually wrap as IIFE.