Expo React Native, How to pick default local images or user image obtained from expo image picker - javascript

I have a form that a user can select a default local image or an image from the user's photo library
Here is an expo snack use android the images can be found in the phone menu in photos
I want to save either the default local image or user's image to the form and to redux, currently able to save default images picked to form and redux.
This is what currently works.
I have a component that gets a selected local image and returns an image path witch is a number. That local image gets saved in form and in redux. currently, the user can change the local image in the form.
ImgSelector Component:
import React, { useState } from "react";
import { List, Selector, View, SelectedImg } from "./styles";
import { FlatList } from "react-native";
import { defaultImages } from "../../data/defaultImages";
const FlatlistItem = ({ image, setImg }) => {
return (
<Selector onPress={() => setImg(image)}>
<View>
<SelectedImg source={image} />
</View>
</Selector>
);
};
const ImgSelector = ({ setImg }) => {
const [selectedId, setSelectedId] = useState(null);
const renderItem = ({ item }) => (
<FlatlistItem setImg={setImg} image={item.image} />
);
return (
<View>
<FlatList
horizontal
data={defaultImages}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
extraData={selectedId}
/>
</View>
);
};
export default ImgSelector;
Default local images are stored like this and the path is the index which is a number this part works fine.
export const defaultImages = [
{
id: “2”,
image: require("../assets/images/singlepane.png"),
}
]
I have an imagePicker component that asks for permissions and returns a uri string that looks like this:
file:/data/data/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FExpoWcPro-a828b17b-dcd7-4a04-93ca-657c8e4e511d/ImagePicker/6106d73f-c886-457d-abe9-1f1232a0d398.jpg
My form component where images are picked and saved:
import React, { useState } from "react";
import { Image } from "react-native";
const CounterForm = ({ navigation, ...props }) => {
// This is current state for default images that works
const [imgUrl, setImgUrl] = useState(props.imgUrl || defaultImage);
const [userImgUri, setUserImgUri] = useState(null);
// This gets the local image from a componnet
const handleEditImg = (newImgUrl) => {
setImgUrl(newImgUrl);
};
// This gets image uri from expo image picker
const handelUserImg = (userUri) => {
setUserImgUri(userUri);
};
// This sends data to a redux action to save
const handleSubmit = () => {
props.onFormSubmit({
id: props.id,
imgUrl,
});
setImgUrl(defaultImage);
};
return (
<FormWrapper>
<Row>
<FormButton onPress={() => handleSubmit()}>
<StyledText title="Save" color={COLORS.appBlue} />
</FormButton>
</Row>
<TopContent>
{/* I tried this to get user image and displays in form */}
<Image
source={{ uri: userImgUri }}
style={{ width: 100, height: 100 }}
/>
{/* This current implementation gets local images
<Image
source={imgUrl}
style={{ width: 100, height: 100 }}
/> */}
{/* I tried this only gets local images
{imgUrl ? (
<Image source={imgUrl} style={{ width: 100, height: 100 }} />
) : (
<Image
source={{ uri: userImgUri }}
style={{ width: 100, height: 100 }}
/>
)} */}
</TopContent>
<Row>
<ImagePicker getUserImg={handelUserImg} />
</Row>
<View>
<ImgSelector setImg={handleEditImg} />
</View>
</FormWrapper>
);
};
export default CounterForm;

if you use the last sdk version of Expo (40) and the right package expo-image-picker you need to follow this instructions.
First you need to ask for permissions :
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
And then call method to select image from library :
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
So you got image uri by accessing result.uri, you need to save this value (e.g in user store) and display it by selecting your store or default value if there is not stored value :
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
/** imgUrl = stored image uri, defaultImages[0].image = default image uri */
<Image source={imgUrl ? { uri: imgUrl } : defaultImages[0].image} />
</View>

