react-native: undefined is not an object (evaluating 'prop.slice') - react-native

im new to coding, and React-Native, and running into this error which i can't find an answer to, after extensive googling (it might be due to my newbiness and not understanding answers given to other users, so please be patient and point me in the right direction, if thats the case)
the error is:
undefined is not an object (evaluating 'timeAsProp.slice')
so to explain what timeAsProp.slice is: basically i needed the time to be passed as a prop to the newOrder() method, in just two digits. if the time is 10:00:00, my newOrder method should just proccess: 10
the current time is stored in state.
currentTime: new Date().toLocaleTimeString()
the above current time state reads out on the emulator as:
12:00:00 //if the time was 12:00 ocklock
The function
newOrder(timeAsProp)
recieves the time, and depending on what the hour is, should return a different array of images to be rendered for each hour on the hour.
(i only put two time options hardcoded into the app as im still building it. afterwards there will be a seperate image order returned, for each hour. its hardcoded this way, with a seperate option for each hour, untill i can figure out how to have a loop automatically set the images in their proper order depending on the time)
so basically, i need a different way of doing this, or figuring out why im getting this error...
thnks in advance ;)
heres my code:
import React, { Component } from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import AwesomeButton from 'react-native-awesome-button';
import Style from './Style';
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentTime: null,
};
}
componentDidMount() {
setInterval(() => {
this.setState({
currentTime: new Date().toLocaleTimeString()
});
}, 1000);
}
handleButtonPress() {
}
**// heres the timeAsProp.slice which is giving me the error:**
newOrder(timeAsProp) {
const hour = timeAsProp.slice(0, 2);
if (hour === '19') {
return [
<div>
<Image source={require('./img/hey2.png')} style={Style.image} />
<Image source={require('./img/vav.png')} style={Style.image} />
<Image source={require('./img/hey1.png')} style={Style.image} />
<Image source={require('./img/yud.png')} style={Style.image} />
</div>
];
}
if (hour === '20') {
return [
<div>
<Image source={require('./img/vav.png')} style={Style.image} />
<Image source={require('./img/hey2.png')} style={Style.image} />
<Image source={require('./img/hey1.png')} style={Style.image} />
<Image source={require('./img/yud.png')} style={Style.image} />
</div>
];
}
}
render() {
return (
<View style={Style.rootContainer}>
<View style={Style.headerContainer}>
<Text style={styles.blue}> {this.state.curentTime} </Text>
</View>
<View style={Style.displayContainer}>
**//this is whats calling newOrder and returning the images**
{this.newOrder(this.state.time)}
</View>
<View style={Style.buttonContainer} >
<AwesomeButton
states={{
default: {
text: 'DeeDee! Dont press da button!',
backgroundStyle: {
backgroundColor: 'blue',
minHeight: 45,
alignItems: 'center',
borderRadius: 30,
marginBottom: 15,
marginLeft: 15,
},
onPress: this.handleButtonPress
}
}}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
blue: {
fontFamily: 'serif',
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
}
});
export default App;
Thanks for taking a look and hopefully teaching me something new!
sb

{this.newOrder(this.state.time)} is what you're calling in the render method but the value you have in state is currentTime:
this.state = {
currentTime: null,
};
Make sure you're passing the right variable in to the method you're calling. Also, it's better to check in the method that the variable has the type and value you expect before using it. So inside of newOrder perhaps check if timeAsProp is type string (typeof timeAsProp === 'string') before calling slice on it.

First, you have to change
{this.newOrder(this.state.time)}
To
{this.newOrder(this.state.currentTime)}
Then, in your constructor, initialize currentTime
constructor(props) {
super(props);
this.state = {
currentTime: new Date().toLocaleTimeString(),
};
}
The reason is that, as stated in the docs, render() executes before componentDidMount(), so in the moment newOrder is being called, currentTime has not been initialized.

Related

how to stop images from rendering on setState

