Cannot set property '0' of null in React Native ref - javascript

I am trying to set up a React Native ref like here, only in a class component:
https://snack.expo.io/PrrDmZ4pk
Here's my code:
class DetailBody extends Component {
constructor() {
super();
this.myRefs = React.createRef([]);
}
clickText(index) {
this.myRefs.current[index].setNativeProps({ style: { backgroundColor: '#FF0000' } });
}
render() {
if (this.props.article.json.results.length === 0) {
return <Loading />;
}
return (
<View >
<View>
<View ref={this.props.highlight} nativeID="some-id" >
{this.props.article.json.results.map((content, index) => (
<TouchableOpacity onPress={() => this.clickText(index)}>
<View key={index} ref={el => this.myRefs.current[index] = el}>{content}</View>
</TouchableOpacity>
</View>
</View>
</View>
This should theoretically let me add a background colors when my ref is clicked, much like the snack I linked to above.
However what I actually see is this:
This seems to be related to .current inside my ref being null, despite passing a default value.
How do I fix this error?

If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element.
Haven't really used it this way but I think you might just need to do this in the constructor:
this.myRefs = React.createRef();
this.myRefs.current = [];

Related

How can I pass a selected object from an array (firestore) to another class in react native?

I have a question about passing specific Objects from an array to another class in react native. If I press the button, all objects go to another class instead of the one I need, but I want the one which I pressed. Do I have to create a function for the selected object/ key or is it enough if I change something in the onPress={} method?
I need to change something here -> TouchableOpacity onPress={()=>this.props.navigation.navigate('detail', item,{})}> Item must be the document (firestore) with all the information I have, but how can I pass the object from the array only?
Here are also some pictures for a better understanding, thank you in advance
export default class foods extends Component {
constructor(props) {
super(props);
this.state = {
posts:[],
}
}
componentDidMount() {
StatusBar.setHidden(true)
this.subscriber = database.collection('users').onSnapshot(docs => {
let posts = []
docs.forEach(document => {
posts.push({
id: document.id,
...document.data(),
})
})
this.setState({posts})
console.log(posts)
})
}
render() {
return (
<ScrollView>
<View style={styles.container}>
{
this.state.posts.map((item) => {
return (
<View >
{
Object.keys(item.post).map((key) => {
return (
<View style={styles.card}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('detail', item, {})}>
<Text>{item.firstName} {item.lastName}</Text>
<Image style={styles.cardImage} source={{ uri: item.post[key].image }} />
<Text>{item.post[key].essenname}</Text>
</TouchableOpacity>
</View>
)
})
}
</View>
)
})
}
</View>
</ScrollView>
);
}
}
export default class detail extends Component {
constructor(props) {
super(props);
this.state = {
post: [this.props.route.params]
}
};
componentDidMount() {
StatusBar.setHidden(true)
console.log('Array : ', this.state.post)
}
render() {
return (
<View style={styles.container}>
<ScrollView>
{this.state.post.map((item) => {
return (
Object.keys(item.post).map((key) => {
return (
<View style={styles.card}>
<Image style={styles.cardImage} source={{ uri: item.post[key].image }} />
<Text>{item.post[key].essenname}</Text>
</View>
)
})
)
})}
</ScrollView>
</View>
);
}
}
I want to pass either Post[0] or Post[1] but not both:
![](https://i.stack.imgur.com/3recX.png)
This is my output, which is the same output after navigating the item to the detail screen:
![](https://i.stack.imgur.com/WMqrd.png)
Detail screen on the right:
![](https://i.stack.imgur.com/gIX0V.png)
The document with the array 'post':
![](https://i.stack.imgur.com/dO1uQ.png)
There is no need to use Object.keys to loop trought your posts, just use item.post.
In your navigation params, just use a spred operator for the root key and pass the post selected: this.props.navigation.navigate('detail', {posts: {...this.state.posts, post: [p]}}).
In your detail component state your are missing to access the params, it should be:
this.state={
post:[this.props.route.params.posts]
}
and replace Object.keys with item.post

How to change components states from another component?

I have an ApplicationHeader component in my applicationn that is called in App.js
ApplicationHeader.js
export default class ApplicationHeader extends Component {
constructor(props){
super(props)
this.state = {
loggedUser: null,
}
}
render() {
return(
<View style={AppStyle.header}>
<View style={{flex:1}}>
<Image source={require('../assets/images/logo.png')} style = { AppStyle.headerLogo}/>
</View>
<View style={{flex:0.1}}>
<Image source={require('../assets/images/bag-topbar.png')} style = { AppStyle.headerBag}/>
</View>
<View style={{flex:0.15}}>
<Image source={this.state.loggedUser !== null ? imgWishListLogged : imgWishList} style = { AppStyle.headerWishlist}/>
</View>
</View>
);
}
}
My App.js renders ApplicationHeader
render() {
return (
<SafeAreaView forceInset={{ bottom: 'never'}} style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<ApplicationHeader />
<AppNavigator />
</SafeAreaView>
);
}
Now, In my Homepage.js I would like to send userLogged state or prop to ApplicationHeader to change the icon when user is logged.
class Homepage extends Component {
render() {.....
Change state to props (or pass the props to the state in your constructor)
<Image source={this.props.loggedUser !== null ? imgWishListLogged : imgWishList} style = { AppStyle.headerWishlist}/>
and then pass the variable in the props of the ApplicationHeader
<ApplicationHeader loggedUser={myVar} />
More info on props here
generally the answer is that you "pass the data higher up" so you don't think about it as "sending from one component to another," you let one component work up the state tree to pass the authentication status higher up in the component tree.
in your example you would not store the state in the lower-level objects, just pass them from the higher components as props.
After some attempts, the best way I found was to implement Redux.
Then, with Redux I just used isLoggedUser prop like follow:
{!isLogged && ( <Image source={imgWishList} style = { AppStyle.headerWishlist}/>)}
{isLogged && ( <Image source={imgWishListLogged} style = { AppStyle.headerWishlist}/>)}

React Native - functional component ref in child component

In render() of my RN component class I call functional component A which loads a Carousel. This Carousel in A then calls functional component B in its renderItem prop. B contains a FlatList w ref defined as <FlatList ref={ref => (this.list = ref)} />.
So A is successfully accessing this.list.scrollToIndex() in an onTouch call defined in A. What I need to do is call this.list.scrollToOffset() in B where the FlatList and the ref are defined - but when I do that I get an error TypeError: TypeError: null is not an object (evaluating '_this.list.scrollToOffset')
I've tried using React.createRef() at the class level but that is not working either.
Here is the code:
//component B
renderList = () => {
if (scrollPosition>0) {
//this call to list fails
this.list.scrollToOffset({
offset: scrollPosition
})
}
return (
<FlatList
ref={ref => (this.list = ref)}
...
/>
)
}
//component A
renderCarousel = () => {
return (
<View style={{ flex: 1 }}>
<View>
<TouchableOpacity
onPress={() => {
this.list.scrollToIndex({ index: 0 }) //this call to list works
}}
>
</TouchableOpacity>
</View>
{
showList && (
<Carousel
renderItem={this.renderList}
/>
)
}
</View>
)
}
I'm guessing I'm in that weird and dreaded js this zone. Any ideas?? TIA!

React Native FlatList - Re-Renders / Mounts child items more than once [duplicate]

I have this code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
}
this._handleRenderItem = this._handleRenderItem.bind(this);
this._keyExtractor = this._keyExtractor.bind(this);
}
componentDidMount() {
let success = (response) => {
this.setState({ dataSource: response.data });
};
let error = (err) => {
console.log(err.response);
};
listarProdutos(success, error);
}
_keyExtractor = (item, index) => item._id;
_handleRenderItem = (produto) => {
return (
<ItemAtualizado item={produto.item} />
);
}
render() {
return (
<Container style={styles.container}>
<Content>
<Card>
<CardItem style={{ flexDirection: 'column' }}>
<Text style={{ color: '#323232' }}>Produtos atualizados recentemente</Text>
<View style={{ width: '100%' }}>
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={this._handleRenderItem}
/>
</View>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
export default Home;
The function _handleRenderItem() is being called twice and I can't find the reason. The first time the values inside my <ItemAtualizado /> are empty, but the second was an object.
This is normal RN behavior. At first, when the component is created you have an empty DataSource ([]) so the FlatList is rendered with that.
After that, componentDidMount triggers and loads the updated data, which updates the DataSource.
Then, you update the state with the setState which triggers a re render to update the FlatList.
All normal here. If you want to try, load the datasource in the constructor and remove the loading in the componentDidMount. It should only trigger once.
If you want to control render actions, you can use shouldComponentUpdate method.
For example:
shouldComponentUpdate(nextProps, nextState){
if(this.state.friends.length === nextState.friends.lenght)
return false;
}
it will break render if friends count not change.
I recreated the issue in this snack. https://snack.expo.io/B1KoX-EUN - I confirmed you can use shouldComponentUpdate(nextProps, nextState) to diff this.state or this.props and return true/false - https://reactjs.org/docs/react-component.html#shouldcomponentupdate docs say this callback should be used only for optimization which is what we're doing here.

ReactNative error onPress

on clicking the text, i get an error saying "undefined is not an object (evaluating '_this2.categoryClicked.bind')"
I think the error is "onPress={()=>this.categoryClicked.bind(this)}" there must be a different way to call the categoryClicked function when the button is clicked. What is wrong in my code ?
class CategoriesView extends React.Component {
constructor(props){
super(props)
}
categoryClicked(){
this.props.categoryPressed(this.props.Category);
}
renderSubCategory(){
return(
this.props.Category.sub_category.map(function(subCategory, i){
return(
<View style={styles.abcd}>
<TouchableHighlight onPress={()=>this.categoryClicked.bind(this)}>
<Text>{subCategory.title}</Text>
</TouchableHighlight>
</View>
)
})
)
}
render(){
return(
<View style={{flex:1}}>
<View style={styles.avf}>
<Text>{this.props.Category.heading}</Text>
</View>
<View style={styles.ddd}>
{this.renderSubCategory()}
</View>
</View>
)
}
}
I believe what you want to do is onPress={this.categoryClicked.bind(this)} instead of an arrow function. .bind(this) returns a function with the context correctly binded to this, therefore, it does not get invoked.
Also, I suggest putting the binding in constructor, as you don't want the binding to happen every time the component re-renders.
e.g.
constructor(props) {
super(props);
this.categoryClicked = this.categoryClicked.bind(this);
}
Then just use onPress={this.categoryClicked}
If you want to pass down sub-category, you can do
constructor(props) {
super(props);
this.subcategoryClicked = props.Category.sub_categories.map(sub_category => this.categoryClicked.bind(this, sub_category));
}
then use like this in render:
this.props.Category.sub_category.map(function(subCategory, i) {
<View style={styles.abcd}>
<TouchableHighlight onPress={this.subcategoryClicked[i]}>
<Text>{subCategory.title}</Text>
</TouchableHighlight>
</View>
P.S, I am not sure if this is a good pattern to follow. Stick to this.categoryClicked(bind, subcategory) if you are not comfortable with doing this. This is one of those things that I am not sure if the optimization is worth it.
this in onPress={()=>this.categoryClicked.bind(this)}> points to sub_category.map function. It should instead point to the class. Can be done this way instead
return (
this.props.Category.sub_category.map((subCategory, i) => { // <--- this way context is preserved
// other lines
onPress={()=>this.categoryClicked.bind(this, subCategory)}>
// other lines
})
);
in categoryClicked method should be accessible
categoryClicked(category){
this.props.categoryPressed(category);
}

Categories

Resources