How to use Scrollview with KeyboardAvoidingView - reactjs

I am a new to react native and i wonder how to use scrollView with keyboardAvoid in the following scene.I have searched for some relevant information, but still have no clue.
Following picture may be more intuitive then explain.
enter image description here
import React,{useRef,useEffect,useState} from 'react';
import { Text, View, StyleSheet,TextInput,ScrollView,KeyboardAvoidingView,TouchableOpacity } from 'react-native';
export default function App() {
const [selectedItem,setSeLectedItem] = useState(null)
const [userTyping,setUserTyping] = useState(false);
const selected = (index) => {
setUserTyping(true)
setSeLectedItem(index)
}
const onBlur=()=>{
setUserTyping(false)
}
return (
<View style={styles.container}>
<KeyboardAvoidingView style={{flex:1}}>
<ScrollView style={styles.scroll} >
{
['1', '2', '3', '4','5','6','7'].map((item,index) => (
<TouchableOpacity onPress={()=>selected(index)} key={item}>
<View style={[styles.itemWrapper,selectedItem===index &&styles.selectedItem]}>
<Text style={styles.itemText}>TEST {item}</Text>
{
(selectedItem===index)&&userTyping&&
<InputFC
style={styles.itemInput}
placeholder='NO TEXT'
placeholderTextColor={'white'}
autoCapitalize={'none'}
onBlur={onBlur}
/>
}
</View>
</TouchableOpacity>
))
}
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
const InputFC = (props) => {
const {style,placeholder,placeholderTextColor,autoCapitalize,onBlur} = props
const inputRef = useRef(null);
useEffect(()=>{
if(inputRef.current) inputRef?.current?.focus()
})
return (
<TextInput
style={style}
ref={inputRef}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
autoCapitalize={autoCapitalize}
onBlur={onBlur}
/>)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#24292e',
},
scroll: {
flex: 1,
paddingHorizontal: 20,
},
itemWrapper:{
width: '100%',
paddingLeft:20,
paddingVertical:20,
marginBottom: 20,
backgroundColor: '#6b6965',
borderRadius:20,
},
itemText:{
fontSize:20,
fontWeight:'bold',
color:'white',
alignItems:'center',
justifyContent:'center',
marginBottom:5,
}
,
itemInput:{
fontSize:20,
color:'white',
},
selectedItem:{
borderWidth:3,
borderColor:'#2188ff'
}
});
Simplified Demo online:
https://snack.expo.dev/#bravo20203/demo-about-scrollview
Thanks.

You can make this work by changing the flex: 1 to different components and by adding
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{flex: 1}}
to the KeyboardAvoidingView. I have also wrapped your content into a SafeAreaView so it does not go into the header on iOS.
Since you are dynamically expanding the TextInput on press, which changes its height, I have added a keyboardVerticalOffset to the KeyboardAvoidingView and made the overflow visible.
The final code is as follows.
import React, { useEffect, createRef, useState, useRef } from 'react';
import { useHeaderHeight } from "#react-navigation/elements";
import {
Text,
View,
KeyboardAvoidingView,
ScrollView,
TextInput,
Platform,
TouchableOpacity,
StyleSheet,
SafeAreaView
} from 'react-native';
export default function App() {
const [selectedItem,setSeLectedItem] = useState(null)
const [userTyping,setUserTyping] = useState(false);
const selected = (index) => {
setUserTyping(true)
setSeLectedItem(index)
}
const onBlur=()=>{
setUserTyping(false)
}
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={Platform.OS === 'ios' && {flex: 1}}
keyboardVerticalOffset={30}
>
<ScrollView style={styles.scroll}>
{
['1', '2', '3', '4','5','6','7'].map((item,index) => (
<TouchableOpacity onPress={()=>selected(index)} key={item}>
<View style={[styles.itemWrapper,selectedItem===index &&styles.selectedItem]}>
<Text style={styles.itemText}>TEST {item}</Text>
{
(selectedItem===index)&&userTyping&&
<InputFC
style={styles.itemInput}
placeholder='NO TEXT'
placeholderTextColor={'white'}
autoCapitalize={'none'}
onBlur={onBlur}
/>
}
</View>
</TouchableOpacity>
))
}
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const InputFC = (props) => {
const {style,placeholder,placeholderTextColor,autoCapitalize,onBlur} = props
const inputRef = useRef(null);
useEffect(()=>{
if(inputRef.current) inputRef?.current?.focus()
})
return (
<TextInput
style={style}
ref={inputRef}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
autoCapitalize={autoCapitalize}
onBlur={onBlur}
/>)
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#24292e',
flex: 1
},
scroll: {
paddingHorizontal: 20,
overflow: "visible",
},
itemWrapper:{
width: '100%',
paddingLeft:20,
paddingVertical:20,
marginBottom: 20,
backgroundColor: '#6b6965',
borderRadius:20,
},
itemText:{
fontSize:20,
fontWeight:'bold',
color:'white',
alignItems:'center',
justifyContent:'center',
marginBottom:5,
}
,
itemInput:{
fontSize:20,
color:'white',
},
selectedItem:{
borderWidth:3,
borderColor:'#2188ff',
}
});
I have updated your snack here. Notice that I have tested this on iOS only.
If the keyboard is opened on select, the selected Input is now fully visible.

