Rewrite useEffect hook from componentDidUpdate lifecycle method - javascript

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.

Related

Alternative to extending a functional component

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.

Can't find the way to useSelector from redux-toolkit within event handler and pass params to it

There is an event handler for click and when it triggered i want to pull specific data from redux using selector where all logic many-to-many is implemented. I need to pass id to it in order to receive its individual data. Based on rules of the react the hooks can be called in function that is neither a React function component nor a custom React Hook function.
So what is the way to solve my problem ?
const handleMediaItemClick = (media: any): void => {
// For example i check media type and use this selector to pull redux data by id
const data = useSelector(playlistWithMediaSelector(imedia.id));
};
As stated in the error message, you cannot call hooks inside functions. You call a hook inside a functional component and use that value inside the function. The useSelector hook updates the variable each time the state changes and renders that component.
Also, when you get data with useSelector, you should write the reducer name you need from the redux state.
const CustomComponent = () => {
// The data will be updated on each state change and the component will be rendered
const data = useSelector((state) => state.REDUCER_NAME);
const handleMediaItemClick = () => {
console.log(data);
};
}
You can check this page for more information.https://react-redux.js.org/api/hooks#useselector
You should probably use local state value to track that.
const Component = () => {
const [imediaId, setImediaId] = useState(null);
const data = useSelector(playlistWithMediaSelector(imediaId));
function handleMediaClick(id) {
setImediaId(id)
}
useEffect(() => {
// do something on data
}, [imediaId, data])
return <div>...</div>
}
Does that help?
EDIT: I gather that what you want to do is to be able to call the selector where you need. Something like (considering the code above) data(id) in handleMediaClick. I'd bet you gotta return a curried function from useSelector, rather than value. Then you would call it. Alas, I haven't figured out how to that, if it's at all possible and whether it's an acceptable pattern or not.

How to access prevProps' in React Hooks when prevProps' data type is object or array

My question is this, I'm having an array I need to monitor its change in my componentDidUpdate lifecycle, now I'm trying to rewrite the component in hooks, I'm having issue in doing so in hooks, a lot of the solutions I found on the internet suggests to create a customized usePrevious hook like this
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
But I found that this hook can only deal with primitive data type, it doesn't seem to work with object or array...
componentDidUpdate(prevProps) {
if (!_.isEqual(myArray, prevProps.myArray)) {
// do something...
}
}
From what you're saying I think using a useEffect hook that has the prop you want to watch for as dependency and the logic inside should do the trick, you can check the React Docs for a more detailed explanation, but basically, it would look like this
useEffect(() => {
// code to run when it changes
}, [yourVar]);

Should I use useselector/useDispatch instead of mapStateToProps

