react native TouchableOpacity, different functions by first and second click - javascript

I'm creating a simple text-to-speech function for my react native app.
I have a button, when you click it for the first time, it will read the text and play the sound.
But I want to make it dynamic. For example: If you click again it should stop, if click again, should play again, etc.....
But now, it is only available for playing the sound with any click.
Where/how should I execute the stopReadText()?
I still don't have any idea about this. Thanks a lot.
Here is the code:
const readText = () => {
Speech.speak('text')
}
const stopReadText = () => {
Speech.stop()
}
return (
<View>
<TouchableOpacity onPress=(readText)>
<Divider style={styles.modalDivider} />
<Image
style={styles.speaker}
source={require('../../assets/speaker.png')}
/>
</TouchableOpacity>
</View>
)
(I am using expo-speech)

You can do it by taking on a boolean state variable:
import { useState } from 'react';
const [isPlay,setIsPlay]=useState(false)
const readText = () => {
Speech.speak('text')
}
const stopReadText = () => {
Speech.stop()
}
const handlePlay =() =>{
if(!setIsPlay){
readText()
setIsPlay(true)
}
else {
stopReadText()
setIsPlay(false)
}
}
return (
<View>
<TouchableOpacity onPress={handlePlay}>
<Divider style={styles.modalDivider} />
<Image
style={styles.speaker}
source={require('../../assets/speaker.png')}
/>
</TouchableOpacity>
</View>
)

Related

Why can't I call the parent function passed as props to child component?

What I am trying To Do
I am building a simple expo managed audio player app. On my App Screen, I need display a list of songs. When a user clicks on the song, it plays and once the play finishes, the "Songs Played" at the bottom of the page should increase. I am using expo-av API for this.
Here is the breakdown of the app:
App.js
Here I have an array (Data) that holds the songs. To keep it simple, I am using the same song for all elements. count variable holds the count of songs and there is a function (IncreaseCount) which is passed to the ChildComponent as prop. Flatlist is used to render the ChildComponents
import { View, Text, FlatList } from 'react-native'
import React, {useState} from 'react'
import ChildComponent from './ChildComponent';
const Data = [
{
key: "1",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav"
},
{
key: "2",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav"
},
{
key: "3",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav"
}
]
export default function App() {
const [count, setcount] = useState(0);
const IncreaseCount = ()=>{
setcount(count + 1);
}
const renderItem = ({item, index})=>{
return(
<View style={{marginTop: 10}} >
<ChildComponent path={item.path} IncreaseCount={()=>IncreaseCount} index={index} songURL={item.song}/>
</View>
)
}
return (
<View style={{justifyContent: "center", alignItems: "center", marginTop: 200}}>
<FlatList
data={Data}
renderItem={renderItem}
extraData={count}
/>
<Text style={{marginTop: 30}}> Number of Songs Played: {count} </Text>
</View>
)
}
ChildComponent
Here I use expo-av API. Using the loadAsync() method, I Initially load the songs upon first render using useEffect hook. Then using onPress method of the button I invoke the playAsync() method of the playBackObject.
Using the setOnPlayBackStatusUpdate method, I listen for status changes. When playBackObjectStatus.didJustFinish becomes true, I call the props.IncreaseCount().
import { View, Button } from 'react-native'
import React, {useRef, useEffect} from 'react'
import { Audio } from 'expo-av';
export default function ChildComponent(props) {
const sound = useRef(new Audio.Sound());
const PlayBackStatus = useRef();
useEffect(()=>{
LoadAudio();
return ()=> sound.current.unloadAsync()
},[])
const LoadAudio = async ()=>{
PlayBackStatus.current = sound.current.loadAsync({uri: props.songURL})
.then((res)=>{
console.log(`load result : ${res}`)
})
.catch((err)=>console.log(err))
}
const PlayAuido = async ()=>{
PlayBackStatus.current = sound.current.playAsync()
.then((res)=>console.log(`result of playing: ${res}`))
.catch((err)=>console.log(`PlayAsync Failed ${err}`))
}
sound.current.setOnPlaybackStatusUpdate(
(playBackObjectStatus)=>{
console.log(`Audio Finished Playing: ${playBackObjectStatus.didJustFinish}`)
if(playBackObjectStatus.didJustFinish){
console.log(`Inside the If Condition, Did the Audio Finished Playing?: ${playBackObjectStatus .didJustFinish}`)
props.IncreaseCount();
}
}
)
return (
<View >
<Button title="Play Sound" onPress={PlayAuido} />
</View>
);
}
Problem I am facing
No matter what I do, I can't get the props.IncreaseCount to be called in App.js. Using console.log inside the if condition of setOnPlayBackStatusUpdate, I know that the props.IncreaseCount() method is being called, but the IncreaseCount() function in App.js is never called. Any help is greatly appreciated!
Here is the snack
Inside here please do this
<ChildComponent path={item.path} IncreaseCount={IncreaseCount} index={index} songURL={item.song}/>
Ive changed IncreaseCount={IncreaseCount}
DO lemme know if this helps
You have two ways to call the IncreaseCount function, in the ChildComponent
<ChildComponent IncreaseCount={IncreaseCount} path={item.path} .......
or
<ChildComponent IncreaseCount={() => IncreaseCount()} path={item.path} .......
You made a mistake while passing increaseCount prop to the ChildComponent
Here are to correct ways to do it:
return(
<View style={{marginTop: 10}} >
<ChildComponent path={item.path} IncreaseCount={IncreaseCount} index={index} songURL={item.song}/>
</View>
)
or: IncreaseCount={() => IncreaseCount()}

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.

