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?
Related
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
This question already has an answer here:
componentWillRecieveProps method is not working properly: ReactJS
(1 answer)
Closed 5 years ago.
I'm afraid this is not going to be a well articulated question as I am somewhat new to React.
I'm modifying the methods of a component SeqAlignmentChart, I'm making the following calls from within componentWillReceiveProps
If I do "console.log(this)" I get
This all looks great. However if I do "console.log(this.props)" I get
Note the 'data' field has gone from length 11 in "this" to length 1 in "this.props" while the 'width' property seems to have transferred. Apologies again for the formatting.
It seems very strange to me that when I print "this.props" I get a data object that doesn't contain the values I would be expecting. Can anyone explain why 'console.log(this.props)' doesn't seem to be printing the same props as when I just do 'console.log(this)' ?
Thanks so much for any insight
edit: here is the code for the component, not sure which parts are relevant for this
export default class SeqAlignmentChart extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
console.log("in mount component $$$$$$$$$$$$$$$$$$$$$");
console.log(this);
this.seqAlignmentChart = new SeqAlignmentVis(
ReactDOM.findDOMNode(this),
this.props.data,
{
width: this.props.width ? this.props.width - 15 : 1000 - 15,
margin: { top: 10, right: 10, bottom: 10, left: 10 }
}
);
this.seqAlignmentChart.render();
}
componentWillReceiveProps(nextProps) {
console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!");
console.log(this);
console.log(this.props);
console.log(this.props.data[0]);
console.log(this.props.data.length);
if (this.props.data.length > 1) {
console.log("should rerender here ^^^^^^^^^^^^^^^^^^^^^^^^");
this.componentDidMount();
}
}
render() {}
}
Take a look at the documentation for componentWillReceiveProps. Notice how it passes nextProps as an argument. nextProps refers to the props that the component is about to receive. this.props refers to the old props that the component currently has.
What happens when you console.log(nextProps)? Is this the data that you expected?
Without a concrete example it is hard to debug, but I would guess 'this' is out of scope.
https://github.com/facebook/react-devtools
Could you try's React's Chrome Devtools? Pretty sure it can inspect React's state better than console.logging.
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,
}
I have a ajax transition which is applyed to a DOM element by setting the className "fetching" in the render()-Method whenever the Component fetches data from the server. After fetch() returns, I let that className be removed so the Component can transition back to its original appearance.
It works like this:
fetchStuff() {
this.setState({fetching: true})
fetch(stuff)
.then(answer => {
// do stuff with the answer
this.setState({fetching: false})
)
}
// and then ...
render() {
let {fetching} = this.state
return <div className={'my-component' + (fetching ? ' fetching' : '')}></div>
}
.my-component {
transition: all .3s;
opacity: 1;
}
.my-component.fetching {
opacity: 0;
}
The start transition works fine! But when React removes the "fetching" className, the Component jumps to its original appearance without transition.
What can I do?
Thanks for your help :)
UPDATE:
Somehow this problem occurs more often in Chrome than in Firefox.
UPDATE 2:
Preventing unnecessary rerenders by using shouldComponentUpdate() on the parent Component made it better but still the transition sometimes skips or is way too fast. But sometimes it is now smooth as it should be.
I'm trying to implement a simple Onsen Navigator in React.
So far I'm receiving an error 'route is not defined' and I was looking through the examples & docs but I only saw the initialRoute prop was provided, how & where does the route prop generated or something? Cause it seems like its not specified.
Here is my the code of my component:
import React, {PropTypes} from 'react';
import ons from 'onsenui';
import * as Ons from 'react-onsenui';
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
index : 0
};
this.renderPage = this.renderPage.bind(this);
this.pushPage = this.pushPage.bind(this);
}
pushPage(navigator) {
navigator.pushPage({
title: `Another page ${this.state.index}`,
hasBackButton: true
});
this.setState({index: this.state.index++});
}
renderPage(route, navigator) {
return (
<Ons.Page key={route.title}>
<section style={{margin: '16px', textAlign: 'center'}}>
<Ons.Button onClick={this.pushPage}>
Push Page
</Ons.Button>
</section>
</Ons.Page>
);
}
render() {
return (
<Ons.Page key={route.title}>
<Ons.Navigator
renderPage={this.renderPage}
initialRoute={{
title: 'First page',
hasBackButton: false
}}
/>
</Ons.Page>
);
}
};
SignUp.propTypes = {
'data-pageName': PropTypes.string.isRequired
};
export default SignUp;
Is this the right syntax in ES6? Have I missed something?
When using Ons.Navigator in react the two required properties are:
initialRoute - it should be an object.
renderPage - method which receives 2 arguments - route and navigator. The route should be an object similar to the initialRoute one. You provide that object when you are calling pushPage and similar methods.
It seems that you already know these 2, but there still 2 other things which you need to be careful about. They are not directly onsen related, but come up a lot when using react in general.
Whenever you have a list of dom elements (for example an array of Ons.Page tags) each of those should have a unique key property.
Whenever you use a method you need to make sure you are binding it if you need some extra arguments.
It seems you also know these two. So the only thing left is to make sure you follow them.
Your syntax is correct - the only thing missing is the route variable in SignUp.render. Maybe you originally copied the renderPage method and that is how you have a leftover Ons.Page.
If you're not putting the SignUp component inside some other navigator, tabbar or splitter then you don't actually need the Ons.Page in its render method. Those are the only cases when they are needed. If you it happens to have one of those components as a parent then you can just specify the key.
PS: I think there should be a React Component Inspector (something like this) which you can install - then I think you may be able to see the place where the error occurs. I think if you knew on which line the problem was you would have been able to solve it. :)
For me, with the object I was passing to initialRoute(), it needed a props property, which itself was an object with a key property. See the before and after below.
Before fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage}}
renderPage={this.renderPage}
/>
);
}
}
This was causing the following console warning:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `Navigator`.
After fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage, props: {key: 'DataEntryPage'}}}
renderPage={this.renderPage}
/>
);
}
}
Notice that the difference I needed to make was the addition of , props: {key: 'DataEntryPage'}.
Feel free to check out this medium article for more information.