Related

TypeError: text.toLowerCase is not a function. (In 'text.toLowerCase()', 'text.toLowerCase' is undefined)

I am doing a project using MERN STACK in this I faced the above error.
ProductContainer.js
import React, { useState, useEffect } from 'react'
import { View, StyleSheet, ActivityIndicator, FlatList, Text} from 'react-native'
import { Container, Header, Icon, Item, Input } from 'native-base';
import ProductList from './ProductList';
import SearchedProduct from './SearchedProducts';
const data = require('../../assets/data/products.json');
const ProductContainer = () => {
const [products, setProducts ] = useState([]);
const [productsFiltered, setProductsFiltered] = useState([]);
const [focus, setFocus] = useState();
useEffect(() => {
setProducts(data);
setProductsFiltered(data);
setFocus(false);
return () => {
setProducts([])
setProductsFiltered([])
setFocus()
}
}, [])
const SearchProduct = (text) => {
setProductsFiltered(
products.filter((i) => i.name.toLowerCase().includes(text.toLowerCase()))
);
};
const openList = () => {
setFocus(true);
};
const onBlur = () => {
setFocus(flase);
};
return (
<Container>
<View style = {{ flexDirection: "row"}}>
<Input
width = "100%"
variant = "rounded"
placeholder="Search"
onFocus={openList}
onChangeText={(text) => SearchProduct(text)}
/>
</View>
{focus == true ? (
<SearchProduct
productsFiltered={productsFiltered}
/>
) : (
<View style={styles.container}>
<Text>Product Container</Text>
<View style={styles.listContainer}>
<FlatList
data={products}
numColumns={2}
renderItem={({item}) => <ProductList
key={item.brand}
item={item}/>}
keyExtractor={item => item.brand}
/>
</View>
</View>
)}
</Container>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default ProductContainer
SearchedProducts.js
import React from 'react';
import { View, StyleSheet, Dimensions} from 'react-native'
import { Content, Left, Body, ListItem, Thumbnail, Text } from 'native-base';
const SearchedProduct = (props) => {
const { productsFiltered } = props;
return(
<Content >
{productsFiltered.length > 0 ? (
productsFiltered.map((item) => (
<ListItem
key={item._id.$oid}
avatar
>
<Left>
<Thumbnail
source={{uri: item.image ?
item.image : 'https://cdn.pixabay.com/photo/2012/04/01/17/29/box-23649_960_720.png'
}}
/>
</Left>
<Body>
<Text>{item.name}</Text>
<Text note>{item.description}</Text>
</Body>
</ListItem>
))
) : (
<View style={styles.center}>
<Text style={{ alignSelf: 'center' }}>
No products match the selected criteria
</Text>
</View>
)}
</Content>
);
};
const styles = StyleSheet.create({
center: {
justifyContent: 'center',
alignItems: 'center',
height: 100
}
})
export default SearchedProduct;
Please can anyone help me to solve this error .If you want I will provide other details of my code.
Thanks in advance
Not sure but you are using SearchProduct name as a component and at the same time as a method. Maybe try to rename changing either component name or method name to see if it works.

React Native onPress doesn't work on IOS, but on Web it does

community!
I am building my first React-Native app and I have problem running this part of the code on IOS.
There is a function passed to onPress prop of a Button, that works on web but when I tried it on IOS nothing happends when pressing the button. The function should update the state of the parent component so it render's the next part of the Game form.
(There is a complete screen code sample at the bottom and you can also check the entire app in this git repository: https://github.com/dogaruemiliano/claim-app)
const handlePress = () => {
handleSubmit(maxPoints)
}
return (
<>
<Text style={styles.header}>Max points</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={maxPoints}
onChangeText={value => setMaxPoints(value)}
keyboardType="numeric"
/>
<KeyboardAvoidingView
behavior='padding'
>
<View>
<TouchableOpacity style={styles.btn}>
<Button
title="Next"
onPress={() => handlePress()} // here is the problem
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</>
)
The function called in the handlePress,
handleSubmit(maxPoints)
is received in the props of the component from the parent component that handles the logic up to the point where it send a POST request to the API.
Here is the complete code of the screen for New Game Creation.
import React, { useRef, useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { SafeAreaView, View, Text, StyleSheet, Button, Platform, KeyboardAvoidingView } from 'react-native'
import { TextInput, TouchableOpacity } from 'react-native-gesture-handler'
import Navbar from '../components/Navbar'
import COLORS from '../constants/Colors'
import { createGame } from '../actions'
const MaxPointsInput = (props) => {
const { handleSubmit } = props
const [maxPoints, setMaxPoints] = useState("")
const [done, setDone] = useState(false)
const inputRef = useRef(null)
const handlePress = () => {
setDone(true) // to test if function is executed
handleSubmit(maxPoints)
}
useEffect(() => {
inputRef.current.focus()
}, [])
return (
<>
<Text style={styles.header}>Max points</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={maxPoints}
onChangeText={value => setMaxPoints(value)}
keyboardType="numeric"
/>
<KeyboardAvoidingView
behavior='padding'
>
<View>
<TouchableOpacity style={styles.btn}>
<Button
title={done ? "Done..." : "Next"}
onPress={() => handlePress()}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</>
)
}
const PlayersInput = (props) => {
const { handleSubmit, currentUser } = props
const [players, setPlayers] = useState([])
const [nameInputValue, setNameInputValue] = useState("")
const inputRef = useRef()
useEffect(() => {
inputRef.current.focus()
}, [])
const addPlayer = () => {
setNameInputValue("")
setPlayers([...players, nameInputValue])
inputRef.current.value = ""
inputRef.current.focus()
}
return (
<>
<Text style={styles.header}>Players</Text>
<View style={{ flex: 1 }}>
<Text>Player #1: {currentUser.attributes.name} (you)</Text>
{players.map((player, index) => {
return(
<Text key={player}>Player #{index + 2}: {player}</Text>
)
})}
<Text>Player #{players.length + 2}</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={nameInputValue}
onChangeText={value => setNameInputValue(value)}
keyboardType="numeric"
/>
</View>
<KeyboardAvoidingView
behavior='padding'
style={{ marginBottom: 200, flex: 1 }}
>
<TouchableOpacity style={styles.btn}>
<Button
title="Add"
onPress={addPlayer}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
<TouchableOpacity style={styles.btn}>
<Button
title="Done"
onPress={() => handleSubmit(players)}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</KeyboardAvoidingView>
</>
)
}
const Confirmation = (props) => {
const { handleSubmit, maxPoints, players, currentUser } = props
return (
<>
<Text style={styles.header}>Details</Text>
<View style={{ flex: 1 }}>
<Text>Max points: {maxPoints}</Text>
<Text>Players: </Text>
<Text>1. {currentUser.attributes.name}</Text>
{players.map((player, index) => {
return (
<Text key={player}>{index + 2}. {player}</Text>
)
})}
</View>
<KeyboardAvoidingView
behavior='padding'
>
<View style={styles.btn}>
<Button
title="Confirm"
onPress={() => handleSubmit({maxPoints, players}, currentUser)}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</View>
</KeyboardAvoidingView>
</>
)
}
function NewGameScreen(props) {
const { navigation, currentUser, createGame } = props
const [maxPoints, setMaxPoints] = useState("")
const [players, setPlayers] = useState([])
const [showMaxPointsInput, setShowMaxPointsInput] = useState(true)
const [showPlayersInput, setShowPlayersInput] = useState(false)
const [showConfirmation, setShowConfirmation] = useState(false)
const handleMaxPointsSubmit = (value) => {
setMaxPoints(value)
setShowMaxPointsInput(false)
setShowPlayersInput(true)
}
const handlePlayersSubmit = (value) => {
setPlayers(value)
setShowPlayersInput(false)
setShowConfirmation(true)
}
const handleConfirmaton = (data, currentUser) => {
createGame({data, currentUser, navigation})
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.inner}>
{ showMaxPointsInput &&
<MaxPointsInput handleSubmit={handleMaxPointsSubmit}/>
}
{ showPlayersInput &&
<PlayersInput handleSubmit={handlePlayersSubmit} currentUser={currentUser} />
}
{
showConfirmation &&
<Confirmation maxPoints={maxPoints} currentUser={currentUser} players={players} handleSubmit={handleConfirmaton} />
}
</View>
<Navbar navigation={navigation} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 'auto',
marginTop: 48
},
inner: {
padding: 24,
paddingBottom: 80,
justifyContent: "center",
width: (Platform.OS === 'web' ? 300 : null),
marginTop: 48,
},
btn: {
borderRadius: 6,
marginTop: 12,
marginBottom: 2,
backgroundColor: COLORS.black,
},
btnOutline: {
borderRadius: 6,
marginTop: 12,
backgroundColor: COLORS.transparent,
borderWidth: 2,
borderColor: COLORS.black,
color: COLORS.black,
},
header: {
fontSize: 32,
textAlign: 'center',
marginBottom: 32,
},
scrollView: {
paddingHorizontal: 20,
},
input: {
textAlign: 'center',
height: 50,
fontSize: 32,
marginBottom: 20,
borderBottomWidth: 2,
borderColor: '#dbdbdb',
paddingBottom: 5,
},
})
const mapStateToProps = (state) => {
return {
currentUser: state.currentUser
}
}
const mapDispatchToProps = (dispatch) => {
return {
createGame: (props) => dispatch(createGame(props))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewGameScreen)
Thank you for reading!
From your code block, it seems that you are wrapping a button inside a TouchableOpacity Component which is imported from react-native-gesture-handler.
You can refactor code into, like removing unwanted wrapping with Touchable Elements if not required.
<View style={styles.btn}>
<Button
title={done ? "Done..." : "Next"}
onPress={handlePress}
color={Platform.OS === 'ios' ? 'white' : 'black'} />
</View>
Import TouchableOpacity from react-native
import { Text, View, StyleSheet ,TextInput, TouchableOpacity} from 'react-native';
Happy Coding!

Why can not I enter texts continually when using TexInput component with a props

I am trying to make a TextInput component as a child with a function which is from the parent component.
but then, I can't enter texts as usual(I have to set the cursor every after I enter 1 text )
would you mind tell me how to resolve this problem?
thank you in advance.
the parent component
import React, { useState, useEffect } from "react";
import { View, StyleSheet } from "react-native";
import TestListInputItem from "../components/TestListInputItem";
export default function HomeScreen() {
const [name, setName] = useState("");
const setInputName = (text) => {
setName(text);
};
return (
<View style={styles.container}>
<TestListInputItem name={name} setInputName={setInputName} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
},
});
the child component
import React, { useState } from "react";
import {
View,
StyleSheet,
Text,
TouchableOpacity,
TextInput,
} from "react-native";
export default function TestListInputItem(props) {
const [count, setCount] = useState(1);
function handleMakeNew() {
setCount((v) => v + 1);
}
function RenderList() {
return (
<View style={styles.list}>
<TextInput
style={styles.InputName}
value={String(props.name)}
onChangeText={(text) => {
props.setInputName(text);
}}
></TextInput>
<TouchableOpacity
onPress={() => {
handleMakeNew();
}}
style={styles.buttonAdd}
>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
}
return [...Array(count).keys()].map((i) => <RenderList key={i} />);
}
const styles = StyleSheet.create({
list: {
width: "100%",
backgroundColor: "#ddd",
padding: 10,
},
InputName: {
borderWidth: 1,
height: 40,
backgroundColor: "#fff",
},
buttonAdd: {
backgroundColor: "orange",
width: 80,
height: 40,
margin: 3,
justifyContent: "center",
alignItems: "center",
alignSelf: "center",
},
});
Node: 12.18.3
React-Native: 4.10.1
Expo: 3.22.3
As I commented, the issues is that your function is creating a new array on every render resetting your state, here's a quick modification that should set you in the right direction
export default function AddItem(props) {
return (
<View style={styles.list}>
<TextInput
style={styles.InputName}
value={props.value}
onChangeText={(text) => {
props.setValue(text);
}}
></TextInput>
</View>
);
}
export default function TestListInputItem(props) {
const [list, setList] = useState([]);
const [value, setValue] = useState("");
function handleMakeNew() {
setList((v) => [...v, value]);
}
return (
<View>
{
list.map((item, i) => <View key={i}><Text>{item}</Text></View>)
}
<AddItem
value={value}
setValue={setValue}
/>
<TouchableOpacity
onPress={() => {
handleMakeNew();
}}
style={styles.buttonAdd}
>
<Text>Add</Text>
</TouchableOpacity>
</View>
)
}
This is known as cursor jumping. To avoid this, in your Input component, maintain a local state and use that state value as input value. when onChange is fired, update the local state value first and then send it to the parent component
Try this in the child component,
import React, { useState } from "react";
import {
View,
StyleSheet,
Text,
TouchableOpacity,
TextInput,
} from "react-native";
export default function TestListInputItem(props) {
const [count, setCount] = useState(1);
const [inputValue, setInputValue] = useState('');
function handleMakeNew() {
setCount((v) => v + 1);
}
const handleInput =(text) =>{
setInputValue(text)
props.setInputName(text)
}
function RenderList() {
return (
<View style={styles.list}>
<TextInput
style={styles.InputName}
value={inputValue}
onChangeText={(text) => {
handleInput(text);
}}
></TextInput>
<TouchableOpacity
onPress={() => {
handleMakeNew();
}}
style={styles.buttonAdd}
>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
}

I keep getting this error. TypeError: addItems is not a function. (In 'addItems(text)', 'addItems' is an instance of Object)

This is the error I get every time I try and add something to my list.
TypeError: addItems is not a function. (In 'addItems(text)', 'addItems' is an instance of Object)
I cannot figure out what I am doing wrong. Im new to react-native so any help would be greatly appreciated.
Here is my App.js
import React, { useState } from 'react';
import { View, Text, StyleSheet, ImageBackground, FlatList } from 'react-native';
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import { uuid } from 'uuidv4';
import Header from './Componets/Header';
import AddItem from './Componets/AddItem';
import background from './Images/sunrise-in-the-smoky-mountains.jpg';
import ListItem from './Componets/ListItem';
const App = () => {
const [item, setItems] = useState([
// {
// id: uuidv4(),
// name: ''
// },
{
id: uuidv4(),
name: "gym"
},
{
id: uuidv4(),
name: "study"
}
]);
const addItems = (text) => {
setItems(prevItems => {
return [{ id: uuidv4(), text }, ...prevItems]
})
}
const deleteItem = (id) => {
setItems(prevVal => {
return prevVal.filter(item => item.id != id)
})
}
return (
<View style={styles.container}>
<ImageBackground source={background} style={styles.image}>
<Header
title="Gotta get this done!" />
<AddItem
addItem={addItems}
/>
<FlatList
data={item}
renderItem={({ item }) => (
<ListItem item={item.name} deleteItem={deleteItem} />
)}
/>
</ImageBackground>
</View>
)
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
flex: 1,
resizeMode: "cover",
justifyContent: "center"
}
})
export default App;
Here is where I call the function and get the error.
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Button } from 'react-native';
const AddItem = ( addItems ) => {
const [text, setText] = useState("");
const onChange = (inputVal) => setText(inputVal);
return (
<View style={styles.addItemView}>
<TextInput
style={styles.inputText}
placeholder="Add item to list..."
onChangeText={onChange}
/>
<TouchableOpacity style={styles.button}>
<Button title="Add Item" onPress={() => addItems(text)} />
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
addItemView: {
flex: 1,
flexDirection: "row",
alignItems: 'center'
},
inputText: {
fontSize: 20,
backgroundColor: "white",
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
width: 250,
},
button: {
alignItems: "center",
justifyContent: "center",
height: 40,
backgroundColor: "#bbc7ad",
borderRadius: 10,
borderWidth: 2,
borderColor: "#99a191",
marginLeft: 20,
}
})
export default AddItem;
You are passing addItems function as a prop in
<AddItem
addItem={addItems}
/>
When you pass any function or value to any functional component in React you can access them only via props so to access addItems you need to access this function from the props
You can do that by following ways
Method 1
const AddItem = ( props ) => {
// We are extracting addItem function from props
const {addItem} = props
const [text, setText] = useState("");
const onChange = (inputVal) => setText(inputVal);
return (
<View style={styles.addItemView}>
<TextInput
style={styles.inputText}
placeholder="Add item to list..."
onChangeText={onChange}
/>
<TouchableOpacity style={styles.button}>
<Button title="Add Item" onPress={() => addItem(text)} />
</TouchableOpacity>
</View>
);
};
Method 2 :
const AddItem = ({addItem} ) => {
.....
return (
<View style={styles.addItemView}>
......
<TouchableOpacity style={styles.button}>
<Button title="Add Item" onPress={() => addItem(text)} />
</TouchableOpacity>
</View>
);
};
This seems to work for now. Still unclear as to why using destructuring in my original version didnt work. Please chime in if you can help with that. Thanks!
const AddItem = (props) => {
const [text, setText] = useState("");
const onChange = inputVal => setText(inputVal);
return (
<View style={styles.addItemView}>
<TextInput
style={styles.inputText}
placeholder="Add item to list..."
onChangeText={onChange}
/>
<TouchableOpacity style={styles.button}>
<Button title="Add Item" onPress={() => props.addItem(text)} />
</TouchableOpacity>
</View>
);
};

React native how to animate view opening up->down

Consider the following component where the user selects an option from a list:
import React, { Component } from "react";
import PropTypes from "prop-types";
import {
View,
Text,
StyleSheet,
Platform,
FlatList,
TouchableNativeFeedback,
TouchableOpacity,
PLatform
} from "react-native";
import Icon from "react-native-vector-icons/Ionicons";
import { colors, metrics } from "../../themes";
const Touchable =
Platform.OS === "android" ? TouchableNativeFeedback : TouchableOpacity;
class MenuSelector extends Component<{
onSelect: () => any,
config: object,
selected: string
}> {
state = {
listOpened: false
};
handlePress = id => {
this.props.onPress(id);
};
handleSelect = id => {
if (this.props.onSelect) this.props.onSelect(id);
this.setState({
listOpened: false
});
};
handleMenu = () => {
this.setState({
listOpened: !this.state.listOpened
});
};
render = () => {
let title = "";
if (this.props.config) {
title = this.props.config[0].title;
if (this.props.selected) {
let found = this.props.config.find(item => {
return item.id === this.props.selected;
});
if (found) title = found.title;
}
}
let top = (
<View style={styles.header}>
<Text style={styles.text}>{title}</Text>
<Touchable>
<Text style={styles.text}>
<Icon
name={"ios-menu"}
size={20}
onPress={this.handleMenu}
/>
</Text>
</Touchable>
</View>
);
let list = null;
if (this.state.listOpened === true) {
list = (
<FlatList
data={this.props.config}
renderItem={({ item }) => (
<Touchable onPress={this.handleSelect}>
<Text style={[styles.text, styles.listItem]}>{item.title}</Text>
</Touchable>
)}
/>
);
}
return (
<View style={styles.container}>
{top}
{list}
</View>
);
};
}
export default MenuSelector;
const styles = StyleSheet.create({
container: {
flex: -1,
flexDirection: "column"
},
header: {
flex: -1,
flexDirection: "row",
justifyContent: "space-between",
padding: 10,
backgroundColor: "blue"
},
text: {
fontSize: 16,
color: "white",
textAlign: "center",
fontWeight: "bold"
},
listItem: {
padding: 10,
backgroundColor: "blue"
}
});
The component is used in the following context:
let config = [
{
title="Option 1",
id="option1"
},
{
title="Option 2",
id="option2"
},
{
title="Option 3",
id="option3"
},
{
title="Option 4",
id="option4"
},
];
return (
<View style={styles.container}>
<MenuSelector
config={config.options}
selected={this.state.mode}
onPress={this.handleButtonListPress}
/>
<FlatList
data={this.props.data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<Text style={styles.text}>{item.name}</Text>
)}
/>
</View>
);
As it is, the <MenuSelector> component appears "at once" on screen. I need to add an sliding effect to <MenuSelector>, "pushing down" the data FlatList when appearing on screen...
On closing, same behaviour, but animating from down to up.
How can I add such animation behaviour to my MenuSelector component ?

Resources