Call a Redux Action inside a useEffect - reactjs

My objective here is to call an action inside a useEffect.
const ShowTodos = (props) =>{
useEffect(()=>{
props.fetchTodos()
},[])
}
const mapStateToProps = (state)=>{
return {
todos:Object.values(state.todos),
currentUserId:state.authenticate.userId
}
}
export default connect(mapStateToProps,{fetchTodos})(ShowTodos)
It works fine but I got a warning
React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array react-hooks/exhaustive-deps.
But if I'm going to add props as my second parameter in my useEffects then it will run endlessly.
My first workaround here is to use the useRef but it seems that it will always re-render thus re-setup again the useRef which I think is not good in terms of optimization.
const ref = useRef();
ref.current = props;
console.log(ref)
useEffect(()=>{
ref.current.fetchTodos()
},[])
Is there any other workaround here ?

That is an eslint warning that you get if any of the dependency within useEffect is not part of the dependency array.
In your case you are using props.fetchTodos inside useEffect and the eslint warning prompts you to provide props as a dependency so that if props changes, the useEffect function takes the updated props from its closure.
However since fetchTodos is not gonna change in your app lifecycle and you want to run the effect only once you can disable the rule for your case.
const ShowTodos = (props) =>{
const { fetchTodos } = props
useEffect(()=>{
fetchTodos()
// eslint-disable-next-line import/no-extraneous-dependencies
},[])
}
const mapStateToProps = (state)=>{
return {
todos:Object.values(state.todos),
currentUserId:state.authenticate.userId
}
}
export default connect(mapStateToProps,{fetchTodos})(ShowTodos)
You however can solve the problem without disabling the rule like
const ShowTodos = (props) =>{
const { fetchTodos } = props
useEffect(()=>{
fetchTodos()
},[fetchTodos])
}
I however will recommend that you know when exactly should you disable the rule or pass the values to the dependency array.

You have to add fetchTodos to dependencies.
const ShowTodos = ({ fetchTodos }) => {
useEffect(() => {
fetchTodos();
}, [fetchTodos])
...
}
or like this.
const ShowTodos = (props) => {
const { fetchTodos } = props;
useEffect(() => {
fetchTodos();
}, [fetchTodos])
...
}

Related

react-hooks/exhaustive-deps and empty dependency lists for "on mount" [duplicate]

This is a React style question.
TL;DR Take the set function from React's useState. If that function "changed" every render, what's the best way to use it in a useEffect, with the Effect running only one time?
Explanation We have a useEffect that needs to run once (it fetches Firebase data) and then set that data in application state.
Here is a simplified example. We're using little-state-machine, and updateProfileData is an action to update the "profile" section of our JSON state.
const MyComponent = () => {
const { actions, state } = useStateMachine({updateProfileData, updateLoginData});
useEffect(() => {
const get_data_async = () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
};
get_data_async();
}, []);
return (
<p>Hello, world!</p>
);
}
However, ESLint doesn't like this:
React Hook useEffect has a missing dependency: 'actions'. Either include it or remove the dependency array
Which makes sense. The issue is this: actions changes every render -- and updating state causes a rerender. An infinite loop.
Dereferencing updateProfileData doesn't work either.
Is it good practice to use something like this: a single-run useEffect?
Concept code that may / may not work:
const useSingleEffect = (fxn, dependencies) => {
const [ hasRun, setHasRun ] = useState(false);
useEffect(() => {
if(!hasRun) {
fxn();
setHasRun(true);
}
}, [...dependencies, hasRun]);
};
// then, in a component:
const MyComponent = () => {
const { actions, state } = useStateMachine({updateProfileData, updateLoginData});
useSingleEffect(async () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
}, [actions]);
return (
<p>Hello, world!</p>
);
}
But at that point, why even care about the dependency array? The initial code shown works and makes sense (closures guarantee the correct variables / functions), ESLint just recommends not to do it.
It's like if the second return value of React useState changed every render:
const [ foo, setFoo ] = useState(null);
// ^ this one
If that changed every render, how do we run an Effect with it once?
Ignore the eslint rule for line
If you truly want the effect to run only once exactly when the component mounts then you are correct to use an empty dependency array. You can disable the eslint rule for that line to ignore it.
useEffect(() => {
const get_data_async = () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
};
get_data_async();
// NOTE: Run effect once on component mount, please
// recheck dependencies if effect is updated.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Note: If you later update the effect and it needs to run after other dependencies then this disabled comment can potentially mask future bugs, so I suggest leaving a rather overt comment as for the reason to override the established linting rule.
Custom hook logic
Alternatively you can use a react ref to signify the initial render. This is preferable to using some state to hold the value as updating it would trigger unnecessary render.
const MyComponent = () => {
const { actions, state } = useStateMachine({updateProfileData, updateLoginData});
const initialRenderRef = useRef(true);
useEffect(() => {
const get_data_async = () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
};
if (initialRenderRef.current) {
initialRenderRef.current = false;
get_data_async();
}
}, [actions]); // <-- and any other dependencies the linter complains about
return (
<p>Hello, world!</p>
);
}
And yes, absolutely you can factor this "single-run logic" into a custom hook if it is a pattern you find used over and over in your codebase.

