React Native - WebView & FlatList in a ScrollView to be scrollable - react-native

I'm making a view in react native but my component has a webview to display HTML, below the webview is a flatlist( list of items)
The parent component is supposed to be scrollable based on the webview & the flatlist.
I tried to put them together but it doesn't work as I want.
Therefore I would appreciate all of your advice & suggestions. Thank you
Updated:
I found out a solution here after the owner of the lib has been updated
https://github.com/iou90/react-native-autoheight-webview/issues/81

You can use WebView as a header component of FlatList as this:
<View style={styles.container}>
<FlatList
data={[
{ key: 'a' },
{ key: 'b' },
{ key: 'c' },
{ key: 'd' },
]}
renderItem={({ item }) => <Text>{item.key}</Text>}
ListHeaderComponent={
<View style={{ height: 200 }}>
<WebView
originWhitelist={['*']}
source={{ html: '<h1>Hello world</h1>' }}
/>
</View>
}
/>
</View>
But there is still a limitation, you have to specify the height of the view that wraps WebView as done above.
Hope, you got the idea ?

Maybe this will help.
import React, { Component } from 'react';
import { Text, View, StyleSheet, WebView, FlatList } from 'react-native';
export default class App extends Component {
onNavigationStateChange = navState => {
if (navState.url.indexOf('https://www.google.com') === 0) {
const regex = /#access_token=(.+)/;
let accessToken = navState.url.match(regex)[1];
console.log(accessToken);
}
};
render() {
const url = 'https://api.instagram.com/oauth/authorize/?client_id={CLIENT_ID}e&redirect_uri=https://www.google.com&response_type=token';
return (
<View style={{flex: 1}}>
<WebView
source={{
uri: url,
}}
scalesPageToFit
javaScriptEnabled
style={{ flex: 1 }}
/>
<FlatList data={[1, 2, 3]} renderItem={(item) => <Text>item</Text>}/>
</View>
);
}
}

Related

Warning: Each child in a list should have a unique key'' prop react native FlatList in Expo

I have an error alert "Warning: Each child in a list should have a unique key'' prop react native FlatList" on Expo Go for iOS but there is no error showing in terminal.
My attempts to solve this error:-
I did wrap FlatList inside SafeAreaView but new error came out "VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead."
I removed key={}, then error to ask for key for each list, instead I already added keyExtractor={}, when I reinstated to add both key={} and keyExtractor={}, the error is gone.
Then, there's only 1 error alert in Expo Go for iOS but there's no error showing in terminal.
Can I anyone spot where I went wrong?
My All.js code:-
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, LogBox } from 'react-native';
import { NativeBaseProvider, FlatList, ScrollView, Divider, Image, Spinner, SafeAreaView } from 'native-base';
import { services } from '../services/Services';
import moment from 'moment';
function All(){
const [newsData, setNewsData] = useState([]);
useEffect(() => {
services('general')
.then(data => {
setNewsData(data)
})
.catch(error => {
alert(error)
})
}, []);
return(
<NativeBaseProvider>
{newsData.length > 1 ? (
<FlatList
data={newsData}
keyExtractor={(item) => {
return item.id
}}
renderItem={({ item }) => (
<View>
<View style={styles.newsContainer}>
<Image
width={550}
height={250}
resizeMode={"cover"}
source={{
uri: item.urlToImage,
}}
alt="Alternate Text"
/>
<Text style={styles.title}>
{item.title}
</Text>
<Text style={styles.date}>
{moment(item.publishedAt).format('LLL')}
</Text>
<Text style={styles.newsDescription}>
{item.description}
</Text>
</View>
<Divider my={2} bg="#e0e0e0" />
</View>
)}
/>
) : (
<View style={styles.spinner}>
<Spinner color="danger.400" />
</View>
)}
</NativeBaseProvider>
);
}
const styles = StyleSheet.create({
newsContainer: {
padding: 10
},
title: {
fontSize: 18,
marginTop: 10,
fontWeight: "600"
},
newsDescription: {
fontSize: 16,
marginTop: 10
},
date: {
fontSize: 14
},
spinner: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: 400
},
});
export default All;
Try using this:
keyExtractor={(item, index) => index.toString()}
You could also, in your renderItem method add on your parent View a Key prop with some unique key comming from your array.
That error is because the item.id you are using is not unique and is repeating on the childrens components.

