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.
Related
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.
I have the following component where within the useEffect, I am calling some data reading related
functions meant to happen once on load.
The problem is, some of the prop data are not available at this stage (still undefined) like the prodData and index.
They are only available when I get into the Nested components like <NestedComponent1 />.
I wish to move this logic into the nested components which will resolve this issue.
But I do not want to repeat these code inside the useEffect for each component. Instead looking to write these 7 lines once maybe in a function
and just call it with the 3 NestedComponents.
Issue is that there is a higher order function wrapping here plus all the values like prodData and index is coming from Redux store.
I can't just move all these logic inside useEffect into a normal JS function and instead need a functional component for this.
And if I make a functional component to perform these operations, I can't call it in the useEffect for each of the NestedComponents.
Cos this is not valid syntax.
React.useEffect(() => {
<NewlyCreatedComponentWithReadingFunctionality />
}, []);
Thus my query is, is there a way I could write a functional component which has the data reading logic inside its useEffect.
And then extend this functional component for each of the functional components so that the useEffect would just fire
when each of these NestedComponents are called?
Doesn't seem to be possible to do this thus looking for alternatives.
This is the existing component where some of these prop values are undefined at this stage.
const MyComponent = ({
prodData,
index,
country,
highOrder: {
AHigherOrderComponent,
},
}) => {
// this is the logic which I am looking to write once and be
// repeatable for all the NestedComponent{1,2,3}s below.
React.useEffect(() => {
const [, code] = country.split('-');
const sampleData = prodData[index].sampleData = sampleData;
const period = prodData[index].period = period;
const indication = prodData[index].indication = indication;
AHigherOrderComponent(someReadDataFunction(code, sampleData));
AHigherOrderComponent(someReadDataFunction(code, period);
AHigherOrderComponent(someReadDataFunction(code, indication);
}, []);
return (
{/* other logics not relevant */}
<div>
<div>
<NestedComponent1 />
<NestedComponent2 />
<NestedComponent3 />
</div>
</div>
);
};
export default connect( // redux connect
({
country,
prodData,
index,
}) => ({
country,
prodData,
index,
})
)(withHighOrder(MyComponent));
React components implement a pattern called composition. There are a few ways to share state between parts of your React application but whenever you have to remember some global state and offer some shared functionality, I would try and manage that logic inside a context provider.
I would try the following:
Wrap all your mentioned components inside a context provider component
Offer the someReadDataFunction as a callback function as part of the context
Within your provider, manage react state, e.g. functionHasBeenCalled that remembers if someReadDataFunction has been called already
Set functionHasBeenCalled to true inside someReadDataFunction
Call someReadDataFunction inside your components within a useEffect based on the props data
This way, your application globally remembers if the function has been executed already but you can still use the latest data within your useEffect within your components to call someReadDataFunction.
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.
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
I have some doubts regarding performance when a functional component has inner functions.
When having a functional component with an inner function, does that inner function get re-created over and over again on every render?
In that case, if I was to memoize the component with React.memo(), would that function be memoized as well?
Here's a dummy example of what I'm referring to:
const MyComponent = React.memo(({genre, name}) => {
const someFunction = genre => {
return (genre === 'male') ? 'Mr.' : 'Ms.';
}
return (
<h1>Hello {someFunction(genre)} {name}!</h1>
);
});
From the react Hooks docs :
Are Hooks slow because of creating functions in render?
No. In modern
browsers, the raw performance of closures compared to classes doesn’t
differ significantly except in extreme scenarios.
[...]
Traditionally, performance concerns around inline functions in React
have been related to how passing new callbacks on each render breaks
shouldComponentUpdate optimizations in child components. Hooks
approach this problem from three sides.
The useCallback Hook lets you keep the same callback reference between
re-renders so that shouldComponentUpdate continues to work:
The useMemo Hook makes it easier to control when individual children
update, reducing the need for pure components.
Finally, the useReducer Hook reduces the need to pass callbacks
deeply, as explained below.
Read more here.
Also in your example, you can put the inner function outside the function component, because it does not use anything from inside the component, like this :
const someFunction = genre => {
return (genre === 'male') ? 'Mr.' : 'Ms.';
}
const MyComponent = React.memo(({genre, name}) => {
return (
<h1>Hello {someFunction(genre)} {name}!</h1>
);
});