ReactJS, useEffect without depts vs const

I've just see this code:
const defaultState = { // some object };
const SomeComponent = ({
complete
}) => {
const [state, setState] = useState(defaultState);
const {
color,
icon
} = state;
useEffect(() => {
if (complete) {
setState({
...state,
color: '#61a60e',
});
} else {
setState({
...state,
color: 'red',
});
}
}, []);
// etc
I wonder, is that really necesary that useEffect?
The if statement inside the useEffect has a control value that comes as prop on the componente (complete), so in my opinion that code would be the same than this:
const defaultState = { // some object };
const SomeComponent = ({
complete
}) => {
const [state, setState] = useState(defaultState);
const {
color,
icon
} = state;
setState({
...state,
color: complete ? '#61a60e' : 'red'
});
I'm ok? Or there's something about useEffect that I'm not considering.
First, You need to understand about useEffect: https://reactjs.org/docs/hooks-effect.html
In your example, useEffect was call without dependencies. So it likes componentDidMout and only calls one time. Base on the complete props. It will update color for state. It works fine.
About your update without useEffect. setState call directly in the component. So every component render (state or props change). The setState will be called => state change => component render => ... => infinite rerender. It's terrible. Never do that.
Note: If you use setState in useEffect, you need to add [dependencies] to avoid infinite loops. dependencies maybe have value or not(like example above). Make sure you understand dependencies in useEffect before using it.

Export function from inside a React function

I have a function that handles React Native location. For demonstration:
const useLocation = () => {
const [fetchingLocation, setFetchingLocation] = useState(true);
...
const changeSystemPermissions = useCallback(() => {...});
useEffect(() => {
//does many things
}, [...])
}
I need to have the function changeSystemPermissions inside useLocation as it uses the state.
I realize that I can export the changeSystemPermissions function as a const with a return [changeSystemPermissions, ...] and then import it in another component with:
const [
changeSystemPermissions,
...
] = useLocation();
However, it will ALSO run the useEffect function. I do want it to run once, but I need to access changeSystemPermissions in several other components and I don't want the useEffect to run multiple times.
I was thinking I will just take out the changeSystemPermissions function outside of useLocation, but it needs to use the state. I suppose I COULD pass the state vars into the changeSystemPermissions when it is outside useLocation, but that would be verbose and ugly.
How can I export changeSystemPermissions and just that function without having to import the whole useLocation function?
Can you move the useEffect to the component one ?
const useLocation = () => {
const [fetchingLocation, setFetchingLocation] = useState(true);
const changeSystemPermissions = useCallback(() => {
...
});
const funcToExecute = useCallback(() => {
....
}, []);
return { changeSystemPermissions, funcToExecute }
}
And put it in the component :
const {
changeSystemPermissions,
funcToExecute,
} = useLocation();
useEffect(() => {
funcToExecute()
}, [...])
Also, if you really need the useEffect to be in the custom hook,
maybe you can add a param to this hook.
const useLocation = (shouldTriggerEffect) => {
const [fetchingLocation, setFetchingLocation] = useState(true);
const changeSystemPermissions = useCallback(() => {
...
});
useEffect(() => {
if (shouldTriggerEffect) {
...
}
}, [shouldTriggerEffect])
return { changeSystemPermissions, funcToExecute }
}
And then in the component,
const {
changeSystemPermissions,
} = useLocation(false);
Tell me if I misunderstood something or if it helps :)
When ever you call a hook inside a React functional component, it will create a new state for that hook and not sharing among components. But there is a library which could help you achieve that:
https://github.com/betula/use-between
You could follow example to use this library or maybe just read the code and utilize the approach for your case to share the hook state between components.

Is this useEffect() unecessarily expensive?

This is a very simple version of my code:
const MyComponent = (props)=>{
const [randVar, setRandVar] = useState(null);
const randomFunction = ()=>{
console.log(randVar, props);
};
useCustomHook = ()=>{
useEffect(()=>{
document.addEventListener('keydown', randomFunction);
return ()=>{
document.removeEventListener('keydown', randomFunction);
}
}, [props, randVar]);
}
useCustomHook();
...
};
I want randomFunction to log accurate values for randVar and props (i.e. logs update when those variables change values), but I'm concerned that adding an event listener and then dismounting it every time they change is really inefficient.
Is there another way to get randomFunction to log updated values without adding props and randVar as dependencies in useEffect?
A few things, your useEffect does not need to be in that custom hook at all... It should probably look like this:
const MyComponent = (props)=>{
const [randVar, setRandVar] = useState(null);
const randomFunction = useCallback(()=>{
console.log(randVar, props);
}, [props, randVar]);
useEffect(()=>{
document.addEventListener('keydown', randomFunction);
return ()=>{
document.removeEventListener('keydown', randomFunction);
}
}, [randomFunction]);
...
};
useCallback will keep your function from being redefined on every render, and is the correct dependency for that useEffect as well. The only thing bad about the performance ehre is that you are logging props so it needs to be in the dependency array of the useCallback and since it is an object that may get redefined a lot, it will cause your useCallback to get redefined on nearly every render, which will then cause your useEffect to be fired on nearly every render.
My only suggestion there would be to separate your logging of props from where you log changes to randVar.
I don't think you need randVar as a dependency and if you had ESLint, it would tell you the same since you never acutally reference randVar in the effect.
If you don't want the function to get rebuilt over and over, you need to either memoize it or useRef. Unfortunately, once it's a ref it's not reactive. Maybe I'm overthinking this, but you could have a ref that you update in an effect with the new value and print out the value of that ref in the callback you pass to randFunction. See my example below.
I don't like how I did this, but it doesn't remake the function. I'm trying to think how I could do it better, but I think this works how you want.
const {
useRef,
useState,
useEffect
} = React;
const useCustomHook = (fn) => {
useEffect(() => {
document.addEventListener("keydown", fn);
return () => {
document.removeEventListener("keydown", fn);
};
}, [fn]);
};
const MyComponent = (props) => {
const [randVar, setRandVar] = useState("");
const randVarRef = useRef("");
useEffect(() => {
randVarRef.current = randVar;
}, [randVar]);
const randFn = useRef(() => {
console.log(randVarRef.current);
});
useCustomHook(randFn.current);
return React.createElement("input", {
type: "text",
value: randVar,
onChange: e => {
setRandVar(e.target.value);
}
});
};
ReactDOM.render(
React.createElement(MyComponent),
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="app"></div>

useEffect props callback function causing infinite loop

I have a problem very similar to this - How do I fix missing dependency in React Hook useEffect.
There is one key difference - I am passing a fetch function to a child component to be called from useEffect, so I can't simply move the function into the body of the effect. The fetch function is re-created every render and causes an infinite loop. I have other local component state that I want to cause the effect to fire.
I basically have a Container Component and a Presentational component. MyPage is the parent of MyGrid and sets up all the redux state:
const MyPage = () => {
const dispatch = useDispatch();
const items= useSelector(selectors.getItems);
const fetching = useSelector(selectors.getFetching);
const fetchItems = opts => dispatch(actions.fetchItems(opts));
return (
<>
{fetching && <div>Loading...</div>}
<h1>Items</h1>
<MyGrid
items={items}
fetchItems={fetchItems}
fetching={fetching}
/>
</>
);
}
const MyGrid = ({ fetchItems, items, fetching }) => {
const [skip, setSkip] = useState(0);
const take = 100;
const [sorts, setSorts] = useState([]);
// when query opts change, get data
useEffect(() => {
const options = { skip, take };
const sortString = getSortString(sorts);
if (sortString) options['sort'] = sortString;
fetchItems(options);
}, [fetchItems, skip, sorts]);
In "MyGrid" "skip" and "sorts" can change, and should make the effect fire.
"fetchItems" is re-created everytime and causes an infinite loop. This
is my problem.
Now, the eslint react-hooks/exhaustive-deps rule is making me put fetchItems in the dependency list. I have prettier setup to autofix on save which makes it worse.
I know the Container/Presentational pattern is out of style with hooks, but it works good for my situation - I may allow swapping out MyGrid for MyList dynamically and don't want to repeat all the redux stuff in each child component.
I tried to useCallback and useMemo, but eslint just makes me put all the same dependencies in it's dependency array parameter.
Is there a way other than disabling the eslint rule
// eslint-disable-next-line react-hooks/exhaustive-deps
to make this work?
There are two ways, you can make it work.
Firstly, using useCallback for fetchItem like
const fetchItems = useCallback(opts => dispatch(actions.fetchItems(opts)), [dispatch, actions]);
Secondly using dispatch directly in child component
const dispatch = useDispatch();
useEffect(() => {
const options = { skip, take };
const sortString = getSortString(sorts);
if (sortString) options['sort'] = sortString;
dispatch(actions.fetchItems(options));
}, [dispatch, actions, skip, sorts]);

Resources