I have images associated with a counter and based on this increment or decrement in counter, a calculation is done and displayed in a text at the bottom.
The problem is that when I render, the images get rendered again and are loaded again and again and again. which I dont want.
If I dont render, the text will not update with the calculated amount.
For the counter I am using react-native-counter.
I have already tried with shouldcomponentupdate, but I want to stop only image rendering, the rest should work.
Please advise.
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Header
backgroundColor="#25D366"
leftComponent={
<Icon
name="menu"
size={40}
color={"#fff000"}
onPress={() => this.props.navigation.openDrawer()}
/>
}
centerComponent={{
text: "Veg & Fruits",
style: {
color: "#ffffff",
fontSize: 25,
fontWeight: "bold",
},
}}
rightComponent={<Icon name="home" color={"#ff0000"} />}
></Header>
/// this is searchbar component,
<SearchBar
fontColor="#ffffff"
fontWeight="bold"
fontSize={20}
iconColor="#c6c6c6"
shadowColor="#ffffff"
cancelIconColor="#c6c6c6"
backgroundColor="#25D366"
placeholder="Search here"
onChangeText={(text) => {
this.setState({ photos: [] });
this.state.search = text;
this.filterList(this.state.search);
console.log("text changed");
}}
onPressCancel={(text) => {
text = "";
//this.filterList(text);
}}
onPress={(text) => {
console.log("rendering");
console.log("now text is: ", this.state.search);
}}
/>
/// in this view images are displayed using functions
<View>
<ScrollView
style={{
height: Dimensions.get("window").height - 200,
}}
>
<View
key={Date.now()}
style={{
flex: 1,
flexDirection: "column",
flexWrap: "wrap",
}}
>
{this.filterList(this.state.search)}
{this._renderImages()}
</View>
</ScrollView>
<CalcText tt={total_num} />
</View>
</div>
);
}
}
class CalcText extends Component {
constructor(props) {
super(props);
this.state = {
ta: 0,
};
}
shouldComponentUpdate(nextProps) {
console.log(nextProps.tt);
if (this.props.tt !== nextProps.tt) {
console.log("changed");
return true;
} else {
console.log("Not changed");
return false;
}
}
render() {
return (
<View style={{ height: 40, backgroundcolor: "ff0000" }}>
<Text style={{ fontSize: 26, fontWeight: "bold" }}>
Total : {this.props.tt}
</Text>
</View>
);
}
}
You can create a TextBox component and split it from ImageComponent. In this way the images will not be bound to the state of the component rendering text, and you can safely change TextBox state and text preventing ImageComponent to re-render.
Edit:
Okei, now i get it. I think you have no possibility to do it like this.
Let's formalize the problem:
<Parent>
<Images calc={functionToCalc} />
<CalcText totalAmount={this.state.totalAmount}/>
</Parent>
functionToCalc is defined in in <Parent> and update parent state, something like:
const funcToCalc = () => {
// when condition occurs
this.setState({
totalAmount : computedAmount
})
}
The state of <Parent> has:
this.state : {
totalAmount: none
}
Whenever condition (buttonClick?) occurs in <Images/> you run functionToCalc update <Parent> state and rerender <CalcText>. Problem here is that also <Images> will be rerender again as all the parent component will be rerender.
this is one of the way to pass info from siblings in React.
You only have a possibility if you want to prevent <Images> rerendering.
Redux or similar libraries for centralize state
You will move the computed calculation in a Store and <CalcText/> will read that from there. <Images/> component will trigger an action modifying that state but won't listen to that so not being rerender.

FlatList ref scrollToIndex is not a function