I found the answer it's updated in expo snack
import React, { useState } from "react";
import { List, Selector, View, SelectedImg } from "./styles";
import { FlatList } from "react-native";
import { defaultImages } from "../data";
const FlatlistItem = ({ image, setImg }) => {
return (
<Selector onPress={() => setImg(image)}>
<View>
<SelectedImg source={{uri: image}} />
</View>
</Selector>
);
};
const ImgSelector = ({ setImg }) => {
const [selectedId, setSelectedId] = useState(null);
const renderItem = ({ item }) => {
return (
<FlatlistItem setImg={setImg} image={item} />
)
}
return (
<View>
<FlatList
horizontal
data={defaultImages}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
extraData={selectedId}
/>
</View>
);
};
export default ImgSelector;
Form
import React, { useState } from "react";
import {Asset} from 'expo-asset';
import StyledText from "../UiComponents/StyledText";
import { TouchableWithoutFeedback, Keyboard } from "react-native";
import {
FormWrapper,
TextInputWrapper,
TopContent,
NumberWrapper,
Row,
FormButton,
View,
} from "./styles";
import StyledInput from "../UiComponents/StyledInput";
const defaultImage = Asset.fromModule(require('../assets/komloy.jpg')).uri
import WindowSelector from "../ImgSelector";
import StyledButton from "../UiComponents/StyledButton";
import ImagePicker from "../components/imagePicker";
import { Image } from "react-native";
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
const CounterForm = ({ navigation, ...props }) => {
const [imgUrl, setImgUrl] = useState(props.imgUrl || defaultImage);
const handleEditImg = (newImgUrl) => {
setImgUrl(newImgUrl);
};
const handelUserImg = (userUri) => {
setImgUrl(userUri);
};
const handleSubmit = () => {
props.onFormSubmit({
id: props.id,
imgUrl
});
setImgUrl(defaultImage);
};
return (
<DismissKeyboard>
<FormWrapper>
<TopContent>
<Image
source={{uri: imgUrl}}
style={{ width: 100, height: 100 }}
/>
</TopContent>
<Row>
<StyledText title="Select a image" />
<ImagePicker getUserImg={handelUserImg} />
</Row>
<View>
<WindowSelector setImg={handleEditImg} />
</View>
</FormWrapper>
</DismissKeyboard>
);
};
export default CounterForm;
Data
import {Asset} from 'expo-asset';
const imageURI = Asset.fromModule(require('./assets/islands.jpg')).uri
const imageURI2 = Asset.fromModule(require('./assets/maeYai.jpg')).uri
export const defaultImages = [
imageURI, imageURI2
]

Related

How to pass array of objects to another screen and display them react native