When creating a React app, if I use the hook useSelector, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch inside a callback (after an assignment in the root level):
function Modal({ children }) {
const isOpen = useSelector(state => state.isOpen);
const dispatch = useDispatch();
function handleModalToggeled() {
// using updated data from store state in a callback
if(isOpen) {
// writing to state, leading to a rerender
dispatch({type: "CLOSE_MODAL"});
return;
}
// writing to state, leading to a rerender
dispatch({type: "OPEN_MODAL"});
}
// using updated data from store state in render
return (isOpen ? (
<div>
{children}
<button onClick={handleModalToggeled}>close modal</button>
</div>
) : (
<button onClick={handleModalToggeled}>open modal</button>
);
);
}
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
Decoupling: with mapStateToProps, container logic (the way store data is injected into the component) is separate from the view logic (component rendering).
useSelector represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source
DX (Developer experience): using the connect function usually means there should be another additional container component for each connected component, where using the useSelector and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.
"Stale props" and "Zombie child": there are some weird edge cases with useSelector, if it depends on props, where useSelector can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect version. verdict: connect is slightly more stable than hooks. source
Performance optimizations: both support performance optimizations in different ways: connect has some advanced techniques, using merge props and other options hidden in the connect function. useSelector accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.
Types: using typescript with connect is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.
The future of React: Hooks are the future of react. This may seam like an odd argument, but change to the ecosystem is right around the corner with "Concurrent mode" and "Server components". While class components will still be supported in future React versions, new features may rely solely on hooks. This change will of course also affect third party libraries in the eco system, such as React-Redux. verdict: hooks are more future proof.
TL;DR - Final verdict: each method has its merits. connect is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector() cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad
const MyComponent = () => {
if (condition) {
// can't do this
const data = useSelector(mySelector);
console.log(data);
}
return null;
}
---
// good
const MyComponent = () => {
const data = useSelector(mySelector);
if (condition) {
console.log(data); // using data in condition
}
return null;
}
If anything, mapStateToPtops is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => {
const data = useSelector(mySelector);
const latestData = useRef()
latestData.current = data
return (
<button
onClick={() => {
setTimeout(() => {
console.log(latestData.current) // always refers to latest data
}, 5000)
}}
/>
)
}
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
You save time by not writing connect function any time you need to access store, and removing it when you no longer need to access store. No endless wrappers in react devtools
You have clear distinction and no conflicts between props coming from connect, props coming from parent and props injected by wrappers from 3rd party libraries
Sometimes you (or fellow developers you work with) would choose unclear names for props in mapStateToProps and you will have to scroll all the way to mapStateToProps in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same line
By using hooks you get general advantages of hooks, the biggest of which is being able couple together and reuse related stateful logic in multiple components
With mapStateToProps you usually have to deal with mapDispatchToProps which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names
.
import { getUsers } from 'actions/user'
class MyComponent extends Component {
render() {
// shadowed variable getUsers, now you either rename it
// or call it like this.props.getUsers
// or change import to asterisk, and neither option is good
const { getUsers } = this.props
// ...
}
}
const mapDispatchToProps = {
getUsers,
}
export default connect(null, mapDispatchToProps)(MyComponent)
See EDIT 2 at the end for the final answer
Since no one knows how to answer, it seems like the best answer is that you should NOT be using useselector when you need information in other places other than the root level of your component. Since you don't know if the component will change in the future, just don't use useselector at all.
If someone has a better answer than this, I'll change the accepted answer.
Edit: Some answers were added, but they just emphasize why you shouldn't be using useselector at all, until the day when the rules of hooks will change, and you'll be able to use it in a callback as well. That being said, if you don't want to use it in a callback, it could be a good solution for you.
EDIT 2: An answer with examples of all that I wanted was added and showed how useSelector and useDispatch are easier to use.
The redux state returned from the useSelector hook can be passed around anywhere else just like its done for mapStateToProps. Example: It can be passed to another function too. Only constraint being that the hook rules has to be followed during its declaration:
It has to be declared only within a functional component.
During declaration, it can not be inside any conditional block . Sample code below
function test(displayText) {
return (<div>{displayText}</div>);
}
export function App(props) {
const displayReady = useSelector(state => {
return state.readyFlag;
});
const displayText = useSelector(state => {
return state.displayText;
});
if(displayReady) {
return
(<div>
Outer
{test(displayText)}
</div>);
}
else {
return null;
}
}
EDIT: Since OP has asked a specific question - which is about using it within a callback, I would like to add a specific code.In summary, I do not see anything that stops us from using useSelector hook output in a callback. Please see the sample code below, its a snippet from my own code that demonstrates this particular use case.
export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
return state && state.selectedFacets;
});
//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
const postParams = constructParticipantListQueryParams(searchCriteria);
const options = {
headers: {
'Content-Type': 'application/json'
},
validateStatus: () => true
};
var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
.then(function(response)
{
if(response.status === HTTP_STATUS_CODE_SUCCESS) {
console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
}
})
.catch(function(error) {
});
}, []);
}
For callback functions you can use the value returned from useSelector the same way you would use the value from useState.
const ExampleComponent = () => {
// use hook to get data from redux state.
const stateData = useSelector(state => state.data);
// use hook to get dispatch for redux store.
// this allows actions to be dispatched.
const dispatch = useDispatch();
// Create a non-memoized callback function using stateData.
// This function is recreated every rerender, a change in
// state.data in the redux store will cause a rerender.
const callbackWithoutMemo = (event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
};
// Create a memoized callback function using stateData.
// This function is recreated whenever a value in the
// dependency array changes (reference comparison).
const callbackWithMemo = useCallback((event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
}, [stateData, doSomething, doSomethingElse]);
// Use the callbacks.
return (
<>
<div onClick={callbackWithoutMemo}>
Click me
</div>
<div onClick={callbackWithMemo}>
Click me
</div>
</>
)
};
Rules of hooks says you must use it at the root of your component, meaning you CANT use it anywhere.
As Max stated in his answer just means that the hook statement itself must not be dynamic / conditional. This is because the order of the base hooks (react's internal hooks: useState, etc) is used by the backing framework to populate the stored data each render.
The values from hooks can be used where ever you like.
While I doubt this will be close to answering your complete question, callbacks keep coming up and no examples had been posted.
not the answer but this hook can be very helpful if you want to get decoupled nature of mapDispatchToProps while keeping simplicity and dev experience of hooks:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
the reason I don't mention one for mapStatesToProps is that useSelector itself is more store-logic-decoupling than mapStatesToProps so don't see any advantage for mapStatesToProps. Of course I dont mean using useSelector directly but instead create a wrapper on it in your store files (e.g. in reducer file) and import from there, like this:
// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)

How to implement componentDidMount with hooks in React to be in line with the EsLint rule "react-hooks/exhaustive-deps": "warn"?

According to the official React documentation, componentDidMount is translated in hooks as:
useEffect(() => {
//code here
},[])
So assuming I want to do an api call within this hook:
useEffect(() => {
getActiveUser();
},[])
After adding the eslint rule "react-hooks/exhaustive-deps", this is a lint error. In order to silence it I can just drop the getActiveUser function inside the array and everything works just fine.
But does that go against the documentation? I was under the impression that the array checks for prop changes. I would like also to point out that the API call is being made without a prop/id, so I could understand the fact of having to do something like that:
useEffect(() => {
getActiveUser(someId);
},[getActiveUser, someId])
So what's going on here? Adding the Eslint rule mean that the array inside the effect can't be empty again?
It matters where getActiveUser is declared. The question doesn't specify, but I assume your component looks something like this:
const MyComponent = (props) => {
const getActiveUser() => {
//...
}
useEffect(() => {
getActiveUser();
}, []) // Lint error.
return <></>;
}
If instead your component looked like this, you wouldn't get a linter error:
const getActiveUser() => {
//...
}
const MyComponent = (props) => {
useEffect(() => {
getActiveUser();
}, []) // No error
return <></>;
}
So why is the first a linter error and the second not? The point of the linter rule is to avoid issue due to stale props or state. While getActiveUser is not itself a prop or state, when its defined inside the component, it may depend on props or state, which may be stale.
Consider this code:
const MyComponent = ({userId}) => {
const [userData, setUserData] = useState(null);
const getActiveUser() => {
setUserData(getData(userId)); // More realistically this would be async
}
useEffect(() => {
getActiveUser();
}, []);
//...
}
Even though that useEffect depends on the userId prop, it only runs once, and so the userId and the userData will be out of sync if the userId changes. Maybe this is your intent, but for the purposes of the linter rule it looks like a bug.
In the case where getActiveUser is defined outside the component, it can't possibly (or at least not reasonably) depend on the state or props of the component, so there's no issue for the linter rule.
So how to fix this? Well, if getActiveUser doesn't need to be defined inside the component, just move it out of the component.
Alternatively, if you're sure you only want this behavior to run when the component mounts, and that won't cause issue due to props changing (it's best to assume all props can change), then you can just disable the linter rule.
But assuming neither of those is the case...
A non-solution (too many effects)
As you've noted, adding getActiveUser to the linter array makes the issue go away:
const MyComponent = ({userId}) => {
const getActiveUser() => {
//...
}
useEffect(() => {
getActiveUser();
}, [getActiveUser]) // No error... but probably not right.
return <></>;
}
But getActiveUser is a different function instance every render, so as far as useEffect is concerned, the deps array changes every render, which will cause an API call after every render, which is almost certainly not what you want.
A fragile solution
Since the root issue in my example is that the userId prop might change, you could also fix this issue by adding userId to the useEffect dependencies:
const MyComponent = ({userId}) => {
const getActiveUser() => {
// Uses userId
}
useEffect(() => {
getActiveUser();
// Linter is still unhappy, so:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userId])
return <></>;
}
This behaves correctly - no extra API calls or stale data - but the linter is still unhappy: it isn't clever enough to know that we've fixed the dependency on getActiveUser by depending on all the things that getActiveUser depends on.
And this is fragile: if you add a prop or state in the future that getActiveUser depends on, and forget to add it here, you're going to have stale data issues.
A better solution
So the recommended solution is:
const MyComponent = ({userId}) => {
const getActiveUsers = useCallback(() => {
// uses userId
}, [userId]);
useEffect(() => {
getActiveUser();
}, [getActiveUsers]) // No error
return <></>;
}
By wrapping getActiveUsers in useCallback, the function instance is only replaced when needed: when userId changes. This means that the useEffect also only runs when needed: when getActiveUsers changes (which is whenever userId changes).
The linter is happy with this solution and if you introduce new dependencies to getActiveUser, you'll only need to change its useCallback deps, not the useEffect.
Dan Abramov's blogpost A Complete Guide to useEffect goes into this in more detail.

Categories