Want to show data onMouseDown event in my REACT app - reactjs

I have a console.log showing the correct information onMouseDown event in my REACT app, how do I change it so it shows the data in a pop up window?
I am happy to use Modal or hover over event, whatever it takes to display the data. Data Consists of:
on item click
{id: 3, group: 1, title: "resiting", bgColor: "#ff9800", start_time: Moment, …}
bgColor: "#ff9800"
end_time: Moment {_isAMomentObject: true, _i: "2018-10-11T15:08:00", _f: "YYYY-MM-DDTHH:mm:ss", _isUTC: false, _pf: {…}, …}
group: 1
id: 3
start_time: Moment {_isAMomentObject: true, _i: "2018-10-11T10:29:00", _f: "YYYY-MM-DDTHH:mm:ss", _isUTC: false, _pf: {…}, …}
title: "resiting"
__proto__: Object
import React, { Component } from "react";
import moment from "moment";
import Timeline from "react-calendar-timeline";
import generateFakeData from "../../generate-fake-data";
export default class App extends Component {
constructor(props) {
super(props);
const { groups, items } = generateFakeData();
const defaultTimeStart = moment()
.startOf("day")
.toDate();
const defaultTimeEnd = moment()
.startOf("day")
.add(1, "day")
.toDate();
this.state = {
groups,
items,
defaultTimeStart,
defaultTimeEnd
};
}
itemRenderer = ({
item,
timelineContext,
itemContext,
getItemProps,
getResizeProps
}) => {
const { left: leftResizeProps, right: rightResizeProps } = getResizeProps();
const backgroundColor = itemContext.selected
? itemContext.dragging
? "red"
: item.selectedBgColor
: item.bgColor;
return (
<div
{...getItemProps({
style: {
backgroundColor,
color: "#ffffff",
borderColor: "none",
borderStyle: "none",
borderWidth: 0,
borderRadius: 0,
borderLeftWidth: itemContext.selected ? 1 : 1,
borderRightWidth: itemContext.selected ? 1 : 1
},
onMouseDown: () => {
console.log("on item click", item);
}
})}
>
{itemContext.useResizeHandle ? <div {...leftResizeProps} /> : null}
<div
style={{
height: itemContext.dimensions.height,
overflow: "hidden",
paddingLeft: 3,
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}}
>
{itemContext.title}
</div>
{itemContext.useResizeHandle ? <div {...rightResizeProps} /> : null}
</div>
);
};
render() {
const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state;
return (
<Timeline
groups={groups}
items={items}
sidebarContent={<div>Vehicle</div>}
sidebarWidth={60}
lineHeight={20}
headerLabelGroupHeight={20}
fullUpdate={false}
itemsSorted
itemTouchSendsClick={false}
stackItems
itemHeightRatio={0.98}
showCursorLine
canMove={true}
canResize={true}
defaultTimeStart={defaultTimeStart}
defaultTimeEnd={defaultTimeEnd}
itemRenderer={this.itemRenderer}
/>
);
}
}
You can find it here on https://still-river-97856.herokuapp.com/tracking

Try something like this, you can use a ant design modal or a react-modal for the Modal(data={item}). But the logic is that once you click ok/away on the modal the clickedItem is set to null so that the modal is not displayed anymore.
Take a look at this code,
onMouseDown: () => {
console.log("on item click", item);
this.setState({clickedItem: item});
}
onModalOkClick = () => {
this.setState({clickedItem: null})
}
render() {
const { groups, items, defaultTimeStart, defaultTimeEnd, clickedItem } = this.state;
return (
<div>
<Timeline
groups={groups}
items={items}
sidebarContent={<div>Vehicle</div>}
sidebarWidth={60}
lineHeight={20}
headerLabelGroupHeight={20}
fullUpdate={false}
itemsSorted
itemTouchSendsClick={false}
stackItems
itemHeightRatio={0.98}
showCursorLine
canMove={true}
canResize={true}
defaultTimeStart={defaultTimeStart}
defaultTimeEnd={defaultTimeEnd}
itemRenderer={this.itemRenderer}
/>
{clickedItem ? <Modal data={clickedItem}> : null}
</div>
);
}
Let me know if you need help with the modal or you are still unsure how to proceed.
The dashboard looks nice btw. :)