How do i pass the data of selected checkboxes to the previous screen in react native.
The following is what i have done so far:
This is my SelectProducts screen
import React, {useState, useEffect} from 'react'
import { StyleSheet, Text, View, Alert, Image, ScrollView, TouchableOpacity } from 'react-native';
import Checkbox from 'expo-checkbox';
const SelectProducts = ({route}) => {
const [checkedBox, setCheckedBox] = useState([]);
const [selectedBoxesItem, setSelectedBoxesItem] = useState([]);
const [itemList, setItemList] = useState([]);
const includeSelectedItem = (item, index) => {
const newCheckedBox = [...checkedBox];
newCheckedBox[index] = !newCheckedBox[index];
setCheckedBox(newCheckedBox);
setSelectedBoxesItem({
selectedUniqueKey: item.id,
selectedItemName: item.ad_headline,
selectedItemPrice: item.ad_price,
selectedItemPic: item.ad_picture
});
}
This is the function that I'm using to send the data to the RecordASale screen after clicking on the Done button that is below the list of checkboxes.
const handleSelectedSubmit = () => {
navigation.navigate({
name: 'RecordASale',
params: {
post: [selectedBoxesItem],
},
merge: true,
})
}
And this is the checkbox:
return (
{itemList.map((item, index) => (
<DataTable.Row>
<DataTable.Cell>
<View style={styles.checkboxContainer}>
<Checkbox
key={item.id}
value={checkedBox[index]}
onValueChange={() => includeSelectedItem(item, index)}
color={checkedBox ? '#800080' : undefined}
style={styles.checkbox}
/>
</View>
</DataTable.Cell>
<DataTable.Cell>
<Image source = {{uri: "https://cdn.beraty.com/beraty-ads/"+item.ad_picture}} style = {{ width: 30, height: 30 }} />
</DataTable.Cell>
<DataTable.Cell>{item.ad_headline}</DataTable.Cell>
<DataTable.Cell>{item.ad_price}</DataTable.Cell>
</DataTable.Row>
))}
<View style = {styles.submitButton}>
<Text style = {styles.submitButtonText} onPress={() => handleSelectedSubmit()}>Done</Text>
</View>
</DataTable>
);
}
What i want to achieve is to get the following details for every checkbox selected:
item.id,
item.ad_headline,
item.ad_price,
item.ad_picture
All the above data should be passed from this SelectProducts screen to RecordASale screen
To my own understanding, what I did was that I passed objects to the function of the state below:
const [selectedBoxesItem, setSelectedBoxesItem] = useState([]);
setSelectedBoxesItem({
selectedUniqueKey: item.id,
selectedItemName: item.ad_headline,
selectedItemPrice: item.ad_price,
selectedItemPic: item.ad_picture
});
So when i did this, I only get the last selected checkbox details passed to the RecordASale screen even though i selected more than one checkbox.
This is how i'm getting the details into the RecordASale screen:
const RecordASale = ({route}) => {
return (
{(route.params?.post) ? route.params?.post.map((item, index) => (
<View>
<View key={index}>
<Image source = {{uri: "https://cdn.beraty.com/beraty-ads/"+item.selectedItemPic}} style = {{ width: 30, height: 30 }} />
<Text>{item.selectedItemName}</Text>
<Text>{item.selectedItemPrice}</Text>
</View>
</View>
)) : <Text></Text>}
);
}
I want the same details as above for all selected checboxes to be passed to the other screen, and not just one.
I believe I'm quite close to that but some things are missing in my code. Please help. Thanks.
I only shared the parts I'm having problems with.
You can use useRoute hook
const SelectProducts = ({
route
}) => {
const routes = useRoute();
console.log(routes.params ? .post)
}

Share QR React Native