React Native Flatlist numColumns is not making multiple columns

I have just started to learn React Native, and this is my first project - a news app. However, I have successfully rendered the image and description of the news using React Native Flatlist.
But when I am using numColumns for making two columns, the column number remains the same. But the number of the shown images becomes the half (i.e. from 18 to 9 in my case). And also the description of the image is going under the image of the next news like shown below -
      
My source code looks like below -
import React, { Component } from 'react'
import { View, Text, FlatList, TouchableHighlight, SectionList, TouchableOpacity , Image,StyleSheet } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler';
import { HomeNewsJSON } from "../../../../assects/JSON/Home"
class HomeNews extends Component {
constructor(props) {
super(props);
this.state = {
news: ""
};
}
componentDidMount() {
this.getInfo();
}
getInfo() {
var data = []
var jsondata = HomeNewsJSON["items"]
// alert(jsondata.length)
this.setState({
news: jsondata
})
}
renderImage(item) {
var a = item;
return (
<TouchableOpacity
style={{flex:1/3,
aspectRatio:1}}>
<Image style={{flex: 1}} resizeMode='cover' source={{ uri: (a.slice(a.indexOf("src") + 5, a.indexOf("</a>") - 3))}}></Image>
</TouchableOpacity>
)
}
renderPic(data) {
var a = data;
return (a.slice(a.indexOf("src") + 5, a.indexOf("</a>") - 3));
}
render() {
var result = Object.entries(this.state.news);
console.log(result)
return (
<View>
<FlatList
contentContainerStyle={{margin:2}}
style={{borderWidth: 0}}
horizontal={false}
numColumns={2}
keyExtractor={item => item.findIndex}
data={result}
renderItem={({ item }) => (
<View>
{this.renderImage(item[1]["description"])}
<Text>{item[1]["description"]}</Text>
</View>
)}
/>
</View>
)
}
}
export default HomeNews
Please, anyone, suggest me a way to fix it. I really appreciate any help you can provide.
try flex:1 with View outside of item
renderItem={({ item }) => (
<View
style={{
flex: 1,
flexDirection: 'column',
}}>
//image here
</View>
)
Let's try add flexDirection : 'row' in view, such as:
<View style={{flexDirection : 'row'}}>
{this.renderImage(item[1]["description"])}
<Text>{item[1]["description"]}</Text>
</View>

Better solution to open the Menu when 3 dots are clicked in React Native

I am able to open menu when 3 dots icon is clicked for each item. But can the code be written in a better way..
Right now menu is getting created for each card item but ideally it would have been good to create single Menu View and dynamically associate it to some card where ever the 3 dots is clicked.
Expo Source Code Link
Code
export default class App extends React.Component {
constructor(props, ctx) {
super(props, ctx);
this.state = {
list: [
{ name: "Michael", mobile: "9292929292", ref: React.createRef() },
{ name: "Mason Laon Roah", mobile: "1232313233", ref: React.createRef() },
{ name: "Constructor", mobile: "4949494949", ref: React.createRef() },
{ name: "Rosling", mobile: "4874124584", ref: React.createRef() }
],
};
}
_menu = null;
hideMenu = () => {
this._menu.hide();
};
showMenu = (ref) => {
this._menu = ref;
this._menu.show();
};
render() {
const renderItem = ({ item, index }) => (
<ListItem
title={
<View>
<Text style={{ fontWeight: "bold" }}>{item.name}</Text>
<Text>{item.mobile}</Text>
</View>
}
subtitle={
<View>
<Text>445 Mount Eden Road, Mount Eden, Auckland. </Text>
<Text>Contact No: 134695584</Text>
</View>
}
leftAvatar={{ title: 'MD' }}
rightContentContainerStyle={{ alignSelf: 'flex-start'}}
rightTitle={this.getMenuView(item.ref)}
/>
);
return (
<View style={styles.container}>
<View style={{ flex: 1, marginTop: 30 }}>
<FlatList
showsVerticalScrollIndicator={false}
keyExtractor={(item, index) => index.toString()}
data={this.state.list || null}
renderItem={renderItem}
ItemSeparatorComponent={() => (
<View style={{ marginBottom: 5 }} />
)}
/>
</View>
</View>
);
}
getMenuView(ref) {
return (
<Menu
ref={ref}
button={<Icon onPress={() => this.showMenu(ref.current)} type="material" color="red" name="more-vert" />}
>
<MenuItem onPress={this.hideMenu}>Menu item 1</MenuItem>
<MenuItem onPress={this.hideMenu}>Menu item 2</MenuItem>
<MenuItem onPress={this.hideMenu} disabled>
Menu item 3
</MenuItem>
<MenuDivider />
<MenuItem onPress={this.hideMenu}>Menu item 4</MenuItem>
</Menu>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Sample Output
As mentioned here, you can find an undocumented UIManager.java class that allows you to create Popups with its showPopupMenu method.
This currently works only for Android.
import React, { Component } from 'react'
import { View, UIManager, findNodeHandle, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/MaterialIcons'
const ICON_SIZE = 24
export default class PopupMenu extends Component {
constructor (props) {
super(props)
this.state = {
icon: null
}
}
onError () {
console.log('Popup Error')
}
onPress = () => {
if (this.state.icon) {
UIManager.showPopupMenu(
findNodeHandle(this.state.icon),
this.props.actions,
this.onError,
this.props.onPress
)
}
}
render () {
return (
<View>
<TouchableOpacity onPress={this.onPress}>
<Icon
name='more-vert'
size={ICON_SIZE}
color={'grey'}
ref={this.onRef} />
</TouchableOpacity>
</View>
)
}
onRef = icon => {
if (!this.state.icon) {
this.setState({icon})
}
}
}
Then use it as follows.
render () {
return (
<View>
<PopupMenu actions={['Edit', 'Remove']} onPress={this.onPopupEvent} />
</View>
)
}
onPopupEvent = (eventName, index) => {
if (eventName !== 'itemSelected') return
if (index === 0) this.onEdit()
else this.onRemove()
}
Source: https://cmichel.io/how-to-create-a-more-popup-menu-in-react-native
There is now a React Native plugin for this. I'm not sure it was around when the question was originally asked. But I'm leaving this here for anyone else looking for the answer.
https://www.npmjs.com/package/react-native-popup-menu
The example worked for me. I wanted to use the vertical ellipsis, so I did this modification to the MenuTrigger part of the example to an icon instead of text:
<MenuTrigger>
<Icon name="more-vert" size={25} color={colors.rustRed} />
</MenuTrigger>
As a side note, I had difficulty finding and using the ellipsis. I eventually went with using react-native-vector-icons by using 'npm -i react-native-vector-icons' and importing the Material Icons like this:
import Icon from 'react-native-vector-icons/MaterialIcons';
Use React Portals
https://reactjs.org/docs/portals.html
In short the receipts is:
You define your dynamic menu at sibling level only once in the parent i.e. in your case it would be adjacent to App.
Handle Click at each item level to open your component. You can pass some specific event days to achieve the dynamism.
Easier example https://codeburst.io/reacts-portals-in-3-minutes-9b2efb74e9a9
This achieves exactly what you are trying to do which is defer the creation of component untill clicked.

How to make FlatList fill the height?

import React from 'react';
import {SafeAreaView, KeyboardAvoidingView, FlatList, View, Text, TextInput, Button, StyleSheet } from 'react-native';
export default class Guest extends React.Component {
state={
command: '',
}
constructor(props) {
super(props)
this.onChangeText = this.onChangeText.bind(this)
this.onKeyPress = this.onKeyPress.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChangeText(text){
const command = text.replace('\n', '');
this.setState({
command: command
})
}
onKeyPress(e){
}
onSubmit(){
}
render() {
return(
<SafeAreaView style={styles.safeAreaView}>
<KeyboardAvoidingView style={styles.keyboardAvoidingView} keyboardVerticalOffset={88} behavior="padding" enabled>
<FlatList
inverted={true}
keyboardShouldPersistTaps='always'
keyboardDismissMode='interactive'
ref='list'
style={styles.flatList}
data={[1, 2, 3]}
renderItem={(props) => {
return(<View><Text>{props.item}</Text></View>)
}}
/>
<TextInput
command={this.state.command}
onChangeText={this.onChangeText}
onKeyPress={this.onKeyPress}
onSubmit={this.onSubmit}
style={styles.textInput}
/>
</KeyboardAvoidingView>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
safeAreaView:{
backgroundColor:"#ffffff",
},
keyboardAvoidingView:{
},
flatList:{
backgroundColor: 'red',
},
textInput:{
backgroundColor: 'yellow'
}
})
I'd like the red flatList to fill the screen (but keep height of yellow textbox).
I've tried flex:1 on flatList, but it simply makes it disappear.
FlatList inherits ScrollView's props, so solution for ScrollView will work:
<FlatList
contentContainerStyle={{ flexGrow: 1 }}
{...otherProps}
/>
Here is the original Github issue for above solution.
EDIT: The parental Views of FlatList should have flex: 1 in their style.
safeAreaView:{
backgroundColor:"#ffffff",
flex: 1
},
keyboardAvoidingView:{
flex: 1
},
use the property style wit flex:
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
keyExtractor = { this.keyExtractor }
data = { this.getPOs() }
ListEmptyComponent = { this.renderEmpty }
ItemSeparatorComponent = { Separator }
renderItem = { this.renderItem }
/>
</View>
)
}
No need to add a parental view to the list, simply:
render() {
return <FlatList style={{width: '100%', height: '100%'}}
{...others}
/>;
}
you can also add height in flatList style or put flatlist inside a view and then add flex for view
In my case the problem was with virtual keyboard. when I open another page. then the keyboard suddenly dismiss. and it cause part of the page to be like someone cut it or clean it. so the solution is to before push the page that contain flatlist first dismiss the keyboard and then navigate to new page
I try every response on this issue but none of them work.
What I do was add a Parent to the FlatList and then give it a style :
<View style={{ height: SCREEN_HEIGHT}}>
SCREEN_HEIGHT is from Dimensions.get('window')
you have to import from "react-native" like this:
import { Dimensions } from "react-native"
Full example:
<View style={{ height: SCREEN_HEIGHT}}>
<FlatList
contentContainerStyle={{ flexGrow: 1 }}
keyExtractor={item => item.name}
numColumns={1}
data={this.state.dataList}
renderItem={({ item, index }) =>
this._renderItemListValues(item, index)
}
/>
</View>

How to push TextInput in a FlatList in react native?

I want to learn to use FlatList in react native,but I can't figure how to push elements in data (the FlatList array). Can someone help me ?
Here's my react native code:
import React, { Component } from 'react';
import { FlatList, StyleSheet, Text, Button,View ,TextInput} from 'react-native';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {text: '',
data:[]
};
}
render() {
return (
<View>
<TextInput
style={{height: 40}}
placeholder="Task"
onChangeText={(text) => this.setState({text})}/>
<Button title="Add" onPress={this.addTask} />
<FlatList
renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22
},
item: {
padding: 10,
fontSize: 18,
height: 44,
}
});
You need to add data prop in the Flatlist Component.
<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
the renderItem is basically looping over elements in the data array. It cannot do that if there is no data. If you are starting with empty data just use data={[]}