I am facing what seems to be a long-lasting issue in react native.
I am using Expo SDK35 with RN version 0.59. I have not updated to Expo SDK36 / RN 0.60 yet, due to large code base, but I could update if that makes up for a solution to my issue.
I have an Animated.View component that has a FlatList child, and I am unable to use the static methods (scrollToIndex() in particular) that should be available on the FlatList reference. See the next example code:
class Example extends React.Component{
constructor(props){
super(props);
this.myRef = null;
}
componentDidUpdate = () => {
/*
somewhere in code outside this class, a re-render triggers
and passes new props to this class.
I do have props change detection, and some more other code,
but I have removed it in order to minimize the code example here
*/
// This call throws:
// TypeError: undefined is not a function (near '...this._scrollRef.scrollTo...')
this.myRef.scrollToIndex({
animated: true,
index: 1,
viewOffset: 0,
viewPosition: 0.5
});
// Other suggested solution from SO
// This also throws:
// TypeError: _this.myRef.getNode is not a function. (In '_this.myRef.getNode()', '_this.myRef.getNode' is undefined)
this.myRef.getNode().scrollToIndex({
animated: true,
index: 1,
viewOffset: 0,
viewPosition: 0.5
});
}
render = () => <Animated.View style={{ /* ... some animated props */ }}>
<FlatList ref={(flatListRef) => { this.myRef = flatListRef; }}
// more FlatList related props
/>
</Animated.View>
}
I have tried to use Animated.FlatList instead, still throws the same errors as in the code example above.
I have also tried to use react native's findNodeHandle() utility function on the received flatListRef parameter, but it returns null.
I have found the same issue posted multiple times in the past here on Stack Overflow, most with no answer, or which do not work for me. These posts are also a bit old (a year or so), which is why I am posting again for the same issue.
Did anyone manage to find a solution/workaround for this issue?
EDIT: Possible workaround
As I was playing with code, I tried to use a ScrollView component instead of FlatList - and the scrollTo method works!
The changes were only on the FlatList - ScrollView specific props (so, for a ScrolLView it would be childs instead of data={[...]} and renderItem={()=>{ ... }}, ect.), and the scrollToIndex method in componentDidMount which was replaced by scrollTo.
The render method of the class, with a ScrollView, now looks like this:
render = () => <Animated.View style={{ /* ... some animated props */ }}>
<ScrollView ref={(flatListRef) => { this.myRef = flatListRef; }}>
{/*
this.renderItem is almost the same as the
renderItem method used on the FlatList
*/}
{ this.state.dataArray.map(this.renderItem) }
</ScrollView>
</Animated.View>
Please note that ScrollView does not have a scrollToIndex() method, so you'll have to cope with manually keeping track of child positions, and maybe, implement a scrollToIndex method of your own.
I am not making this the answer to my question, because the underlying issue remains. But as a workaround, maybe you can go with it and call it a day...
TL;DR;
this.myRef = React.createRef();
this.myRef.current.doSomething(); // note the use of 'current'
Long version:
While the idea behind what I was trying was correct, the error in my original post seems to be quite stupid. In my defense, the docs were not clear (probably...). Anyway...
React.createRef returns an object with a few fields on it, all of them useless for the developer (used by React in the back) - except one: current.
This prop holds the current reference to the underlying component that the ref is attached to. The main ref object is not usable for the purpose I meant to in my original question above.
Instead, this is how I should've used the ref correctly:
this.myRef.current.scrollToIndex(...)
Hold up, don't crash
Both the main myRef object, and the current field will be null if the component has not yet mounted, has unmounted at any point later, or if the ref cannot be attached to it for some reason. As you may know (or found out later), null.something will throw an error. So, to avoid it:
if ((this.myRef !== null) && (this.myRef.current !== null)){
this.myRef.current.scrollToIndex(...);
}
Extra insurance
If you try to call an undefined value as a function on a field on the ref, your code will crash. This can happend if you mistakenly reuse the same ref on multiple components, or if the component you attached it to does not have that method (i.e. View does not have a scrollTo method). To fix this you have two solutions:
// I find this to be the most elegant solution
if ((this.myRef !== null) && (this.myRef.current !== null)) {
if (typeof this.myRef.current.scrollToIndex === "function") {
this.myRef.current.scrollToIndex(...);
}
}
or
if ((this.myRef !== null) && (this.myRef.current !== null)) {
if (typeof this.myRef.current.scrollToIndex === "function") {
try {
this.myRef.current.scrollToIndex(...);
} catch (error) {
console.warn("Something went wrong", error);
}
}
}
I hope this to be useful for anyone else learning to use refs in React. Cheers :)
With Animated.ScrollView:
Create a ref to your FlatList (the old way only works):
<ScrollView ref={ (ref) => (this.MyRef=ref) } />
Access scrollToIndex using this.myRef.getNode().scrollToIndex
Animated.FlatList is currently not working unfortunately...
With FlatList:
Create a ref to your FlatList by:
<FlatList ref={ this.flatListRef } />
constructor(props) {
super(props);
this.flatListRef = React.createRef();
}
Access scrollToIndex using this.flatListRef.current.scrollToIndex
Also make sure to wrap your code inside an if statement like:
if (this.myRef.getNode()) { this.flatListRef.getNode().scrollToIndex(); }
o do not know if this will help you... it scroll to a especific item in the list:
/*Example to Scroll to a specific position in scrollview*/
import React, { Component } from 'react';
//import react in our project
import {
View,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
Image,
TextInput,
} from 'react-native';
//import all the components we needed
export default class App extends Component {
constructor() {
super();
//Array of Item to add in Scrollview
this.items = [
'zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten ',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
'sixteen',
'seventeen',
'eighteen',
'nineteen',
'twenty ',
'twenty-one',
'twenty-two',
'twenty-three',
'twenty-four',
'twenty-five',
'twenty-six',
'twenty-seven',
'twenty-eight',
'twenty-nine',
'thirty',
'thirty-one',
'thirty-two',
'thirty-three',
'thirty-four',
'thirty-five',
'thirty-six',
'thirty-seven',
'thirty-eight',
'thirty-nine',
'forty',
];
//Blank array to store the location of each item
this.arr = [];
this.state = { dynamicIndex: 0 };
}
downButtonHandler = () => {
if (this.arr.length >= this.state.dynamicIndex) {
// To Scroll to the index 5 element
this.scrollview_ref.scrollTo({
x: 0,
y: this.arr[this.state.dynamicIndex],
animated: true,
});
} else {
alert('Out of Max Index');
}
};
render() {
return (
<View style={styles.container}>
<View
style={{
flexDirection: 'row',
backgroundColor: '#1e73be',
padding: 5,
}}>
<TextInput
value={String(this.state.dynamicIndex)}
numericvalue
keyboardType={'numeric'}
onChangeText={dynamicIndex => this.setState({ dynamicIndex })}
placeholder={'Enter the index to scroll'}
style={{ flex: 1, backgroundColor: 'white', padding: 10 }}
/>
<TouchableOpacity
activeOpacity={0.5}
onPress={this.downButtonHandler}
style={{ padding: 15, backgroundColor: '#f4801e' }}>
<Text style={{ color: '#fff' }}>Go to Index</Text>
</TouchableOpacity>
</View>
<ScrollView
ref={ref => {
this.scrollview_ref = ref;
}}>
{/*Loop of JS which is like foreach loop*/}
{this.items.map((item, key) => (
//key is the index of the array
//item is the single item of the array
<View
key={key}
style={styles.item}
onLayout={event => {
const layout = event.nativeEvent.layout;
this.arr[key] = layout.y;
console.log('height:', layout.height);
console.log('width:', layout.width);
console.log('x:', layout.x);
console.log('y:', layout.y);
}}>
<Text style={styles.text}>
{key}. {item}
</Text>
<View style={styles.separator} />
</View>
))}
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 30,
},
separator: {
height: 1,
backgroundColor: '#707080',
width: '100%',
},
text: {
fontSize: 16,
color: '#606070',
padding: 10,
},
});
if i completly wrong, tell me...
Because ScrollView has no scrollToOffset function and It has only scrollTo function.
So let use function scrollTo with ScrollView or scrollToOffset with FlatList and it works normal.
If you are working with 'KeyboardAwareFlatList' this worked nicely:
https://github.com/APSL/react-native-keyboard-aware-scroll-view/issues/372
In short, use useRef and use the innerRef property of the KeyboardAwareFlatList rather than the ref property.