I'm new in react/react native. I'm trying to share a QR Code as image.
Generate QR works, but I want to share it as an image (whatsapp, bluetooth, etc).
import QRCode from 'react-native-qrcode-svg';
let svg = useRef();
//let svg = '';
<QRCode
size={300}
value={`${name}`}
getRef={(c) => (svg = c)}
/>
I tried "get base64 string encode of the qrcode" from official documentation, but I just don't get it
//From Off Doc
getDataURL() {
this.svg.toDataURL(this.callback);
}
callback(dataURL) {
console.log(dataURL);
}
What I tried to do (all my code):
import React, { useRef } from 'react';
import QRCode from 'react-native-qrcode-svg';
const QR = ({ name }: any) => {
let svg = useRef();
const getDataURL = () => {
svg.toDataURL(callback(dataURL));
//console.log(svg);
}
callback(dataURL) {
console.log(dataURL);
}
return (
<>
<QRCode
size={300}
value={`${name}`}
getRef={(c) => (svg = c)}
/>
<Button onPress={getDataURL}
title="Call Funct"
color="#1FAAE2" />
</>
);
get error svg.toDataURL is not a function.
I have been in this for days, I also read another stackover queries with the same problem but solutions in those questions didn't work for me. Thank you in advance guys
Error toDataURL
console.log(svg)
I have changed a couple of things in your code and used it on a expo app where I installed react-native-qrcode-svg and react-native-svg
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, TextInput, Button } from "react-native";
import { useRef } from "react";
import QRCode from "react-native-qrcode-svg";
const QR = ({ name }: any) => {
let svg = useRef<SVG>(null);
const getDataURL = () => {
svg?.toDataURL(callback);
//console.log(svg);
};
function callback(dataURL: string) {
console.log(dataURL);
}
return (
<>
<QRCode size={300} value={`${name}`} getRef={(c) => (svg = c)} />
<Button onPress={getDataURL} title="Call Funct" color="#1FAAE2" />
</>
);
};
export default function App() {
const input = useRef<TextInput>(null);
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
<QR />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
Main changes from your code is defining the callback as a function
// you had
callback(dataURL) {
console.log(dataURL);
}
// but it should be
function callback(dataURL) {
console.log(dataURL);
}
// or
const callback = (dataURL) => {
console.log(dataURL)
}
and doing the call properly on getDataURL
// you had
svg.toDataURL(callback(dataURL));
// but it should be
svg.toDataURL(callback);
After those changes clicking in the button returns the dataURL in the console as expected.
Old answer before question edit:
Your issue seems to be that svg is not defined when you call svg.toDataURL how are you calling the function? If you are doing that on the first render it is possible that the ref is not ready yet.
If you are doing that using a callback in a button in the screen then the issue should be around the code setting the ref.
Can you post your whole component?
My solution with typescript: I needed to add
'// #ts-ignore' because I used Typescript
import React, { useRef } from 'react';
import QRCode from 'react-native-qrcode-svg';
import { TouchableOpacity, Text } from 'react-native';
export const Screen1 = ( ) => {
const svg = useRef();
const getDataURL = () => {
// #ts-ignore
svg.current?.toDataURL(callback);
};
const callback = (dataURL: string) => {
console.log( dataURL );
}
return (
<>
<QRCode
value={ "Some String" }
size={ 250 }
color="black"
backgroundColor="white"
getRef={(c: any) => ( svg.current = c )}
/>
<TouchableOpacity
activeOpacity={ 0.8 }
style={ styles.Button }
onPress={() => getDataURL() }
>
<Text style={ styles.Text }> Press </Text>
</TouchableOpacity>
</>
)
You get the function not found error when you testing it with web, test it with iOS simulator then it will work.

using Onclick, fetch api data in react native

I am new to React Native.
I am facing an issue with this view. Basically it is something like this when you click a button it generates any random number, now this random number becomes an id and it goes to at the end of the API url, And using this new API - with ID at the end of it. - data gets fetched. Now i've divided this task in two parts generating random number code (i.e. snippet 1) and fetching data from api ( i.e. snippet 2). As of now, I don't know how to combine them because i am new to react native so a little help here would be appreciated from anyone.
Snipppet 1
import { StyleSheet, View, Button, Text } from 'react-native';
export default class MyProject extends Component {
constructor(){
super();
this.state={
// This is our Default number value
NumberHolder : 0
}
}
GenerateRandomNumber=()=>
{
var RandomNumber = Math.floor(Math.random() * 5000) + 1 ;
this.setState({
NumberHolder : RandomNumber
})
}
render() {
return (
<View style={styles.MainContainer} >
<Text style={{marginBottom: 10, fontSize: 20}}>{this.state.NumberHolder}</Text>
<Button title="Generate Random Number" onPress={this.GenerateRandomNumber} />
</View>
);
}
}
const styles = StyleSheet.create(
{
MainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
});
Snippet 2
import React, { useState } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Button } from "native-base";
import { StyleSheet, View, ActivityIndicator, Text, TouchableOpacity, Image } from 'react-native';
export default function MyFUnction() {
const [data, setData] = useState(null);
const [visible, setVisible] = useState(true);
const fetchData = async () => {
const resp = await fetch("https://jsonplaceholder.typicode.com/photos/7");
const data = await resp.json();
setData(data);
setVisible(false);
};
const renderItem = ({ item }) => {
return (
<TouchableOpacity style={styles.list}>
<Text>{item.title}</Text>
</TouchableOpacity>
);
};
return (
<NativeBaseProvider>
<Center flex={1}>
{visible && <Button onPress={() => fetchData()}>Press</Button>}
{data && (
<FlatList
data={data}
renderItem={(item) => this.renderItem(item)}
keyExtractor={(item) => item.id.toString()}
/>
)}
</Center>
</NativeBaseProvider>
);
}
const styles = StyleSheet.create({
list: {
paddingVertical: 4,
margin: 5,
backgroundColor: '#fff',
},
});
Thanks in advance!!
I think you must understand components clearly! React is component based! But your case is not 2 components! You have a component for fetching api and showing in list! Generating a random number is not a component, but it is a method(or function) in your list component! I think it is better to use one component for list and wrap a function to it for generating random number.
import React, { useState } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Button } from "native-base";
import { StyleSheet, View, ActivityIndicator, Text, TouchableOpacity, Image } from 'react-native';
export default function MyFUnction() {
const [data, setData] = useState(null);
const [visible, setVisible] = useState(true);
const generatRandomNumber = () => {
return Math.floor(Math.random() * 5000) + 1
}
const fetchData = async () => {
const resp = await fetch(`https://jsonplaceholder.typicode.com/photos/${generatRandomNumber()}`);
const data = await resp.json();
setData(data);
setVisible(false);
};
const renderItem = ({ item }) => {
return (
<TouchableOpacity style={styles.list}>
<Text>{item.title}</Text>
</TouchableOpacity>
);
};
return (
<NativeBaseProvider>
<Center flex={1}>
{visible && <Button onPress={() => fetchData()}>Press</Button>}
{data && (
<FlatList
data={data}
renderItem={(item) => this.renderItem(item)}
keyExtractor={(item) => item.id.toString()}
/>
)}
</Center>
</NativeBaseProvider>
);
}
const styles = StyleSheet.create({
list: {
paddingVertical: 4,
margin: 5,
backgroundColor: '#fff',
},
});
But if you want to passing data between components you are have, it is not related to function or class component and you can pass data between them with props! So your code like this:
Random Number Component
import { StyleSheet, View, Button, Text } from 'react-native';
export default class MyProject extends Component {
constructor(props){ //edited
super(props); //edited
this.state={
// This is our Default number value
NumberHolder : 0
}
}
GenerateRandomNumber=()=>
{
var RandomNumber = Math.floor(Math.random() * 5000) + 1 ;
this.props.randomNumber(RandomNumber)
}
render() {
return (
<View style={styles.MainContainer} >
<Text style={{marginBottom: 10, fontSize: 20}}>{this.state.NumberHolder}</Text>
<Button title="Generate Random Number" onPress={this.GenerateRandomNumber} />
</View>
);
}
}
const styles = StyleSheet.create(
{
MainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
});
Your list component:
import React, { useState } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Button } from "native-base";
import { StyleSheet, View, ActivityIndicator, Text, TouchableOpacity, Image } from 'react-native';
// import MyProject
export default function MyFUnction() {
const [data, setData] = useState(null);
const [visible, setVisible] = useState(true);
const [number, setNumber] = useState(null);
const fetchData = async () => {
const resp = await fetch(`https://jsonplaceholder.typicode.com/photos/${number}`);
const data = await resp.json();
setData(data);
setVisible(false);
};
const renderItem = ({ item }) => {
return (
<TouchableOpacity style={styles.list}>
<Text>{item.title}</Text>
</TouchableOpacity>
);
};
return (
<NativeBaseProvider>
<Center flex={1}>
<MyProject radomNumber={(number) => setNumber(number)}
{visible && <Button onPress={() => fetchData()}>Press</Button>}
{data && (
<FlatList
data={data}
renderItem={(item) => this.renderItem(item)}
keyExtractor={(item) => item.id.toString()}
/>
)}
</Center>
</NativeBaseProvider>
);
}
const styles = StyleSheet.create({
list: {
paddingVertical: 4,
margin: 5,
backgroundColor: '#fff',
},
});
thanks everyone for helping me.
here i am posting answer to my question, if anybody in future need an answer.
import React, { useEffect, useState, Component } from 'react';
import { StyleSheet, View, Button, FlatList, Text, Image } from 'react-native';
export default class MyProject extends Component {
constructor(){
super();
this.state={
// This is our Default number value
NumberHolder : 1,
books: []
}
}
GenerateRandomNumber=()=>
{
var RandomNumber = Math.floor(Math.random() * 5000) + 1;
fetch(`https://jsonplaceholder.typicode.com/photos/${RandomNumber}`)
.then((response) => response.json())
.then(booksList => {
this.setState({ books: booksList });
});
this.setState({
NumberHolder : RandomNumber
})
}
render() {
let Image_Http_URL ={ uri: 'https://reactnativecode.com/wp-content/uploads/2017/05/react_thumb_install.png'};
return (
<View style={styles.MainContainer} >
<Text style={{marginBottom: 10, fontSize: 20}}>{this.state.NumberHolder}</Text>
<Image
style={{width: '100%', height: 200,resizeMode : 'stretch' }}
source={{uri: this.state.books.url}}
/>
<Text style={{marginBottom: 10, fontSize: 12}}>{this.state.books.title}</Text>
<Text style={{marginBottom: 10, fontSize: 12}}>{this.state.books.url}</Text>
<Text style={{marginBottom: 10, fontSize: 12}}>{this.state.books.thumbnailUrl}</Text>
<Button title="Generate Random Number" onPress={this.GenerateRandomNumber} />
</View>
);
}
}
const styles = StyleSheet.create(
{
MainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
});

