Defining hooks inside an array - javascript

I would like to create a bunch of filters to pause or play my Video. My implementation is something like as shown below:
const Player = () => {
const playFilters = [useIsPageVisible(), useIsInViewport(videoElement)];
const player = useInitPlayer();
useEffect(() => {
if (player) {
if (playFilters.every((filter) => filter)) {
player.play()
} else {
player.pause()
}
}
}, [player, playFilters])
return <Player player={player}/>
}
I know defining hooks inside an if condition is not correct. But how about defining them as above? Could it be done in a better way?

In order for React to correctly preserve the state of Hooks, it's crucial that hooks are called in the same order each time a component renders. That's why one of the Rules of Hooks is Don’t call Hooks inside loops, conditions, or nested functions.
Storing hook results in an array does not break that rule (or any other rule), there it is is perfectly fine to do that.
See more details here.

Related

How To Pass State Update To Custom Render Loop In React's `useEffect`?

I often encounter the situation that I build a window.requestAnimationFrame based render loop, but want some states to be exchanged with the inside of the loop. There are a few ways to accommplish that, but I'm not sure which on is the best. My setup usually looks something like this:
function Animation({someState}) {
const loopDataRef = useRef();
useEffect(() => {
//do something initialization work for the renderloop, e.g. getContext, etc.
let frame;
const loop = (cts) => {
frame = window.requestAnimationFrame(loop)
//do some loop work using someState and loopDataRef.current
}
loop();
return () => window.cancelAnimationFrame(frame);
}, []);
}
Note that using someState inside the loop will always use the value from the last ran of the effect, not necessarily the current value. Here are the some obvious solutions:
I can put someState in the dependency array of useEffect, this way the loop will be restarted whenever the state changes. But if the initialization is expensive, for example with WebGL where I create all the textures there and compile the shaders, it doesn't seem very elegant.
I can use two effects, one for the initialization of the loop itself, and another one for just the loop, which stops and starts on every state change. But I still think, ideally an animation loop should run until it stops and not stop/start in between.
Another solution with two effects, but this time I do const someStateRef = useRef() and then create an effect with someState in its dependency array which just writes someState into someStateRef.current so I can use it inside the loop. Here is an example where I implement this. Alternatively one can write someState into someStateRef.current on every render, without another effect. Looks very performant, but not really elegant.
What's the most React way to do it?
I'd go with the 3rd option that you already implemented, as it looks exactly like one example from the React Hooks FAQ (3rd code block):
function Example(props) {
// Keep latest props in a ref.
const latestProps = useRef(props);
useEffect(() => {
latestProps.current = props;
});
useEffect(() => {
function tick() {
// Read latest props at any time
console.log(latestProps.current);
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []); // This effect never re-runs
}
(replacing set/clearInterval with request/cancelAnimationFrame)
For completeness: The first two options suffer from the flaws you mentioned, and writing someState to someStateRef.current on every render without another effect is not recommended.
Avoid reading and updating refs during rendering because this makes your component’s behavior difficult to predict and understand.
source

Rewrite useEffect hook from componentDidUpdate lifecycle method

Suppose we have an input for buyer id and we want to fetch the buyer details each time the buyerId is changed.
The following code looks like this for class components
componentDidUpdate(prevProps,prevState) {
if (this.state.buyerId !== prevState.buyerId) {
this.props.fetchBuyerData(this.state.buyerId); // some random API to search for details of buyer
}
}
But if we want to use useEffect hook inside a functional component how would we control it. How can we compare the previous props with the new props.
If I write it as per my understanding it will be somewhat like this.
useEffect(() => {
props.fetchBuyerData(state.buyerId);
}, [state.buyerId]);
But then react's hooks linter suggests that I need to include props into the dependency array as well and if I include props in the dependency array, useEffect will be called as soon as props changes which is incorrect as per the requirement.
Can someone help me understand why props is required in dependency array if its only purpose is to make an API call.
Also is there any way by which I can control the previous state or props to do a deep comparison or maybe just control the function execution inside useEffect.
Deconstruct props either in the function declaration or inside the component. When fetchBuyerData is used inside the useEffect hook, then only it needs to be listed as a dependency instead of all of props:
// deconstruct in declaration
function MyComponent({ fetchBuyerData }) {
useEffect(() => {
// caveat: something is wrong with the use of `this.state` here
fetchBuyerData(this.state.buyerId);
}, [fetchBuyerData, state.buyerId]);
}
// deconstruct inside component
function MyComponent(props) {
const { fetchBuyerData } = props;
useEffect(() => {
// caveat: something is wrong with the use of `this.state` here
fetchBuyerData(this.state.buyerId);
}, [fetchBuyerData, state.buyerId]);
}
I'd assume you're rewriting your class component info functional one. Then you'd be better off including your fetch request right where you set new state.bayerId (I assume it's not an external prop). Something like:
const [buyerId, setBuyerId] = React.useState('')
const handleOnChange = (ev) => {
const { value } = ev.target
if (value !== buyerId) {
props.fetchBuyerData(value)
setBuyerId(value)
}
...
return (
<input onChange={handleOnChange} value={buyerId} />
...
The code snippet is somewhat suboptimal. For production I'd assume wrap change handler into useCallback for it to not be recreated on each render.

Why doesn't useEffect hook work as expected?

I've never used hooks in React and I'm trying to use useEffect() but I don't seem to get the basics of its correct structure and use.
I was able to achieve the results with plain JavaScript but with useState the state remains untouched.
Then I found useEffect after searching for a while, and this is what I could get it to look like-
// Background Parallax Effect
let [translateY,setTranslateY] = useState(0);
useEffect(()=> {
const getScrollPos = ()=> {
setTranslateY(window.scrollY*(-.2));
requestAnimationFrame(getScrollPos);
}
document.addEventListener("DOMContentLoaded",getScrollPos)
},[translateY]);
I highly suspect that its structure isn't as it is supposed to be.
So I want to know the fixes and how it exactly works to help understand the structure better.
The issue with your first code is that you add translateY as a dependency to useEffect. . You should remove translateY as a dependency and also remove the event listener when the component unmounts. Also you have a requestAnimationCallback within the getScrollPos function which is triggered unconditionally causing infinite loop
useEffect(()=> {
const getScrollPos = ()=> {
setTranslateY(window.scrollY*(-.2));
}
const setScrollPos = () => {
requestAnimationFrame(getScrollPos);
}
document.addEventListener("DOMContentLoaded",setScrollPos);
return () => {
document.removeEventListener("DOMContentLoaded",setScrollPos)
}
},[]);
Note that if you update the state with same value, react prevents a re-render.
In the second code, although you call the state update by using listenScroll directly in render function, it doesn't cause a loop because you would be setting the same value to update state and hence an infinite loop doesn't occur

Does calling setter of useState hook inside if statement imply BREAKING RULES OF HOOKS?

React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Does calling a hook means just calling useState e.g. const [state, useState] = useState(0)?
What about calling setter in conditionals ?
Is this code breaking rules of hooks ?
const [oneHook, setOneHook] = useState(0)
const [anotherHook, setAnotherHook] = useState(false)
if (something) {
setOneHook(1)
setAnotherHook(true)
} else {
setOneHook(0);
setAnotherHook(false)
}
Thanks !
No, that code example does not break the rules of hooks. Every time the component renders there will be exactly the same number of calls to useState in exactly the same order, so that will be fine.
I do want to point out that setting state right away in the body of the component doesn't make much sense. When the component mounts it will start rendering with the initial values from state, but then before it can finish rendering the state has already changed and it has to start over. But presumably that's just an artifact of the example, and in your real code the if would be inside a useEffect or some other practical code location.
React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Alright,the following code shows the example for the above statement. I had the same issue where i needed to set the state inside of a loop, like following
const [someArray, setArray] = useState([]);
someValue.forEach((value, index) => {
setArray([...someArray, value]) //this should be avoided
});
Above thing i have achieved like this following
var temp = [];
var counter = 0;
someValue.forEach((value, index) => {
temp.push(value);
counter++;
if (counter === someValue.length) {
setArray(temp)
}
});
if you are setting a state inside the loop than each time the component re renders which you do not want to get into.
Is this code breaking rules of hooks
No Your code looks fine, as you are setting up the state only based on condition for only once when the component renders

Law-abiding React Hooks

I'm looking for a way to use my custom hook inside the map function as I render the components...
FYI, I'm totally aware of the hooks rule " Don’t call Hooks inside loops, conditions, or nested functions. " But I believe there is a way around it without breaking the rule....
Here's some more info:
We've got a custom hook called useCommaHook as below:
export const useCommaHook = price => {
const priceArray = price.toString().split("")
const beforeComma = priceArray.slice(0, 1).join("")
const afterComma = priceArray.slice(1, 4).join("")
return `${beforeComma},${afterComma}`
}
Which can be used to add comma for currencies while rendering:
const renderTours = data => {
return data.map((element, idx) => {
return (
<span className="filtered-tour__description-price">
*** --> We'd like to use our hook here to get comma for every price in the loop***
{`from ${element.node.priceFrom}`
</span>
)
})
}
As commented above we'd like to apply that filter to all the node prices in our array.
How would you go about solving such an issue ? any idea is appreciated.
useCommaHook is not a hook! It doesn't call any of the native hooks (useState, ...). Therefore the rules of hooks do not apply.
Building your own Hooks lets you extract component logic into reusable functions. But here your useCommaHook is a simple functional component its function is to
return `${beforeComma},${afterComma}`
You didn't even use native hooks inside useCommaHook and there is not state in your function and so State Hook is not applicable in your code.

Categories