How do i navigate to a new screen from FlatList? - reactjs

I would like to navigate to a screen called GridVid when clicking the items in my FlatList. I can't figure out how to do this as the
onPress={() => this.props.navigation.navigate('GridVid')}
only will work being called in App.js as thats where the StackNavigator is defined, not the ListItem class (which is in a separate file called ListItem.js)
//App.js
class SettingsClass extends Component {
constructor(props) {
super(props)
this.state = {
columns: 3, //Columns for Grid
};
}
render() {
const {columns} = this.state
return (
<View style={styles.grid}>
<FlatList
numColumns={columns}
data={[
{uri:'https://randomuser.me/api/portraits/thumb/women/12.jpg'},
{uri:'https://randomuser.me/api/portraits/thumb/women/13.jpg'},
{uri:'https://randomuser.me/api/portraits/thumb/women/14.jpg'},
]}
renderItem={({item}) => {
return (<ListItem itemWidth={(ITEM_WIDTH-(10*columns))/columns}
image={item}
/>
)
}}
keyExtractor={
(index) => { return index }
}
/>
</View>
);
}
}
//Settings Class swipes to GridVid
const SettingsStack = createStackNavigator({
SettingsScreen: {
screen: SettingsClass
},
GridVid: {
screen: GridVidClass
},
});
//ListItem.js
export default class ListItem extends Component {
state = {
animatepress: new Animated.Value(1)
}
animateIn() {
Animated.timing(this.state.animatepress, {
toValue: 0.90,
duration: 200
}).start()
}
animateOut() {
Animated.timing(this.state.animatepress, {
toValue: 1,
duration: 200
}).start()
}
render() {
const {itemWidth} = this.props
return (
<TouchableWithoutFeedback
onPressIn={() => this.animateIn()}
onPressOut={() => this.animateOut()}
onPress={() => this.props.navigation.navigate('GridVid')} //WONT WORK HERE in this file!!!!
>
<Animated.View style={{
margin:5,
transform: [{scale: this.state.animatepress}] }}>
<Image style={{width:itemWidth, height: 100}} source={this.props.image}></Image>
</Animated.View>
</TouchableWithoutFeedback>
);
}
}
//GridVid.js
export default class GridVidClass extends Component {
render() {
return (
<View style={styles.container}>
<Text>On GridVid </Text>
</View>
);
}
}
Is there any way to call onPress={() => this.props.navigation.navigate('GridVid') within the FlatList (or anywhere in App.js) as opposed to ListItem (where it wont work at the moment)? In ListItem however, at least i'm clicking the image that i want and have some reference to what i'm clicking.

What you need to do is pass a onPress prop to your ListItem that will make the navigation happen.
//App.js
class SettingsClass extends Component {
constructor(props) {
super(props)
this.state = {
columns: 3, //Columns for Grid
};
}
render() {
const {columns} = this.state
return (
<View style={styles.grid}>
<FlatList
numColumns={columns}
data={[
{uri:'https://randomuser.me/api/portraits/thumb/women/12.jpg'},
{uri:'https://randomuser.me/api/portraits/thumb/women/13.jpg'},
{uri:'https://randomuser.me/api/portraits/thumb/women/14.jpg'},
]}
renderItem={({item}) => {
return (<ListItem itemWidth={(ITEM_WIDTH-(10*columns))/columns}
image={item}
onPress={() => this.props.navigation.navigate('GridVid') // passing the onPress prop
/>
)
}}
keyExtractor={
(index) => { return index }
}
/>
</View>
);
}
}
//Settings Class swipes to GridVid
const SettingsStack = createStackNavigator({
SettingsScreen: {
screen: SettingsClass
},
GridVid: {
screen: GridVidClass
},
});
//ListItem.js
export default class ListItem extends Component {
state = {
animatepress: new Animated.Value(1)
}
animateIn() {
Animated.timing(this.state.animatepress, {
toValue: 0.90,
duration: 200
}).start()
}
animateOut() {
Animated.timing(this.state.animatepress, {
toValue: 1,
duration: 200
}).start()
}
render() {
const {itemWidth} = this.props
return (
<TouchableWithoutFeedback
onPressIn={() => this.animateIn()}
onPressOut={() => this.animateOut()}
onPress={this.props.onPress} // using onPress prop to navigate
>
<Animated.View style={{
margin:5,
transform: [{scale: this.state.animatepress}] }}>
<Image style={{width:itemWidth, height: 100}} source={this.props.image}></Image>
</Animated.View>
</TouchableWithoutFeedback>
);
}
}

ListItem is not in StackNavigator so it doesn't know what navigation is
You can go with like Vencovsky's answer or pass navigation prop from ListItem's parent component
<ListItem
itemWidth={(ITEM_WIDTH-(10*columns))/columns}
image={item}
navigation={this.props.navigation}
/>

Related

React native state is not working correctly

I have this component :
import React, { Component } from "react";
import { Text, View, Image, TouchableOpacity } from "react-native";
import { styles } from "./styles";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { getAccounts } from "../../redux/user/selectors";
const UserMenuAccount = ({ active, account, balance }) => (
<View style={styles.accountContainer}>
<Image
source={require("../../../assets/Usermenu/check.png")}
style={[styles.icon, { opacity: active ? 1 : 0 }]}
/>
<Text style={styles.text}>{account}</Text>
<Text style={[styles.text, { opacity: 0.5 }]}>{balance} ETH</Text>
</View>
);
class UserMenuAccounts extends Component {
state = {
userAccounts: [],
};
updateData = index => {
const {userAccounts} = this.state
const { GetAccounts } = this.props;
const data =[...GetAccounts]
data[index].isActive = data[index].isActive ? false : true
this.setState({userAccounts:data})
}
render() {
const { userAccounts } = this.state;
const { GetAccounts } = this.props;
return (
<View>
<Text style={{ opacity: 0.5, fontSize: 16, fontWeight: "bold" }}>
Click to switch
</Text>
{userAccounts.map((users, index) => {
return (
<TouchableOpacity onPress={()=>this.updateData(index)}>
<UserMenuAccount
account={`${users}`}
balance={0}
active={active.isActive || false}
key={index}
/>
</TouchableOpacity>
);
})}
</View>
);
}
}
const mapStateToProps = createStructuredSelector({
GetAccounts: getAccounts,
});
export default connect(mapStateToProps, null)(UserMenuAccounts)
;
The problem is I'm trying to setState active to only one UserMenuAccout but it sets active for all of them , could you please suggest me the way I can fix this ? thanks in advance , I'm grabbing accounts from redux store and mapping them as shown in the code
Try this might help
class UserMenuAccounts extends Component {
constructor(props){
this.state = {
userAccounts: props.GetAccounts,
};
}
updateData(index){
const data = […this.state.userAccounts];
data[index].isActive = data[index].isActive? false: true;
this.setState({userAccounts: data};
}
render() {
const { userAccounts } = this.state;
return (
<View>
<Text style={{ opacity: 0.5, fontSize: 16, fontWeight: "bold" }}>
Click to switch
</Text>
{userAccounts.map((users, index) => {
return (
<TouchableOpacity onPress={()=> this.updateData(index)}>
<UserMenuAccount
account={`${users}`}
balance={0}
active={users.isActive || false}
key={index}
/>
</TouchableOpacity>
);
})}
</View>
);
}
}

How to set the state from a button inside static navigationoption react-native?

I want to click the TouchableOpacity and set the state true so that will open. I am getting error. and how to align the button in center at the header? alignSelf is not working.
`
import React, {Component} from 'react';
import {
StyleSheet,
SafeAreaView,
View,
TouchableOpacity,
Text,
} from 'react-native';
import Menu from '../../src/components/menubar';
export default class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {isMenubarDisplayed: false};
}
static navigationOptions = {
headerTitle: () => {
return (
<TouchableOpacity
onPress={()=> this.setState({isMenubarDisplayed: true})}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
);
},
headerTitleStyle: {
alignSelf: 'center',
flex: 1,
},
};
render() {
return (
<SafeAreaView style={styles.container}>
{this.state.isMenubarDisplayed ? (
<Menu />
) : null}
</SafeAreaView>
);
}
}`
You need to try this, expo-snack .
This is my below code for search.js ,
import * as React from 'react';
import { Text, View, StyleSheet,TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import Menu from './menu';
import Icon from 'react-native-vector-icons/FontAwesome';
export default class Search extends React.Component {
constructor(props){
super(props);
this.state={
isMenubarDisplayed: false,
}
}
static navigationOptions = ({ navigation }) => {
return {
headerTitle: () => {
return (
<TouchableOpacity onPress={navigation.getParam('toggleMenu')}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
);
},
};
};
toggleMenu = () => {
this.setState({ isMenubarDisplayed: !this.state.isMenubarDisplayed})
}
renderMenu = () => (
<Menu />
)
componentDidMount(){
this.props.navigation.setParams({
toggleMenu: this.toggleMenu
});
}
render() {
return (
<View style={styles.container}>
{this.state.isMenubarDisplayed?this.renderMenu():<View></View>}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Hope it helps. feel free for doubts.
This is all you need https://reactnavigation.org/docs/en/header-buttons.html#header-interaction-with-its-screen-component
static navigationOptions = ({ navigation }) => {
return {
headerTitle: () => {
return (
<View style={{ flex: 1, alignItems: 'center' }}>
<TouchableOpacity onPress={navigation.getParam('toggleMenu')}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
</View>
);
},
};
};
componentDidMount() {
this.props.navigation.setParams({ toggleMenu: this.toggleMenu });
}
toggleMenu = () => {
this.setState({isMenubarDisplayed: true});
}

How can I make componentDidMount render again?

I'm fetching api(makeup API) in Explore component and using it also in Explorebutton.
Im taking brands as a button in ExploreButtons. When i click button in FlatList element in ExploreButtons I want to see images from api in second FlatList in ExploreButtons. Is there a way componentDidMount can rerender when i click button?
import React, { Component } from 'react'
import { View } from 'react-native'
import ExploreButtons from './ExploreButtons'
export default class Explore extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
makeupApi: 'http://makeup-api.herokuapp.com/api/v1/products.json',
}
}
callbackFunction = (item) => {
this.setState({
makeupApi: 'http://makeup-api.herokuapp.com/api/v1/products.json?brand=' + item,
})
}
async componentDidMount() {
try {
const response = await fetch(this.state.makeupApi);
const responseJson = await response.json();
this.setState({
isLoading: false,
dataSource: responseJson,
}, function () {
});
const reformattedArray = this.state.dataSource.map(obj => {
var rObj = {};
rObj = obj.brand;
return rObj;
});
this.setState({
duplicatesRemoved: reformattedArray.filter((item, index) => reformattedArray.indexOf(item) === index)
})
}
catch (error) {
console.error(error);
}
};
render() {
console.log(this.state.makeupApi)
return (
<View style={{ flex: 1 }}>
<ExploreButtons
api={this.state.dataSource}
removedDuplicatesFromAPI={this.state.duplicatesRemoved}
parentCallback={this.callbackFunction}
makeupApi= {this.state.makeupApi} />
</View>
)
}
}
export default class ExploreButtons extends Component {
getBrandImages = (item) => {
this.props.parentCallback(item)
}
render() {
return (
<View style={{ flex: 1 }}>
<View>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={this.props.removedDuplicatesFromAPI}
renderItem={({ item }) =>
<TouchableOpacity
style={styles.exploreButtons}
onPress={() => {
this.getBrandImages(item)
}}
>
<Text>{item}</Text>
</TouchableOpacity>
}
keyExtractor={item => item}
/>
</View>
<View>
<FlatList
data={this.props.api}
renderItem={({ item }) =>
<View>
<Image source={{ uri: item.image_link }}
style={{
alignSelf: "center",
width: '100%',
height: 300,
}} />
</View>
}
keyExtractor={item => item.id.toString()} />
</View>
</View>
)
}
}
You could just put all the logic inside componentDidMount on another function and call it when you call the callback. As a first very rough approach this would work:
Notes: you don't really need the API URL in the state, put the item on the state and construct the URL based on it.
import React, { Component } from 'react';
import { View } from 'react-native';
import ExploreButtons from './ExploreButtons';
export default class Explore extends Component {
API_URL = 'http://makeup-api.herokuapp.com/api/v1/products.json';
constructor(props) {
super(props);
this.state = {
isLoading: true,
item: null,
dataSource: null,
duplicatesRemoved: [],
};
}
getAPIURL(item) {
if(!item){
return API_URL
}
return `${API_URL}?brand=${item}`;
}
async fetchData(item) {
try {
const url = getAPIURL(item);
const response = await fetch(url);
const responseJson = await response.json();
this.setState({
isLoading: false,
dataSource: responseJson,
item,
});
const reformattedArray = responseJSON.map(({ brand }) => brand);
this.setState({
duplicatesRemoved: reformattedArray.filter(
(item, index) => reformattedArray.indexOf(item) === index,
),
});
} catch (error) {
console.error(error);
}
}
async componentDidMount() {
fetchData();
}
render() {
const { dataSource, duplicatesRemoved, item } = this.state;
return (
<View style={{ flex: 1 }}>
<ExploreButtons
api={dataSource}
removedDuplicatesFromAPI={duplicatesRemoved}
parentCallback={this.fetchData}
makeupApi={getURL(item)}
/>
</View>
);
}
}
export default class ExploreButtons extends Component {
getBrandImages = item => {
this.props.parentCallback(item);
};
render() {
const { removedDuplicatesFromAPI, api } = this.props;
return (
<View style={{ flex: 1 }}>
<View>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={removedDuplicatesFromAPI}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.exploreButtons}
onPress={() => {
this.getBrandImages(item);
}}
>
<Text>{item}</Text>
</TouchableOpacity>
)}
keyExtractor={item => item}
/>
</View>
<View>
<FlatList
data={api}
renderItem={({ item }) => (
<View>
<Image
source={{ uri: item.image_link }}
style={{
alignSelf: 'center',
width: '100%',
height: 300,
}}
/>
</View>
)}
keyExtractor={item => item.id.toString()}
/>
</View>
</View>
);
}
}
How can I make componentDidMount render again?
Not sure what you mean, but I think what you are asking is How can I make componentDidMount *run* again?, and to do that, you would need to have the same code in callbackFunction to run that again. componentDidMount will only run after the first time the component render.
Also notice that if you want to rerender the FlatList you need to pass extraData so it know that it needs to rerender.

Can't use react-native-snap-carousel

I would like use react-native-snap-carousel but when I try to init in I have an error :(
the exemple :
import Carousel from 'react-native-snap-carousel';
export class MyCarousel extends Component {
_renderItem ({item, index}) {
return (
<View style={styles.slide}>
<Text style={styles.title}>{ item.title }</Text>
</View>
);
}
render () {
return (
<Carousel
ref={(c) => { this._carousel = c; }}
data={this.state.entries}
renderItem={this._renderItem}
sliderWidth={sliderWidth}
itemWidth={itemWidth}
/>
);
}}
My code :
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Carousel from 'react-native-snap-carousel';
export default class App extends React.Component {
_renderItem ({item, index}) {
return (
<View style={styles.slide}>
<Text style={styles.title}>{ item.title }</Text>
</View>
);}
render () {
return (
<Carousel
ref={(c) => { this._carousel = c; }}
data={this.state.entries}
renderItem={this._renderItem}
sliderWidth={150}
itemWidth={100}
/>
);
}}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
}});
Screenshot
the same on app.js react Native
I'have see a issue (the same like me)
link to Github Issue
But not answer and issue be close
As the screenshot says, this.state.entries is null.
You must initialize it :
export default class App extends React.Component {
constructor() {
super()
this.state = {
entries: [],
}
}
_renderItem ({item, index}) {
return (
<View style={styles.slide}>
<Text style={styles.title}>{ item.title }</Text>
</View>
);}
render () {
return (
<Carousel
ref={(c) => { this._carousel = c; }}
data={this.state.entries}
renderItem={this._renderItem}
sliderWidth={150}
itemWidth={100}
/>
);
}}
In this example, entries: [] wont display anything since there's no object in it. You can initialize it with wanted data:
entries: [
{ title: 'hello' },
{ title: 'world' },
]
Btw, this issue has nothing to do with the plugin itself, even if they could catch it.

