React hooks component did update with prevProps - reactjs

I was converting my react code from class to hooks
I previously had something like this
export default class Editor extends React.Component {
constructor(props) {
super(props)
this.state = this.getStateObject()
}
getStateObject() {
const { page } = this.props
return {
label: page.label || '',
session: page.session || false
}
}
componentDidUpdate(prevProps) {
if (!_.isEqual(this.props.projects, prevProps.projects)) {
this.setState(this.getStateObject())
}
}
When Trying to move to functional, I did something like this
export default function tabEditor ({page}:Page) {
const [appPageInfo, setAppPageInfo] = useState({
label: page.label || '',
session: page.session || false
})
/* Equivalence in hooks
componentDidUpdate(prevProps) {
if (!_.isEqual(this.props.projects, prevProps.projects)) {
this.setState(this.getStateObject())
}
}
*/
const handleOnClickUpdate = () => {
updateMobileAppPage(Object.assign({}, page, appPageInfo))
close()
}
but I am unable to determine the equivalence of componentDidUpdate in React hooks.

You could use the useEffect hook add pass it the projects props as dependency like this :
useEffect(() => {
// Whatever code you want to run if props.projects change
}, [props.projects]);

To mimic componentDidUpdate in Hooks, you make use of useEffect and then pass in the conditional parameter with which useEffect will run whenever the parameter changes. Example below
useEffect(() => {
.......
//This useEffect will re-run whenever the parameter in the square bracket updates/changes.
}, [parameter])

Related

How to use React.useRef() in class component?

I need this code to be implemented in class component.
It is in order to use a upload progress in my class component with react-toastify
function Example(){
const toastId = React.useRef(null);
function handleUpload(){
axios.request({
method: "post",
url: "/foobar",
data: myData,
onUploadProgress: p => {
const progress = p.loaded / p.total;
if(toastId.current === null){
toastId = toast('Upload in Progress', {
progress: progress
});
} else {
toast.update(toastId.current, {
progress: progress
})
}
}
}).then(data => {
toast.done(toastId.current);
})
}
return (
<div>
<button onClick={handleUpload}>Upload something</button>
</div>
)
}
How can i do that?
useRef() is among react hooks which are meant to be used in Functional components. But if you want to create a reference in a class-based component, you can do it from the class constructor like the code below:
constructor(props) {
super(props);
this.myRef = React.createRef();
}
Check React.createRef().
Assign your value in constructor i.e bind with this.
createRef !== useRef, useRef is used to preserve the value across re-renders and for that in class component you need to bind it with this not createRef

How to initialize the react functional component state from props

I'm using React hooks for app state, I wondered about how to initialize the functional component state using props? The useState hook doc says something definitive like,
const [count, setCount] = useState(0);
I want to initialize that 0 value by the value of props being passed to the component. The Older as,
import React from 'react';
export default class Sym extends React.Component{
constructor(props){
super(props);
this.state = {
sym : [0,3,2,8,5,4,1,6],
active: this.props.activeSym
}
this.setActive = this.setActive.bind(this);
}
setActive(itemIndex){
this.setState({
active: itemIndex
});
}
render(){
return (
<div><h1>{ this.state.sym[this.state.active]}</h1></div>
);
}
}
works fine. Where the parent Component passes activeSym prop and Sym component initializes the state with it using this.props.activeSym in constructor. Is there any workaround to achieve same in function component?
First you can define it from props (if the prop exist):
const [count, setCount] = useState(activeSym);
And then you can update this value, when prop doesn't have a value immediately (when component rendered):
useEffect(() => {
if (activeSym) {
setCount(activeSym);
}
}, [activeSym])
Yes, this can be possible with functional component too! You just need to add useEffect to listen to prop change for initializing state with prop value
export const newComponent = (props) => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => {
setVal(value);
}, [value]);
return <div>{val}</div>;
};
Attching sandbox link
https://codesandbox.io/s/confident-agnesi-ohkq7?file=/src/MakeComponent.js
Yes you can first define state using props:
const [name, setName] = useState(props.obj?.name);
And then you can if the state is still undefined means props doesn't have a value, then:
useEffect(() => {
if (JSON.stringify(props.obj) !== "{}") {
setName(props.obj?.name);
}
}, [props.obj])
Just as follows :
const MyFunctionalComponent = ({myProp}) => {
const [count, setCount] = useState(myProp)
return (
/* ... */
)
}
There are two ways you can change the state:
one is using this.state and
another one is this.setState.
We use the first method to initialize the state in the constructor, and the second method is used for the rest of the time.
Initialize State in the Constructor
One way is to initialize the state is in the constructor. As we discussed earlier constructor is the first method to be called when React instantiates the class. This is the perfect place to initialize the state for the component because the constructor is called before the React renders the component in the UI.
class WithConstructor {
constructor() {
this.state = {
name: "StackOverflow"
}
}
}
Initialize State Without Constructor
Another way of initializing state in React is to use the Class property. Once the class is instantiated in the memory all the properties of the class are created so that we can read these properties in the render function.
class WithoutConstructor {
state = {
name: "StackOverflow"
}
}

useEffect alternate for Class Component

