so basically im trying to get this render function to actually update the value in real time (as of now it just jumps to the final value upon completion of the animation)
this.state = {
pleaseDisplayMe: Animated.value(0);
}
triggerAnimation(){
Animated.timing(
this.state.pleaseDisplayMe,
{toValue: 100,
duration: 5000}
).start();
}
render(){
return <Animated.Text>{this.state.pleaseDisplayMe}</Animated.Text>
}
I feel like because this library can animated values under the hood then there must be a way to display whats happening. Or is there some sort of content/value property I can feed in through style?
I've tried custom writing my own setInterval function, but I need it to line up better with other Animations' timing and I'd love access to RN's Easing library!
thanks!
React Native has timers that you can use. For example, to do what you are trying to accomplish with setInterval:
state = {pleaseDisplayMe: 0}
componentDidMount() {
setInterval(() => {
this.setState({pleaseDisplayMe: this.state.pleaseDisplayMe + 1})
}, 1000);
}
render() {
return (
<Text>{this.state.pleaseDisplayMe}</Text>
)
}
However, if you are trying to instead trying to actually get the value from the animation, you should attach a listener to Animated and get the value from there as there is no way to synchronously read the value because it might be driven natively. A possibly very poor (I'm also new to React Native) example of this would be something like this:
state = {
pleaseDisplayMe: new Animated.Value(0),
value: 0,
}
triggerAnimation(){
this.state.pleaseDisplayMe.addListener(({value}) => this.setState({value: value}));
Animated.timing(
this.state.pleaseDisplayMe,
{toValue: 100,
duration: 5000}
).start();
}
render() {
return (
<Text>{this.state.value}</Text>
)
}
I used this library (https://github.com/wkh237/react-native-animate-number) to achieve that effect.
import AnimateNumber from '../AnimateNumber';
const targetNumber = 1000;
<AnimateNumber
value={targetNumber}
interval={26} // in miliseconds
formatter={(number) => parseInt(number)}
easing={'easeOut'}
/>
The formatter was important for me otherwise it would animate using very long decimals (from 0 to 1000, you would see 33.3280329804 for instance, but with the formatter you only see integers).
Also, looking at the lib source code, I realized that the start number was hardcoded (it always started from 0). Since it's a very simple library (only one file), you can easily copy dist/index.js and paste that into your project and override the code you need.
In my case, I wanted the component to not animate on mounting, but only when the value prop changed, so inside it's constructor I changed:
this.state = {
value : 0,
displayValue : 0
}
to this:
this.state = {
value : props.value,
displayValue : props.value,
}
Related
I am trying to construct 1-minute candlestick.
I have a component that will continuously passing a number (the trade price) to his child component.
This child component will keep update its state: (High, Low, Open, Close) base on the new number he gets from the parent. (e.g. if the number coming in, is higher than the current this.state.high, it will update this.state.high to the new number) After every minute a setInterval function it will take the states and construct a candle and pass it down to its own children.
the state are:
high, low, open, close, newCandle
I got it working by using
shouldComponentUpdate(nextProps:props, nextState:state){
if(this.props !== nextProps)
this.updateStates(nextProps.newTradePrice); //will update the high, low, open, close state
if(JSON.stringify(nextState.nextMinuteCandle) !== JSON.stringify(this.state.nextMinuteCandle) ) //once the one minute interval is up, there will be a function that will auto set the newCandle state to a new Candle base on the current high, low, open, close state
return true;
return false;
}
I read in the document that shouldComponentUpdate should only be used for optimization not to prevent something to reRender. I am using this to prevent reRender and infinite loop.
I've been stuck on this for days, I cant figure out a way to design this better. Any advice on how to design this better?
2nd related question:
In fact I am relying on shouldComponentUpdate for almost ALL my component too. This can't be right. e.g.
I have a CalculateAverageVolume child component, that takes in the this.state.newCandle. And update the volume every time the newCandle changes (i.e. every minute)
constructor(props: props) {
super(props);
this.state = {
[...],
currentAverage: 0,
showVolume: true
};
}
onCloseHandler()
{
this.setState({showVolume: false});
}
updateAvg(newCandleStick: CandleStick){
//do caluation and use this.setState to update the this.state.currentAverage
}
shouldComponentUpdate(nextProps:props, nextState:state)
{
if(JSON.stringify(this.props.candleStick) !== JSON.stringify(nextProps.candleStick) || this.state.showVolume !== nextState.showVolume){
this.updateAvg(nextProps.candleStick);
return true;
}
return false;
}
render() {
return (
<>
{(this.state.showVolume &&
<IndicatorCard
cardHeader="Volume"
currentInfo={this.state.currentAverage.toString()}
onCloseHandler={()=>this.onCloseHandler()}>
</IndicatorCard>
)}
</>
);
}
}
Can someone please teach me how to design this or restructure this? This works perfectly, but doesn't seem like the right way to do it
I would simplify the component like below.
import { useMemo, useState, memo, useCallback } from "react";
function Component({ candleStick }) {
// use props here to calculate average
const updateAverage = () => 0; // use candleStick props to calculate avg here
const [showVolume, setShowVolume] = useState();
// Compute the average from prop when component re-renders
// I would also add useMemo if `updateAverage` is an expensive function
// so that when prop remains same and `showVolume` changes we don't need to calculate it again
const currentAverage = useMemo(updateAverage, [candleStick]);
const onCloseHandler = useCallback(() => setShowVolume(val => !val), []);
return showVolume ? (
<IndicatorCard
cardHeader="Volume"
currentInfo={currentAverage}
onCloseHandler={onCloseHandler}
/>
) : null;
}
// If true is returned, component won't re-render.
// Btw React.memo by default would do shallow comparison
// But if deep comparison function is required, I would use lodash or other utility to do the check instead of JSON.stringify.
const arePropsEqual = (prev, next) =>
isEqual(prev.candleStick, next.candleStick);
export default memo(Component, arePropsEqual);
shouldComponentUpdate is usually reserved for discrete events that you can control. Howevr, it seems like you are dealing with a continuous stream of data.
Two ways to handle it:
Pass down a function reference that handles a stream to the child component and let that handle you state updates in your child component.
Use the context API to inform child component about the changes
Reference implementation :
Upadting State with Context API : https://javascript.plainenglish.io/react-context-api-part-2-updating-state-through-a-consumer-7be723b54d7b
Streams : https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
React with Streams : https://blog.bitsrc.io/how-to-render-streams-with-react-8986ad32fffa
I hit the same spot you're in when starting React. The problem here is that React, at least the basic aspects of it, isn't enough when you're talking about data flow. What you need to look into is a React data management framework, of which Redux is probably the most popular. Go look at Redux and make sure you're looking at the latest documentation based around hooks.
You'll say to yourself "Oh! That makes perfect sense" - I know I did.
Other, similar frameworks are React Query and React's own Context API. The main point I'm trying to make is that you really need data management to do the thing you're looking for.
This is my first time making a finite state Automata. I tried making a stop light kind of program where if you click the button, the light changes once, starting at green, then to yellow if clicked again, then to red, before looping again. I managed to make it work, except for one small bug. It needs to be clicked twice before the screen updates, and I don't really know how to fix it.
I noticed on my console that the currentLightState changes when I click on it, but reverts back to the previous color on the first click. I figured out that it's because it is not in sync with my state. However, when I attempt to put currentLightState inside the class, and assign this.state.light, currentLightState becomes undefined. I tried adding a .bind(this) at the end of this.state.light, but I just get another error saying it's not a function. Any suggestions?
I didn't post my update function or my onHandleClick function since it doesn't directly deal with currentLightState.
const lightMachine = {
green:{
LIGHT: 'yellow'
},
yellow:{
LIGHT: 'red'
},
red:{
LIGHT: 'green'
}
}
// current location of currentLightState
let currentLightState = 'green';
class App extends React.Component{
constructor(props){
super(props);
this.state = {
light: 'green' //initial value
};
}
transition(state, action){
// This is where my currentLightState messes up the first run
currentLightState = this.state.light
const nextLightState = lightMachine[currentLightState][action]
this.setState({light: nextLightState})
}
render(){
return(
<div>
<button onClick={this.onHandleClick.bind(this)}>
change the Light!
</button>
{currentLightState}
</div>
);
}
}
EDIT: Here is my onHandleClick function :)
onHandleClick(){
this.update(currentLightState)
};
also, I think I solved my problem by just substituting the currentLightState in the render function with this.state.light .
Idk if that is a legitimate fix or not, but it seems to work currently.
it would be nice though if someone can still answer why when you put the currentLightState inside the class, and assign state.light to it, it becomes undefined. It would help expand my React knowledge :)
Welcome to the boards Jr194!
On topic, I think it will be beneficial to keep currentLight and lightMachine as values managed by your component state. This helps ensure all your logic is within the same context, avoiding weird errors like "blah blah is not defined."
Second, you seem to have defined a few functions that can really be narrowed down to one. update, onHandleClick and transition all seem to be trying to do the same thing.
Lastly, consider reorganizing the code in your lightMachine to be a bit more intuitive so that you can quickly navigate to the next light. You already know what the current color is in the parent key, is their really a need for having its object contain another key with the same value?
Consider the following code:
class App extends React.Component {
state = {
currentLight: "green",
lightMachine: {
green: {
nextLight: "yellow"
},
yellow: {
nextLight: "red"
},
red: {
nextLight: "green"
}
}
};
transition = () => {
const currentLight = this.state.currentLight;
const nextLight = this.state.lightColors[currentLight].nextLight;
this.setState({
currentLight: nextLight
});
};
render() {
return (
<div>
<button onClick={this.transition}>Click Me</button>
<div>{this.state.currentLight}</div>
</div>
);
}
}
This should let you quickly change lights as expected. And here's the sandbox: https://codesandbox.io/s/4x08ovyjp7
Let me know if you have any questions :)
Also regarding your question about why currentLightState gives you an error in your render when you put it inside the class. This is not a React issue, it's a JavaScript issue.
Let's go through some examples:
const test= "hello"
class Example extends React.Component{
state = {
greeting: "woof"
}
render(){
<div>{test}</div>
}
}
As you know, this won't give us any error. We are tapping into the variable that's defined outside our class and that's completely fine.
Now let's see look at this
class Example extends React.Component{
state = {
greeting: "woof"
}
test = this.state.greeting
render(){
<div>{test}</div>
}
}
This gives us an error. Why, because in your render method, you are treating test like it's a variable, when it is not. In a class object like the one you have written, currentLightState and now test are treated as properties, not variables. In order to get access to a property inside a class, you need to make use of the "this" keyword, something you already have been doing with this.update, this.handleOnClick and etc, which are also properties.
Now we know this code will work.
class Example extends React.Component{
state = {
greeting: "woof"
}
test = this.state.greeting
render(){
<div>{this.test}</div>
}
}
I'm trying to make scroll to the top, if a certain condition is met, in the component's componentWillReceiveProps event ... but nothing happens:
componentWillReceiveProps(nextProps) {
// some code...
if (newQuery === query && this.scrollViewRef.current) {
console.log('Should scroll to top'); // << logs successfully
this.scrollViewRef.current.scrollTo({
x: 0,
y: 0,
duration: 500,
animated: true,
});
}
}
Code snippet of how I created ref for the scrollView:
class SearchResult extends React.Component {
constructor(props) {
super(props);
this.scrollViewRef = React.createRef();
}
//...
}
render method:
render () {
return (
<ScrollView
ref={this.scrollViewRef}
contentContainerStyle={{ marginVertical: 8 }}
>
...
)
}
I also tried to scroll manually via a button press ... doesn't work as well
Any help ?
I figured this out ...
The scrollView worked perfectly in an isolated env ( a brand new project ) ...
I thought the issue could be in the container of that scrollview ... and I found that the parent component has also a ScrollView ... once i removed it, everything worked perfectly.
For those people who use useRef() method and gets 'xRef.scrollTo' is not a function error, try to use it like xRef.current.scrollTo({[YOUR_PARAMS]}).
I didn't know this current thing and was getting crazy.
React Native docs say:
Note: The weird function signature is due to the fact that, for historical reasons, the function also accepts separate arguments as an alternative to the options object. This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
Maybe try scrollToOffset method, if you are also using FlatList with ScrollView?
Imagine that you have a component that bridges an imperative API to react component, for example, Mapbox API.
So you made yourself a Map component and you want the every time that user changes the center prop, the map updates to the new coordinates. It looks something like this:
class Map extends Component {
el = React.createRef();
componentDidMount(props) {
this.map = new mapboxgl.Map({
container: this.el.current,
style: 'mapbox://styles/mapbox/streets-v9',
center: this.props.center,
zoom: this.props.zoom
});
}
componentDidUpdate(prevProps) {
// notice this condition
if (prevProps.center !== this.props.center) {
this.map.setCenter(this.props.center);
}
}
componentWillUnmount() {
this.map && this.map.remove();
}
render() {
return (
<div ref={this.el} className="map" />
)
}
}
And we use it like so:
<Map center={[35.173906, 32.706769]} zoom={16} />
Now here is the catch: since we pass a new array every time, the condition is always true, even for changes unrelated to the map at all.
I made a simple example to demonstrate it:
https://codesandbox.io/s/pmz40wkm8j
Notice that when we increment the counter, the map center updates too.
There are 2 ways I can think of to solve it:
using _.isEqual which will work. but I want if it will affect performance for large arrays and Objects, for example, you can pass a pretty lengthy object of layers to mapbox.
Enforce the user to use Immutable.js, but it doesn't feel right because not everyone feels comfortable with it
What will be the best approach to handle that in your opinion?
I think _.isEqual is superfluous here, just use this function in your componentDidUpdate
centerPropsHasBeenChanged(prevProps, props) {
return (
props.center[0] !== prevProps.center[0] || props.center[1] !== prevProps.center[1]
)
}
Option 2
If you're using big arrays (so not coordinates, but something else), it looks like the fastest option is to JSON.stringify() and compare then
I have come across a problem about states based on properties.
The scenario
I have a Component parent which creates passes a property to a child component.
The Child component reacts according to the property received.
In React the "only" proper way to change the state of a component is using the functions componentWillMount or componentDidMount and componentWillReceiveProps as far as I've seen (among others, but let's focus on these ones, because getInitialState is just executed once).
My problem/Question
If I receive a new property from the parent and I want to change the state, only the function componentWillReceiveProps will be executed and will allowed me to execute setState. Render does not allow to setStatus.
What if I want to set the state on the beginning and the time it receives a new property?
So I have to set it on getInitialState or componentWillMount/componentDidMount. Then you have to change the state depending on the properties using componentWillReceiveProps.
This is a problem when your state highly depends from your properties, which is almost always. Which can become silly because you have to repeat the states you want to update according to the new property.
My solution
I have created a new method that it's called on componentWillMount and on componentWillReceiveProps. I have not found any method been called after a property has been updated before render and also the first time the Component is mounted. Then there would not be a need to do this silly workaround.
Anyway, here the question: is not there any better option to update the state when a new property is received or changed?
/*...*/
/**
* To be called before mounted and before updating props
* #param props
*/
prepareComponentState: function (props) {
var usedProps = props || this.props;
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === usedProps.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
componentWillMount: function () {
this.prepareComponentState();
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
/*...*/
I feel a bit stupid, I guess I'm loosing something...
I guess there is another solution to solve this.
And yeah, I already know about this:
https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
I've found that this pattern is usually not very necessary. In the general case (not always), I've found that setting state based on changed properties is a bit of an anti-pattern; instead, simply derive the necessary local state at render time.
render: function() {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === this.props.currentQuestion.id;
});
return ...; // use currentResponses instead of this.state.currentResponses
}
However, in some cases, it can make sense to cache this data (e.g. maybe calculating it is prohibitively expensive), or you just need to know when the props are set/changed for some other reason. In that case, I would use basically the pattern you've written in your question.
If you really don't like typing it out, you could formalize this new method as a mixin. For example:
var PropsSetOrChangeMixin = {
componentWillMount: function() {
this.onPropsSetOrChange(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.onPropsSetOrChange(nextProps);
}
};
React.createClass({
mixins: [PropsSetOrChangeMixin],
onPropsSetOrChange: function(props) {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
// ...
});
Of course, if you're using class-based React components, you'd need to find some alternative solution (e.g. inheritance, or custom JS mixins) since they don't get React-style mixins right now.
(For what it's worth, I think the code is much clearer using the explicit methods; I'd probably write it like this:)
componentWillMount: function () {
this.prepareComponentState(this.props);
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
prepareComponentState: function (props) {
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},