React Native pseudo leaking memory after remove elements from screen (ios)

I'm experiencing some out of memory crashes in production. Trying to isolate the problem I could make a small app to reproduce the issue.
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
const { count } = this.state;
const extraContent = new Array(200 * count).fill().map((_, index) => (
<View key={index}><Text>Line {index}</Text></View>
));
return (
<View style={styles.container}>
<View style={styles.actions}>
<TouchableOpacity onPress={() => this.setState({ count: count + 1})}>
<View style={styles.button}>
<Text style={styles.buttonText}>Add</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => count > 0 && this.setState({ count: count - 1})}>
<View style={styles.button}>
<Text style={styles.buttonText}>Remove</Text>
</View>
</TouchableOpacity>
</View>
<View>
<Text>Current count: {count}</Text>
<View>{extraContent}</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
width: '100%',
},
actions: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
buttonText: {
color: '#ffffff',
},
button: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#95afe5',
height: 50,
width: 100,
marginBottom: 5,
borderRadius: 5,
},
});
The code adds and removes some views from the screen when you press and or remove. It's expected that pressing Add three times and then Remove three times one would end with the same amount of memory use. What's really happening is that some of the memory is not released as shown in the graph:
It's interesting that adding again the three times and removing three times the peak of memory consumption is lower than the first round and there's no leak, but if we change to add/remove five times there's an extra pseudo leak.
I call it pseudo leak because from time to time, could understand why, a good portion of this retained memory is released, but it never comes back to the original baseline. It makes me believe that this effect may not be an actual leak, but some kind of cache instead.
In my production app this effect has reached 150+ MB leading to OOM crashes on devices with 1GB of RAM.
Does any one know what is it and if there's a way to avoid this behavior?
Perhaps your onPress props are implicitly returning data unnecessarily. What happens if you put {} after the arrow to prevent a return. Does it make any difference?
You may be able to do something for your setState as well. Consider trying: setState(() => {…code})
This might also be helpful:
When to use React setState callback
Sorry, this is more of a comment, but I wanted to add in some code blocks to help communicate. I think your example is defeating some of React's caching by creating the elements each time inside the render function. Is the result any different if the elements in extraContent are more stable? One way to do this is below:
Create extraContent outside of render, store it in state, and make the remove function return a slice of the array. Something like:
// in constructor
this.state = {
extraContent: [],
};
// in component
add() {
const totalElements = this.state.extraContent.length;
this.setState({ extraContent: this.state.extraContent.concat(new Array(200).fill().map((_, index) => (
<View key={totalElements + index}><Text>Line {totalElements + index}</Text></View>
))
});
}
remove() {
const totalElements = this.state.extraContent.length;
if (totalElements > 0) {
this.setState({ extraContent: this.state.extraContent.slice(0, totalElements - 200) });
}
}
render() {
// cut out everything before the return
// use add and remove functions in your TouchableOpacity components
}
I'm mentioning this because it's possible the problem you've identified in your example is different from the one in your real app, unless you are employing the same strategy for creating components there.

TextInput value prop not taking proper state value

I am trying to build an input field which will only take numbers as input. Minimal component definition to explain my problem is as below
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
text: 'PlaceHolder'
}
}
sanitizeInput(input) {
let sanitizedInput = input.replace(/[^0-9]/g, '');
this.setState({text: sanitizedInput});
}
render() {
console.log("In render - ", this.state.text);
return (
<View style={{flex: 1, justifyContent: 'center'}}>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.sanitizeInput(text)}
value={this.state.text}
/>
</View>
);
}
}
But, when I am executing it, I am not getting the desired result. It seems that the TextInput is not respecting the value prop passed into it. The console.log is clearly showing desired value to be shown in TextInput, but I am not able to get that value properly in TextInput of device.
A video describing my problem is posted here