Related

how to set checked item true or false in multi checkbox react native

I have a small request
I want to know how to set checked item true or false in multi checkbox by Sub Terms react native
In the following code, when I click on the checkbox, I want to change the relevant item in the array in function onchecked() and update the component.
import React, { useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
CheckBox,
TouchableOpacity,
} from "react-native";
const terms = [
{
term_id: 21,
name: "Clothing",
checked: false,
children: [
{
term_id: 24,
name: "Accessories",
checked: false,
children: [
{
term_id: 25,
name: "Scarf",
checked: false,
children: [],
},
{
term_id: 22,
name: "Tshirts",
checked: false,
children: [],
},
],
},
],
},
];
export default function Categoris() {
const [unSelectedterms, setSelectionTerms] = useState(terms);
const onchecked = (id) => {
console.log(id);
setSelectionTerms(unSelectedterms);
};
const recursive = (data, level = 0) => {
return data.map((item, key) =>
item.children?.length ? (
<>
{renderItem(level, item.name, item.term_id, item.checked, key)}
{recursive(item.children, level + 1)}
</>
) : (
renderItem(level, item.name, item.term_id, item.checked, key)
)
);
};
const renderItem = (level, name, id, checked, key) => (
<TouchableOpacity
style={{ flexDirection: "row", alignItems: "center" }}
key={key}
onPress={() => {
onchecked(id);
}}
>
<CheckBox
value={checked}
onValueChange={() => {
onchecked(id);
}}
/>
<Text style={{ fontWeight: "bold" }}>
{name}
{id} {level > 0 && "- ".repeat(level)}
</Text>
</TouchableOpacity>
);
return <View style={styles.container}>{recursive(unSelectedterms)}</View>;
}
const styles = StyleSheet.create({
item: {
fontSize: 20,
},
container: {
backgroundColor: "#fff",
padding: 50,
},
});
In the following code, when I click on the checkbox, I want to change the relevant item in the array in function onchecked() and update the component.
One option would be to write some code to traverse the terms tree and update the correct checked value, but I might suggest another approach. Instead store the checked state separately from the terms tree to simplify state updates.
const [checkedStates, setCheckedStates] = useState({});
const onChecked(id) => {
// this will toggle based on the current value of the checkbox
setCheckedStates(current => ({
...current,
[id]: !current[id],
});
}
...
// and then in the recursive call
{renderItem(level, item.name, item.term_id, !!checkedStates[item.id], key)}
This approach should avoid a more complicated tree traversal and update at the cost of an extra piece of state. If there isn't a need to update other properties in terms that's the tradeoff I would choose, but if other pieces of terms can be updated then maybe the tree traversal is a better option.

React Native setState not causing rendering

I'm a complete beginner at react native and now I'm stuck with an update problem. I'm using react-native-paper and typescript.
In my app, I want to press a button and then the text field should change its text.
The problem is somehow at the button, or the called function because in the console log its always "before: true after:true" or "before: false after:false",
but what I expected is "before: true after: false" or vice-versa
I've also got a second Text View which is not shown at all.
Maybe someone can tell me what I am doing wrong?
My index.js
import * as React from 'react';
import { AppRegistry } from 'react-native';
import { Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
export default function Main() {
return (
<PaperProvider>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent('main', () => Main);
My MyNavigation.tsx (which contains currently my whole app).
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
ViewRoute = () =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {this.state.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
<Button icon="camera" mode="contained" onPress={this.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
SettingsRoute = () => <Text>Settings</Text>;
HelpRoute = () => <View></View>
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}
State Updates May Be Asynchronous React Documentation
So You cannot test your console.log in this way. Use the callback function of setState method as follows,
this.setState({ connected: !this.state.connected }, () => {
console.log("after: " + this.state.connected);
console.log("--------------");
});
Hope this will help you.
Your issue is here,
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
setState is async function and it takes some time to update the state. It does not block execution of next statements. So you will always get the previous state only for both the console.log.
To get the actual updated value, you should use callback in setState.
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected }, () => console.log("after: " + this.state.connected); ); //Now you will get updated value.
console.log("--------------");
};
For this,
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
this.state.connected is either true or false (Boolean) which will never be shown on screen. If you still want to see the value on screen, then you can use this hack.
<Text>
{this.state.connected.toString()}
</Text>
Update
From the docs,
Pages are lazily rendered, which means that a page will be rendered the first time you navigate to it. After initial render, all the pages stay rendered to preserve their state.
Instead of this,
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
You should use this version of renderScene,
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
Your complete code should look like this,
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
const ViewRoute = (props) =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {props.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{props.connected.toString()}
</Text>
<Button icon="camera" mode="contained" onPress={props.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
const SettingsRoute = () => <Text>Settings</Text>;
const HelpRoute = () => <View></View>
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function() {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}

How to update MapboxGL.ShapeSource dynamically?

Using react-native-mapbox-gl/maps, when a SymbolLayer is dynamically added to a ShapeSource, it seems it is not shown, or the ShapeSource is not updated.
Here is the example to reproduce : based on CustomIcon example, I replaced the code with the code below. To reproduce, just execute the examples, copy-paste the code in place of the existing code in CustomIcon.js example.
import React from 'react';
import { View, Text } from 'react-native';
import MapboxGL from '#react-native-mapbox-gl/maps';
import sheet from '../styles/sheet';
import BaseExamplePropTypes from './common/BaseExamplePropTypes';
import Page from './common/Page';
import Bubble from './common/Bubble';
const styles = {
icon: {
iconAllowOverlap: true,
},
view: {
width: 60,
height: 60,
borderColor: 'black',
borderWidth: 1,
alignItems: 'center',
justifyContent: 'center'
},
text: {
fontSize: 50
}
};
const customIcons = ['😀', '🤣', '😋', '😢', '😬']
class CustomIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
featureCollection: {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
coordinates: [-73.970895, 40.723279],
type: 'Point'
},
id: 1,
properties: {
customIcon: customIcons[0]
}
}]
},
};
this.onPress = this.onPress.bind(this);
this.onSourceLayerPress = this.onSourceLayerPress.bind(this);
}
onPress(e) {
const feature = {
type: 'Feature',
geometry: e.geometry,
id: Date.now(),
properties: {
customIcon: customIcons[this.state.featureCollection.features.length]
}
};
this.setState(({ featureCollection }) => ({
featureCollection: {
type: 'FeatureCollection',
features: [
...featureCollection.features,
feature
]
}
}));
}
onSourceLayerPress(e) {
const feature = e.nativeEvent.payload;
console.log('You pressed a layer here is your feature', feature); // eslint-disable-line
}
render() {
return (
<Page {...this.props}>
<MapboxGL.MapView
ref={c => (this._map = c)}
onPress={this.onPress}
style={sheet.matchParent}
>
<MapboxGL.Camera
zoomLevel={9}
centerCoordinate={[-73.970895, 40.723279]}
/>
<MapboxGL.ShapeSource
id="symbolLocationSource"
hitbox={{width: 20, height: 20}}
onPress={this.onSourceLayerPress}
shape={this.state.featureCollection}
>
{this.state.featureCollection.features.map((feature, ind) => (
<MapboxGL.SymbolLayer
id={"symbolLocationSymbols" + feature.id}
key={feature.id}
filter={['==', 'customIcon', customIcons[ind]]}
minZoomLevel={1}
style={styles.icon}
>
<View style={styles.view}>
<Text style={styles.text}>
{feature.properties.customIcon}
</Text>
</View>
</MapboxGL.SymbolLayer>
))}
</MapboxGL.ShapeSource>
</MapboxGL.MapView>
<Bubble>
<Text>Tap to add an icon</Text>
</Bubble>
</Page>
);
}
}
export default CustomIcon;
We can see that clicking on the map changes the state, adds a feature, but does not show the feature on the map.
How can we make the ShapeSource update dynamically ?
The whole discussion about the subject is in here: https://github.com/react-native-mapbox-gl/maps/issues/248
To make it short : I wanted to use dynamics SVGs as SymbolLayer (so that I can change the colour for instance), but this is not possible : giving SymbolLayer any child component is not a proper way to do.
We need instead to use Images in parallel of ShapeSource and SymbolLayer, because Images can be updated dynamically.
Here is a code example :
import React from 'react';
import MapboxGL from '#react-native-mapbox-gl/maps';
const myImages = {
'image-1': 'path/to/image-1',
'image-2': 'path/to/image-2'
}
const createFeature = ({
showPin,
icon = 'image-1', // as long as any added feature has an icon belonging to the static myImages, it works.
coordinates,
id
}) => ({
// https://github.com/react-native-mapbox-gl/maps/blob/master/docs/ShapeSource.md -> shapeSource prop
// https://geojson.org
// this has a geoJSON shape
type: 'Feature',
id,
properties: {
showPin,
icon
},
geometry: {
type: 'Point',
coordinates,
}
})
class MyMarkers extends React.Component {
state = {
featureCollection: MapboxGL.geoUtils.makeFeatureCollection(),
}
componentDidMount() {
this.updateFeatures()
}
componentDidUpdate(prevProps) {
// update features based on any criteria
if (conditionOnProps(prevProps, this.props)) this.updateFeatures()
}
updateFeatures() {
const featureCollection = MapboxGL.geoUtils.makeFeatureCollection()
for (let feature of this.props.features) {
MapboxGL.geoUtils.addToFeatureCollection(
featureCollection,
createFeature(feature)
)
}
this.setState({ featureCollection });
}
onPress = (e) => {
const feature = e.nativeEvent.payload;
this.props.doAnythingWithPressedFeature(feature);
}
render() {
return (
<>
<MapboxGL.Images images={myImages} />
<MapboxGL.ShapeSource
id='markersShape'
shape={this.props.featureCollection}
onPress={this.onPress}
>
<MapboxGL.SymbolLayer
id='markersSymbol'
filter={['==', 'showPin', true]}
style={{
iconAllowOverlap: true,
iconImage: ['get', 'icon'],
}}
/>
</MapboxGL.ShapeSource>
</>
)
}
}
export default MyMarkers;