General solution for subscribing to listeners in react native

Is there a reusable way of subscribing to listener like keyboard events.
Actually I have a button with position absolute at the very bottom of my screen and when keyboard pops up it comes floating on top and that does not look very good.
So I am hiding that button when keyboard is visible but if you have similar scenario on multiple screens it becomes headache to add subscription on every screen currently I am doing it this way.
class Profile extends Component {
constructor(props) {
super(props);
this._keyboardDidShow = this._keyboardDidShow.bind(this);
this._keyboardDidHide = this._keyboardDidHide.bind(this);
}
componentDidMount() {
// subscribing to keyboard listeners on didMount
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
this._keyboardDidShow
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
this._keyboardDidHide
);
}
_keyboardDidShow() {
this.setState({
keyboardVisible: true,
});
}
_keyboardDidHide() {
this.setState({
keyboardVisible: false,
});
}
componentWillUnmount() {
// unsubscribing listeners on unMount
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
const AnimatedBottomButton = Animated.createAnimatedComponent(BottomButton);
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
{!this.state.keyboardVisible && (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
)}
</ScrollView>
);
}
}
I don't like the above solution since I have to add subscription related code to every Component I want to subscribe for keyboard events I am new to javascript and still learning it.
If any one out there can help me with some general solution of it would be very good.
Custom components come in handy in these situations. You can create a single component with desired behaviors implemented and then you can add that component to the screens you want to use.
Sample
export default class CustomButton extends Component {
state = {
visible: true
}
componentDidMount() {
// subscribing to keyboard listeners on didMount
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => this._toggleVisiblity(false)
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => this._toggleVisiblity(true)
);
}
_toggleVisiblity = (visible) => {
this.setState({ visible })
}
componentWillUnmount() {
// unsubscribing listeners on unMount
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
if (this.state.visible === false) return null
return (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
);
}
}
class Profile extends Component {
render() {
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
<CustomButton />
</ScrollView>
);
}
}
You can go a bit further if you like and create a HOC.
Sample
const withKeyboardEvents = WrappedComponent => {
return class extends Component {
state = {
visible: true,
};
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => this._toggleVisiblity(false)
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => this._toggleVisiblity(true)
);
}
_toggleVisiblity = visible => {
this.setState({ visible });
};
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
return (
<React.Fragment>
{this.state.visible === true && (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
)}
<WrappedComponent />
</React.Fragment>
);
}
};
};
class Profile extends Component {
render() {
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
</ScrollView>
);
}
}
export default withKeyboardEvents(Profile)

Resources