How to update a text input on change

I'm trying to update a text input as it changes but it doesn't seem to work.
Here's my simplified example:
var Message = React.createClass({
getInitialState: function() {
return {textValue: ''};
},
render: function() {
return (
<View style={[styles.container]}>
<ProfilePicture userId={this.props.userId}/>
<TextInput
ref={component => this._textInput = component}
style={{flex: 8, paddingLeft: 5, fontSize: 15}}
onChangeText={this._handleChange}
//multiline={true}
defaultValue={""}
value={this.state.textValue}
returnKeyType={"send"}
blurOnSubmit={false}
autoFocus={true}
enablesReturnKeyAutomatically={true}
onSubmitEditing={this._onSubmit}
/>
</View>
);
},
_handleChange: function(text) {
this.setState({textValue: "foo"});
},
_onSubmit: function(event) {
this._clearText();
},
_clearText: function() {
this._textInput.setNativeProps({text: ''});
},
});
I'm expecting that as soon as someone enters in some text it gets automatically altered to read "foo" but this doesn't work.
Any ideas?
UPDATE
Plot thickens,
If I call the same function for onBlur it works but only when there is no text already in the text input. If I change the function to set the value using this._textInput.setNativeProps({text: 'foo'}); instead of this.setState({textValue: "foo"}); then it works both when the text input is empty and has data.
Example:
render: function() {
return (
<TextInput
ref={component => this._textInput = component}
style={{flex: 8, paddingLeft: 5, fontSize: 15}}
onChangeText={this._handleChange}
onBlur={this._handleChange}
//multiline={true}
defaultValue={""}
value={this.state.textValue}
returnKeyType={"send"}
blurOnSubmit={false}
autoFocus={true}
enablesReturnKeyAutomatically={true}
onSubmitEditing={this._onSubmit}
/>
);
},
_handleChange: function(text) {
// what to do here check if there are youtube videos?
this._textInput.setNativeProps({text: 'foo'});
}
So in the above the _handleChange works for onBlur but not for onChangeText. Weird right?
Not really an optimal solution but looking at the react native code for react-native v 0.17.0 it looks like any changes made to the component's value during onChange don't take affect.
The code has changed on HEAD and this could fix it. https://github.com/facebook/react-native/blob/master/Libraries/Components/TextInput/TextInput.js#L542
To get around this you can wrap the code to reset the text inputs value in a setTimeout like this
var self = this;
setTimeout(function() {self._textInput.setNativeProps({text: newText}); }, 1);
This creates a new change outside of the current change event.
Like I said not an optimal solution but it works.
There is another issue that the cursor position needs to be updated if the new text is larger than the old text, this isn't available on master yet but there is a PR that looks like it is close to being merged. https://github.com/facebook/react-native/pull/2668
You need to bind your onChangeText to this. Without that in the _handleChange function "this" does not refer to the component and thus setState is not going to work the way you expect it to.
<TextInput
ref={component => this._textInput = component}
style={{flex: 8, paddingLeft: 5, fontSize: 15}}
onChangeText={this._handleChange.bind(this)} // <-- Notice the .bind(this)
//multiline={true}
defaultValue={""}
value={this.state.textValue}
returnKeyType={"send"}
blurOnSubmit={false}
autoFocus={true}
enablesReturnKeyAutomatically={true}
onSubmitEditing={this._onSubmit}
/>