I have a card component that I need to update to add new items

I built a card component that shows a list of user data and images with antd on nextJs. I want to build a functionality that creates a modal to input new data and image and adds it to the user interface as a new card, but I am confused on how to get my hands around it. I need assistance. Here's a link to my code!
import React from 'react';
import { Avatar, Card, Icon, List } from 'antd';
import { ICON_LIST, LIST_TEXTS, STYLES, USER_UPLOAD } from './constants';
const { AVATAR, CARD_CONTAINER, ICON, USER_LIST } = STYLES;
const { INNER, MORE, UPLOAD, VERTICAL } = LIST_TEXTS
class Home extends React.Component {
state = {
clicks: 0,
};
IncrementIconText = () => {
this.setState({ clicks: this.state.clicks + 1 });
}
render() {
const actions = ( ICON_LIST.map(({ type }) => (
<span>
<Icon key={type} type={type} onClick={this.IncrementIconText} style={ICON} />
{this.state.clicks}
</span>
)));
return (
<List
itemLayout={VERTICAL}
dataSource={USER_UPLOAD}
renderItem={item => (
<List.Item style={USER_LIST}>
<Card
actions={actions}
cover={<img alt={UPLOAD} src={item.image} />}
extra={<Icon type={MORE} />}
hoverable
title={<a><Avatar src={item.image} style={AVATAR} />{item.user}</a>}
type={INNER}
style={CARD_CONTAINER}
>
{item.story}
</Card>
</List.Item>
)}
/>
);
}
}
export default Home;
constants.js
export const ICON_LIST = [
{
key: "heart",
type: "heart",
},
{
key: "dislike",
type: "dislike",
},
{
key: "meh",
type: "meh",
},
]
export const LIST_TEXTS = {
INNER: "inner",
MORE: "more",
UPLOAD: "upload",
VERTICAL: "vertical",
};
export const STYLES = {
AVATAR: {
marginRight: 8
},
CARD_CONTAINER: {
width: "650px",
marginBottom: 50
},
ICON: {
marginRight: 8
},
USER_LIST: {
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center"
},
};
export const USER_UPLOAD = [
{
image: "http://sugarweddings.com/files/styles/width-640/public/1.%20The%20Full%20Ankara%20Ball%20Wedding%20Gown%20#therealrhonkefella.PNG",
story: "Today's my birthday next week! What do you think?",
user: "Chioma",
},
{
image: "https://dailymedia.com.ng/wp-content/uploads/2018/10/7915550_img20181007141132_jpeg01c125e1588ffeee95a6f121c35cd378-1.jpg",
story: "Going for an event. Do you like my outfit",
user: "Simpcy",
},
{
image: "https://i0.wp.com/www.od9jastyles.com/wp-content/uploads/2018/01/ankara-styles-ankara-styles-gown-ankara-tops-ankara-gowns-ankara-styles-pictures-latest-ankara-style-2018-latest-ankara-styles-ankara-ankara-styles.png?fit=437%2C544&ssl=1",
story: "Saturdays are for weddings. Yay or nay!",
user: "Angela",
},
]
So this could get you started:
https://codesandbox.io/s/1r7j6lom34?fontsize=14
I moved your static USER_UPLOAD into the state of Home and wrote a method to add a new upload to that state.
You would now need to come up with a component that shows your modal and calls AddUpload with the right values.
Also your card-actions don't seem to function properly. To fix that i suggest creating a wrapper component for Card that has a state with the appropriate click counters. That way every card has its own clickcounters.