Render between start and stop button in React Native

I wanna render between displaying a start and stop button based on pressing the buttons. That means if the start button gets pressed the stop button should be showed and vice versa. In the standard case the stop button should be rendered. With the following code I get an error because status can be read only so I need some other way to set the status.
const SampleComponent = () => {
const startButton = () => {
return <Component1 />;
};
const stopButton = () => {
return <Component2 />;
};
const status = stopButton;
return (
<View>
<Pressable
onPress={() => {
if (status == stopButton) {
status = startButton;
} else {
status = stopButton;
}
}}
>
{status}
</Pressable>
</View>
);
};
export default SampleComponent;
I need a React Native way to do this. I tried to study the docs and also other material but I just don't get it.
React way of doing this would be to use state hook.
You could use a boolean state variable and toggle it on button click.
Solution
const SampleComponent = () => {
const StartButton = () => {
return <Component1 />;
};
const StopButton = () => {
return <Component2 />;
};
const [status, setStatus] = useState(false);
return (
<View>
<Pressable
onPress={() => {
setStatus(!status);
}}
>
{status ? <StopButton /> : <StartButton />}
</Pressable>
</View>
);
};
export default SampleComponent;
Remember to give components uppercase letter.

React native useState and return statement not working together

Here whenever i click the icon it doesnt show anything. It supposed to be showing some text and when clicked again it should hide the text. Im using react native.
import React, { useState } from 'react';
import { View, Text, StyleSheet, Button, Image, TouchableOpacity} from 'react-native';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
export default function Edit(props, { navigation }) {
const [slide, setSlide] = useState(false);
const toggle = () => {
setSlide(!slide);
console.log('clicked');
return (
<View>
<Text>random</Text>
<Text>random</Text>
</View>
);
}
return (
<View>
<FontAwesome name="sliders" size={30} color="#000" onPress={() => toggle()}/>
</View>
}
After testing the only thing it shows is the console.log('clicked') message. It does not display anything else. Also the icon displays normally. Everything is working except the and the content in those tags.
Rather than returning the View from your toggle function, you actually need to display that view your view hierarchy (eg what is returned from your component).
I've demonstrated in the example by using a ternary expression -- if slide is true, it gets shown, otherwise it does not.
export default function Edit(props, { navigation }) {
const [slide, setSlide] = useState(false);
const toggle = () => {
setSlide(!slide);
console.log('clicked');
}
return (
<View>
<FontAwesome name="sliders" size={30} color="#000" onPress={() => toggle()}/>
{slide ? <View>
<Text>random</Text>
<Text>random</Text>
</View> : null}
</View>
);
}
Snack example: https://snack.expo.io/7lVezwWs7

Remove an item in AsyncStorage using FlatList

Sorry for the inexperience, but how do I remove an Item in Async Storage renderized in Flat List, for example:
This is my component that creates a flatlist
export default function Saved() {
const [colors, setColors] = useState([]);
useEffect(() => {
async function getStorage() {
const nomeStorage = await AsyncStorage.getAllKeys();
if (nomeStorage != null) {
setColors(nomeStorage);
}
}
getStorage();
}, [colors])
return (
<View style={styles.body}>
<FlatList
data={colors}
keyExtractor={(item) => item}
renderItem={({ item }) => <Saveds data={item} />}
/>
</View>
);
}
and this is my FlatList renderized component
export default function Saveds(props) {
return (
<View>
<View style={styles.Boxes}>
<Box color={props.data}>
<Badge
badgeStyle={styles.badge}
value={<Text style={styles.color}>{props.data}</Text>}
/>
</Box>
<TouchableOpacity style={styles.btn}>
<Icon name={'trash-outline'} color={'#FFF'} size={30} />
</TouchableOpacity>
</View>
</View>
);
}
I need one way to when I click in my TouchableOpacity, I delete the selected data in my AsyncStorage.
The name in my AsyncStorage is the same as the value, so I can delete the AsyncStorage getting the value of my props.data.
Anyone can help me?
Deleting from your async storage should be as easy as just calling AsyncStorage.removeItem(key)
I had some similar functionality in an app that I made a while ago, I attached the delete function call to the onLongPress prop of touchableOpacity:
<TouchableOpacity
onPress={() => navigation.navigate('UserScreen', data)}
onLongPress={handleLongPress}>
<View style={styles.container}>
// ...
</View>
</TouchableOpacity>
And earlier in the component, I defined a function that handles the deleting:
const handleLongPress = async () => {
// In your instance, you should be OK to replace data.key with props.data
await AsyncStorage.removeItem(data.key);
/* I then had another function that was passed in as a prop
to update another component when the deletion had taken place */
await onChange();
};

Categories