I just learned that in functional components I can use useEffect to keep an eye on any side effect (ex: when using localStorage) which makes sure my state is hooked up with the effect.
I want to have similar functionality in my class based Component for localStorage. How can I make sure that my state updates itself as soon as there is any change into the localStorage?
This is how I did it.
class Login extends Component {
constructor(props) {
super(props);
this.state = {
// Get value from localStorage or use default
isLoggedIn: localStorage.getItem('isLoggedIn') || 0
}
// Listen to storage event
window.addEventListener('storage', (e) => this.storageChanged(e));
// Bind this to storageChanged()
this.storageChanged = this.storageChanged.bind(this);
}
storageChanged(e) {
if(e.key === 'isLoggedIn') {
this.setState({isLoggedIn: e.newValue})
}
}
render() {
return <p>{this.state.isLoggedIn}</p>
}
}
That's how I could hook into the localStorage changes using class based component.
You can try a hook that check for localStorage changes like in the docs.
useEffect(() => {
function checkStorage(e){
if('keyStateYouWantToSync' == e.key && stateYouWantToSync != JSON.parse(e.newValue)){
setStateYouWantToSync(JSON.parse(e.newValue))
}
}
window.addEventListener('storage', checkStorage)
return () => window.removeEventListener('storage', checkStorage)
})
You can also change the key checking to what ever key you want to check.
Edit:
For class components
checkStorage = (e) => {
if('keyStateYouWantToSync' == e.key && this.state.stateYouWantToSync != JSON.parse(e.newValue)){
this.setState({stateYouWantToSync: JSON.parse(e.newValue)})
}
}
componentDidMount(){
window.addEventListener('storage', this.checkStorage)
}
componentWillUnmount(){
window.removeEventListener('storage', this.checkStorage)
}

How to migrate componentWillReceiveProps in react 16.0.0?

I have a reactcomponent that has a few obsolete events:
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
componentWillReceiveProps(nextProps) {
const {
presets: { sortCriteria: sortBy, customCriteria },
} = nextProps;
const { appColumnsSorted } = this.state;
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
getSortedAppColumns = (appColumns, sortBy, criticalFirst) => {
//returns object
};
'componentWillMount' is basically to initialize the appColumnsSorted. The issue is that with v16 this event is obsolete. So what can event can I use for this now? Also what is the way to migrate 'componentWillReceiveProps' in this scenario?
What you're using componentWillMount for can be done in the constructor so
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
will change to
export default class YourClass extends Component {
constructor(props) {
// keep a separate method just to avoid writing code in constructor for readability
this.state = constructInitialState(props);
}
constructInitialState(props) {
const state={};
//More state handling as required
const { applicationStages } = props;
if (applicationStages && applicationStages.length > 0) {
state.appColumnsSorted = this.getSortedAppColumns(someVar);
}
return state;
}
}
This approach is slightly better because getDerivedStateFromProps will be called before each render and will waste computation.
From the code snippet it is not obvious why you want to store it in state. If you do save it to state then the only way you have would be to use componentDidUpdate as mentioned in the other answer by Aaditya Thakkar. This will require you to mirror your props in state only for comparison purpose (Mapping props to state is not the best way, more on this link https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti-pattern-unconditionally-copying-props-to-state)
I would, however, recommend calling your sort computation method and return its result in render directly; this would avoid extra checks in componentDidUpdate to render the second time. As I'm not aware if these props are coming from redux or a parent React component, another option would be to avoid expensive computation in this class and simply provide the correct value from either the parent component or calculate the value in the redux store and send the final prop directly for use.
ComponentWillReceiveProps can be replaced with getDerivedStateFromProps. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state. It's a static method, so this can not be used inside it.
Hence, you can no longer reference this.getSortedAppColumns from getDerivedStateToProps, you need componentDidUpdate lifecycle for that. Here, I have broken down ComponentWillReceiveProps into getDerivedStateFromProps and componentDidUpdate:
static getDerivedStateFromProps(nextProps, prevState) {
const {
presets: { sortCriteria: sortBy },
} = nextProps;
if (sortBy === prevState.sortBy) return null;
return ({ sortBy: nextProps.sortBy });
}
componentDidUpdate(_, prevState) {
const { appColumnsSorted, sortBy } = this.state;
if (sortBy !== prevState.sortBy) {
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
}

Warning: setState(...): Cannot update during an existing state transition

I'm getting the following error:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
My component:
import React from 'react';
import { Redirect } from 'react-router'
import Notifications from 'react-notification-system-redux';
constructor(props) {
super(props);
this.state = {
invite_token: this.props.match.params.token,
FormSubmitSucceeded: false,
inviteRequestSubmitSucceeded: false
};
}
....
inviteAlreadyUsed() {
const notificationOpts = {
message: 'Invitation already used!',
};
this.props.createNotificationSuccess(notificationOpts);
}
render() {
const { invite } = this.props;
if (invite && invite.status === "completed") {
this.inviteAlreadyUsed();
return <Redirect to={{ pathname: '/' }}/>;
}
...
Any suggestions on how to avoid this warning? Is this not how you would handle a redirect?
this.inviteAlreadyUsed(); in render -> reducer updating a state -> it call new render -> this.inviteAlreadyUsed(); -> reducer update a state and again and again...
Just don't call inviteAlreadyUsed in render.
First, I think you should bind the inviteAlreadyUsed() function. You can use arrow function () => {}.
inviteAlreadyUsed = () => {
const notificationOpts = {
message: 'Invitation already used!',
};
this.props.createNotificationSuccess(notificationOpts);
}
Second, seems like you set the state with props in constructor. Setting it in componentWillMount() might be a better approach.
constructor(props) {
super(props);
this.state = {
invite_token: '',
FormSubmitSucceeded: false,
inviteRequestSubmitSucceeded: false
};
}
componentWillMount() {
this.setState({
invite_token: this.props.match.params.token
})
}

Resources