react native multiple video with Swiper component play pause issue

I have multiple videos in the swiper to show videos one by one, but all the videos are loaded and playing at the same time and audios are messed up, I want current video only play at a time.
import * as React from 'react';
import { Text, View, StyleSheet,Image, Dimensions } from 'react-native';
import { Constants } from 'expo';
import { Video } from 'expo';
import Swiper from './Swiper';
import InViewPort from './InViewport';
const screenWidth = Dimensions.get('window').width ;
const screenHeight = Dimensions.get('window').height;
export default class App extends React.Component {
constructor(props) {
super(props);
// Your source data
this.state = {
images: {},
muted : false,
paused: true,
};
this.player = Array();
this.onChangeImage = this.onChangeImage.bind(this);
}
videoError(err){
console.warn(err);
}
pauseVideo = () => {
var curr = this.state.currentIndex;
console.warn(curr);
if(this.player[curr]) {
this.setState({paused: true });
}
}
playVideo = () => {
var curr = this.state.currentIndex;
console.warn(curr);
if(this.player[curr]) {
this.setState({paused: false});
}
}
handlePlaying = (isVisible) => {
isVisible ? this.playVideo() : this.pauseVideo();
}
onChangeImage (index) {
this.setState({ currentIndex: index});
}
render() {
let items = Array.apply(null, Array(15)).map((v, i) => {
return {
id: i,
caption: i + 1,
source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
dimension: '{ width: 150, height: 150 }',
};
});
return(
<View style={styles.DefaultView}>
<Swiper
showsPagination={false}
onIndexChanged={this.onChangeImage}
index={0}
>
{items.map((item, key) => {
if(key==1 || key ==5){
return (
<InViewPort onChange={this.handlePlaying} key={key}>
<Video onError={this.videoError}
muted={this.state.muted}
paused={this.state.paused}
source={{uri: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4' }}
style={styles.backgroundVideo}
ref={(ref) => {
this.player[key] = ref;
}}
controls={true}
/>
</InViewPort>
)
}else{
return(
<Image
resizeMode='contain'
style={{width:screenWidth, height: screenHeight}}
source={item.source}
key={key}
/>
)
}
})}
</Swiper>
</View>
)
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
flexDirection: 'row',
},
DefaultView: {
flex: 1,
backgroundColor: '#000',
width: screenWidth,
justifyContent:'center',
alignItems:'center'
},
iconContainer: {
flexDirection: "row",
justifyContent: "space-evenly",
width: 150,
},
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
width: screenWidth,
height: 300,
marginTop:'50%',
position:'absolute',
},
});
I need some idea on this, we have a player reference to be used, also swiper component have onIndexChanged which will trigger when we moved to next video, how we can link the reference of the player to onIndexChanged and when we do swipe how we make it current video only to play?
As per Andrew suggestion I have used InPortView component too determine the current view of swipe, but still I am not sure how to make reference for video elements to be used in the functions for play and pause the concern video.
Components used:
For video react-native-video
For Swiper : react-native-swiper
Updated Full code with Expo example : Expo Snack
So taking your snack. I managed to get it to work.
I moved the Video out into its own component and passed a few additional props to it, the index in the array and the currentIndex showing.
export default class App extends React.Component {
constructor(props) {
super(props);
// Your source data
this.state = {
images: {},
muted : false,
paused: true,
currentIndex: 0
};
}
onChangeImage = (index) => {
console.log('currentIndex ', index)
this.setState({ currentIndex: index});
}
render() {
let items = Array.apply(null, Array(15)).map((v, i) => {
return {
id: i,
caption: i + 1,
source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
dimension: '{ width: 150, height: 150 }',
};
});
return(
<View style={styles.DefaultView}>
<Swiper
showsPagination={false}
onIndexChanged={this.onChangeImage}
index={0}
>
{items.map((item, key) => {
if(key==1 || key ==5){
return (
<VideoPlayer key={key} index={key} currentIndex={this.state.currentIndex}/>
)
}else{
return(
<Image
resizeMode='contain'
style={{width:screenWidth, height: screenHeight}}
source={item.source}
key={key}
/>
)
}
})}
</Swiper>
</View>
)
}
}
The video component uses react-native-inviewport to help handle whether or not it is in the viewport. However it doesn't play nicely with react-native-swiper but it is possible to get it to work.
export default class VideoPlayer extends React.Component {
pauseVideo = () => {
if(this.video) {
this.video.pauseAsync();
}
}
playVideo = () => {
if(this.video) {
this.video.playAsync();
}
}
handlePlaying = (isVisible) => {
this.props.index === this.props.currentIndex ? this.playVideo() : this.pauseVideo();
}
render() {
return (
<View style={styles.container}>
<InViewPort onChange={this.handlePlaying}>
<Video
ref={ref => {this.video = ref}}
source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
style={{ width: WIDTH, height: 300 }}
/>
</InViewPort>
</View>
)
}
}
When I used the InViewPort component alone it seemed to think that the video in position 6 was in the viewport and would play it. So what I use the InviewPort is to perform a check to compare the index of the video with the currentIndex if they match play the video otherwise pause. I suppose this could be updated to use componentDidUpdate to handle the changes in the props. However, additional checks will need to be performed when the component mounts so that it doesn't play the video.
Here is my snack with it working. https://snack.expo.io/#andypandy/swiper-video

Resources