I have just updated to react native 0.54.0 that also includes alpha 1 of react 16.3, naturally I get a lot of warnings regarding depreciation of componentWillMount
and componentWillReceiveProps
.
I have an animated route component that was relying on componentWillReceiveProps
at its core it receives new path, compares it to previous one, if they are sets old children, animates them out, sets new children and animates them in.
Here is code in question:
componentWillReceiveProps(nextProps: Props) {
if (nextProps.pathname !== this.props.pathname) {
this.setState({ previousChildren: this.props.children, pointerEvents: false }, () =>
this.animate(0)
);
}
}
Now for the questions I have regarding porting this to static getDerivedStateFromProps
1) I no longer have access to this.props
, hence no access to previous pathname, I guess I can store these props in state, is this correct approach now? Seems like repetition of data.
2) As I have no access to this
I can't call my animation function, that relies on a state. How can I bypass this?
3) I need to first set state and then call the animation, as getDerivedStateFromProps
sets state by returning the values, I can't do much afterwards, so is there a way to set state and after thats done execute a callback?
4) pathname
bit is only used in componentWillreceiveProps
right now, if I move it to state and never use this.state
inside getDerivedStateFromProps
(because I can't) this.state.pathname
errors as being defined but never used. Is the best approach here to make it static as well?
My first instinct was to change it to componentDidUpdate
, but we are not supposed to use setState
inside of it correct? (this does work in my scenario though).
componentDidUpdate(prevProps: Props) {
if (this.props.pathname !== prevProps.pathname) {
this.setState({ previousChildren: prevProps.children, pointerEvents: false }, () =>
this.animate(0)
);
}
}
NOTE: As far as I heard I think there is a feature coming in "suspense" that will allow us to keep views mounted on component unmount event? I wasn't able to find any reference to this, but it sounds like something I could potentially utilise for this animation
For anyone interested, this is snippet for a full component in question
// @flow
import React, { Component, type Node } from "react";
import { Animated } from "react-native";
type Props = {
pathname: string,
children: Node
};
type State = {
animation: Animated.Value,
previousChildren: Node,
pointerEvents: boolean
};
class OnboardingRouteAnomation extends Component<Props, State> {
state = {
animation: new Animated.Value(1),
previousChildren: null,
pointerEvents: true
};
componentWillReceiveProps(nextProps: Props) {
if (nextProps.pathname !== this.props.pathname) {
this.setState({ previousChildren: this.props.children, pointerEvents: false }, () =>
this.animate(0)
);
}
}
animate = (value: 0 | 1) => {
Animated.timing(this.state.animation, {
toValue: value,
duration: 150
}).start(() => this.animationLogic(value));
};
animationLogic = (value: 0 | 1) => {
if (value === 0) {
this.setState({ previousChildren: null, pointerEvents: true }, () => this.animate(1));
}
};
render() {
const { animation, previousChildren, pointerEvents } = this.state;
const { children } = this.props;
return (
<Animated.View
pointerEvents={pointerEvents ? "auto" : "none"}
style={{
alignItems: "center",
opacity: animation.interpolate({ inputRange: [0, 1], outputRange: [0, 1] }),
transform: [
{
scale: animation.interpolate({ inputRange: [0, 1], outputRange: [0.94, 1] })
}
]
}}
>
{previousChildren || children}
</Animated.View>
);
}
}
export default OnboardingRouteAnomation;
With react version 16.3, using componentWillReceiveProps
is fine, but will cause a deprecation warning to be shown in the console. It will be removed in version 17. FWIW Dan Abramov has warned against using alpha functionality in production - but this was back in March of 2018.
Let's walk through your questions:
1) I no longer have access to this.props, hence no access to previous pathname, I guess I can store these props in state. Is this the correct approach now? Seems like repetition of data.
Yes.
If you want to update / re-render your component, you should combine props
and state
. In your case, you want to update the component / trigger animation when pathname
is changed with a life cycle method.
Seems like repetition of data.
check this out: React Team post on State and Lifecycle
2) As I have no access to this I can't call my animation function, which relies on state. How can I bypass this?
Use componentDidUpdate
componentDidUpdate
This function will be called after render is finished in each of the re-render cycles. This means that you can be sure that the component and all its sub-components have properly rendered itself.
This means you can call animate
after setState
in componentDidUpdate
3) I need to first set state and then call the animation, as getDerivedStateFromProps sets state by returning the values, I can't do much afterwards, so is there a way to set state and after thats done execute a callback?
Refer to point #2 (Use componentDidUpdate)
4) pathname bit is only used in componentWillreceiveProps right now, if I move it to state and never use this.state inside getDerivedStateFromProps (because I can't) this.state.pathname errors as being defined but never used. Is the best approach here to make it static as well?
No, It will used by componentDidUpdate
Here's how I would approach this:
// @flow
import React, { Component, type Node } from "react";
type Props = {
pathname: string,
children: Node
};
type State = {
previousChildren: Node,
pointerEvents: boolean
};
class OnboardingRouteAnomation extends Component<Props, State> {
state = {
previousChildren: null,
pointerEvents: true,
pathname: ''
};
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.pathname !== prevState.pathname) {
return {
previousChildren: nextProps.children,
pointerEvents: false,
pathname: nextProps.pathname
};
}
return null;
}
componentDidUpdate(prevProps, prevState) {
if (prevState.pathname !== this.state.pathname){
console.log("prevState.pathname", prevState.pathname);
console.log("this.props.pathname", this.props.pathname);
this.animate(0);
}
}
componentDidMount(){
this.setState({ pathname: this.props.pathname});
}
animate = (value: 0 | 1) => {
console.log("this animate called", this);
animationLogic(value);
};
animationLogic = (value: 0 | 1) => {
if (value === 0) {
this.setState({ previousChildren: null, pointerEvents: true }, () => this.animate(1));
}
};
render() {
const { animation, previousChildren, pointerEvents } = this.state;
const { children } = this.props;
return (
<div>
{this.props.children}
</div>
);
}
}
export default OnboardingRouteAnomation;
I believe this is how the react devs intended this should be handled. You should
call animate after updating via componentDidUpdate
because it's a side effect.
I'd use a more descriptive name for indicating the path has changed. Something like isPathUpdated
. You could then have animate
check isPathUpdated
as a toggle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With