React Native - useRef for an array (Expo)

Been using Expo and RN on an app, but I'm stuck with a problem. I'm using Expo Video
to make a photo/video gallery. The problem is, if I have more than 1 video in the array, only the last one will play (problem with useRef). And I couldn't find a way to create a ref for each one of them.
Solutions that I've tried and half worked: I created a VideoComponent (as a function then added on return), and each component had its own useRef and useState for playing inside the component a different useRef/useState for video/status for each. It worked okay-ish. But the problem was when other states changed (user presses like, for example). Whenever a state changes, and rerenders, the whole video reset to the beginning. Which is not ok.
The video reset on state change of other components doesn't affect the video if doing it normally (one useRef/state) but as I said, It's only playing the last component, which is not okay.
import React, { useRef, useState } from 'react';
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
StatusBar,
} from 'react-native';
function App(props) {
const [allData, setAllData] = useState([
{
medias: [
{ link: 'https://link.com/link1.avi', mediaExtension: 'avi' },
{ link: 'https://link.com/link2.jpg', mediaExtension: 'jpg' },
{ link: 'https://link.com/link3.mov', mediaExtension: 'mov' },
],
name: 'Name',
description: 'description',
},
]);
const video = useRef(null);
const [status, setStatus] = useState({});
return (
<View style={{}}>
<FlatList
horizontal
data={allData}
renderItem={({ item }) => (
<View style={{}}>
{item.medias.map((item) => (
<View>
{item.mediaExtension === 'mov' || 'avi' || 'WebM' ? (
<View style={{ flex: 1 }}>
<TouchableOpacity
onPress={() =>
video.isPlaying
? video.current.pauseAsync()
: video.current.playAsync()
}>
<Video
ref={video}
style={{ alignSelf: 'center' }}
source={{
uri: item.link,
}}
onPlaybackStatusUpdate={(status) =>
setStatus(() => status)
}
/>
</TouchableOpacity>
</View>
) : (
<Image style={{}} source={{ uri: item.link }} />
)}
</View>
))}
</View>
)}
/>
</View>
);
}
export default App;
As far as I understand, you want to create a FlatList of Videos, and onScroll you want to pause it. This can be implemented as shown below
Also, here is a Working Example for this
import * as React from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import VideoPlayer from './components/VideoPlayer';
const Videos = [
{
_id: 1,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 2,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 3,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 4,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 5,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 6,
source: require('./assets/videoplayback.mp4'),
},
];
export default function App() {
const [Viewable, SetViewable] = React.useState([]);
const ref = React.useRef(null);
const onViewRef = React.useRef((viewableItems) => {
let Check = [];
for (var i = 0; i < viewableItems.viewableItems.length; i++) {
Check.push(viewableItems.viewableItems[i].item);
}
SetViewable(Check);
});
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 80 });
return (
<View style={styles.container}>
<FlatList
data={Videos}
keyExtractor={(item) => item._id.toString()}
renderItem={({ item }) => <VideoPlayer {...item} viewable={Viewable} />}
ref={ref}
onViewableItemsChanged={onViewRef.current}
viewabilityConfig={viewConfigRef.current}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
and VideoPLayer component looks like this
import * as React from 'react';
import { Text, View, StyleSheet, Dimensions } from 'react-native';
import Constants from 'expo-constants';
import { Video, AVPlaybackStatus } from 'expo-av';
export default function VideoPlayer({ viewable, _id, source }) {
const video = React.useRef(null);
React.useEffect(() => {
if (viewable) {
if (viewable.length) {
if (viewable[0]._id === _id) {
video.current.playAsync();
} else {
video.current.pauseAsync();
}
} else {
video.current.pauseAsync();
}
} else {
video.current.pauseAsync();
}
}, [viewable]);
return (
<View style={styles.container}>
<Video
ref={video}
source={source}
rate={1.0}
volume={1.0}
resizeMode={'contain'}
isLooping
shouldPlay
style={styles.video}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
width: Dimensions.get('window').width,
marginBottom: 100,
marginTop: 100,
},
video: {
width: Dimensions.get('window').width,
height: 300,
},
});

Conditional rendering with React Hooks : loading

I am learning how to use React Hooks and have been stuck for many hours on something that's supposed to be very simple.
I am trying to display a a text if the state variable "loading" is true. If it's false, I want to display something else.
No matter what I try, "loading" is always false or at least, the UI does not appear to reflect its value.
here is the code:
import React, {useState, useEffect} from 'react';
import {View, SafeAreaView, Text} from 'react-native';
const testScreen= (props) => {
const [loading, setLoading ] = useState(true);
useEffect(() => {
setLoading(false);
}, []);
if(loading)
{
return <Text>Hi</Text>;
}
else
{
return<Text.Hey</Text>
}
}
export default testScreen;
Any help will be more than welcome and I am sorry if this is very basic.
UPDATE: Here is the actual code I am working with. SetLoading is supposed to update the state variable to false but never does or at least, the UI des not render.
import React, {useState, useEffect} from 'react';
import {View, SafeAreaView, Text, ActivityIndicator} from 'react-native';
import CategoryTag from '../Components/CategoryTag';
import firestore from '#react-native-firebase/firestore';
const CategoryScreen = (props) => {
const topicCollection = firestore().collection('Topics')
.where("active","==",true);
//hooks for topics
const [topics,setTopics] = useState([]);
const [loading, setLoading ] = useState(true);
//get all active topics
useEffect(() => {
return topicCollection.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const { active, name } = doc.data();
list.push({
id: doc.id,
active,
name,
});
});
setTopics(list);
setLoading(false);
});
}, []);
const renderTopics = () =>{
return(
topics.map((item) =>{
return(
<CategoryTag key = {item.id}
color={userTopics.includes(item.name) ?"#51c0cc":"#303239"}
name = {item.name}
isSelected = {userTopics.includes(item.name)}
handleClick = {addTopicToUserTopicCollection}
/>
)
})
)
}
if(loading)
{
return (
<SafeAreaView style={{flex:1, backgroundColor:"#455a65"}}>
<View style={{width:200, padding:20, paddingTop:60}}>
<Text style ={{fontSize:25, fontWeight:"bold",
color:"#fff"}}>What are you</Text>
<Text style ={{fontSize:22, color:"#fff"}}>interested in?
</Text>
</View>
<View style={{flex:1, alignItems:"center",
justifyContent:"center", alignSelf:"center"}}>
<ActivityIndicator />
</View>
</SafeAreaView>
)
}
else // this never runs
{
return (
<SafeAreaView style={{flex:1, backgroundColor:"#455a65"}}>
<View>
<View style={{width:200, padding:20, paddingTop:60}}>
<Text style ={{fontSize:25, fontWeight:"bold",
color:"#fff"}}>What are you</Text>
<Text style ={{fontSize:22, color:"#fff"}}>interested in?
</Text>
</View>
<View style ={{flexDirection:"column", paddingTop:20}}>
<View style ={{padding:15, paddingTop:15,
marginBottom:15,
flexWrap:"wrap", flexDirection:"row"}}>
{renderTopics(topics)}
</View>
</View>
</View>
</SafeAreaView>
);
}
}
export default CategoryScreen;
You are immediately setting your setLoading state to false and therefore loading text might be rendering for fraction of second, or not at all, like a glitch. Try setting setLoading with a timeout and then you will see the intended behaviour.
const TestScreen= (props) => {
const [loading, setLoading ] = useState(true);
useEffect(() => {
setTimeout(()=>setLoading(false), 3000);
}, []);
if(loading)
{
return <Text>Hi</Text>;
}
else
{
return<Text>hey</Text>
}
}

Categories