Make the RBSheet reusable as a widget React-Native - javascript

I am using RBSheet Package for showing up the bottom sheet in my project. I have understood the concept of how to use it from the documentation. Is there a way that I can make the bottom sheet reusable like a Widget? I have tried doing this:
BottomSheetComponent.js
const BottomSheet = ({ message, buttonText }) => {
// to open this sheet as soon as someone call this Component
this.RBSheet.open();
return(
<RBSheet ref={ ref => { this.RBSheet = ref; }}
customStyles={{mask: { backgroundColor: COLORS.dark }, container: { elevation: 100 }}}>
<View style={styles.messageContainer}>
{/* Add more data later */}
</View>
</RBSheet>
);
}
export default BottomSheet;
MainComponent.js
const MainComponent = () => {
const bottomSheet = () => {
// Trying to call the bottom sheet here
<BottomSheet />
}
return(
<View>
<Button onPress={() => bottomSheet()} title="Bottom Sheet" />
</View>
);
}
export default MainComponent;
I have failed to get the bottom sheet. I have just started working on React Native. I don't want to go with the generic way which is explained in the Package's docs, it is not a good practice to make the same bottom sheet in two different pages from scratch.

So, after a lot of research, I finally figured out what I was doing wrong.
I was using it for the Class Component, although I was using Functional Component
The reference was missing, for which, Metro Build Runner, was giving me errors.
Solution
To solve the issue:
I need to create my own reference
Pass it to the ref props of the Package
MainComponent.js
import React, { useRef } from 'react';
const MainComponent = () => {
// To be used for the reference for the bottom sheet
const sheetRef = useRef();
const bottomSheet = () => {
// Here how you open the bottom sheet right
sheetRef.current.open();
}
return(
<View>
<Button onPress={() => bottomSheet()} title="Bottom Sheet" />
{/* Passing the sheet ref for the binding */}
<BottomSheet sheetRef={sheetRef} />
</View>
);
}
export default MainComponent;
BottomSheet.js
const BottomSheet = ({ sheetRef }) => {
return(
{/* This is the place to make it work */}
<RBSheet ref={sheetRef}
customStyles={{mask: { backgroundColor: COLORS.dark }, container: { elevation: 100 }}}>
<View style={styles.messageContainer}>
{/* Add more data later */}
</View>
</RBSheet>
);
}
export default BottomSheet;

Can you try this?
const bottomSheet = () => {
// Trying to call the bottom sheet here
return <BottomSheet />;
}

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()}

React Native - how do you call the function of a child component from its parent (without triggering an infinite loop)?

I am trying to create a React Native e-commerce app where the featured products are shown, but then the user can view a list of categories via a sheet popping up from the bottom, which will load the products from said category.
I have managed to create such a bottom sheet using react-native-btr's BottomSheet. However, the function to show/hide the component (simply toggling a boolean in state) needs to be available to the component itself (to handle the back button and backdrop presses).
This is the component code:
const TestNav = (props, ref) => {
const [visible, setVisible] = useState(false);
const toggleVisible = () => {
setVisible(!visible);
};
useImperativeHandle(ref, () => toggleVisible());
return (
<BottomSheet
visible={visible}
//setting the visibility state of the bottom shee
onBackButtonPress={toggleVisible}
//Toggling the visibility state on the click of the back botton
onBackdropPress={toggleVisible}
//Toggling the visibility state on the clicking out side of the sheet
>
<View style={styles.bottomNavigationView}>
<View
style={{
flex: 1,
flexDirection: 'column',
}}
>
{DummyData.map((item) => {
return (
<Button
key={item.id}
title={item.name}
type="clear"
buttonStyle={styles.button}
onPress={() => console.log(item.name)}
/>
);
})}
</View>
</View>
</BottomSheet>
);
};
export default React.forwardRef(TestNav);
And here is the code for the screen where it's being used (it's called ChatScreen as I'm using it as a testing ground since I haven't implemented that feature yet)
import React, { useRef } from 'react';
import {SafeAreaView,StyleSheet,View,Text} from 'react-native';
import TestNav from '../components/TestNav';
import { Button } from 'react-native-elements';
const ChatScreen = () => {
const childRef = useRef(null);
const toggleBottomNavigationView = () => {
if (myRef.current) {
childRef.current.toggleVisible;
}
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<Text
style={{
fontSize: 20,
marginBottom: 20,
textAlign: 'center',
}}
>
Content goes here
</Text>
<Button
onPress={() => toggleBottomNavigationView()}
title="Show Bottom Sheet"
/>
<TestNav ref={childRef} />
</View>
</SafeAreaView>
);
};
export default ChatScreen;
However, this code has somehow triggered an infinite loop, as I get this message:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
How do I go about fixing this?
I think the issue lies with how you define the imperative handle. Hook callbacks are called each time the component renders and so () => toggleVisible() is called each render and creates a render loop. It should be passed a callback that returns the imperative functions/values to be made available to callees.
const toggleVisible = () => {
setVisible(visible => !visible);
};
useImperativeHandle(ref, () => ({
toggleVisible,
}));
In ChatScreen you then need to invoke the function. I'll assume the myRef in your snippet was a typo since it's not declared in the component and the usage appears to be similar to the guard pattern.
const toggleBottomNavigationView = () => {
childRef.current && childRef.current.toggleVisible();
// or childRef.current?.toggleVisible();
};

React Native: Passing useState() data to unrelated screens

Explanation: I am creating a fitness app, my fitness app has a component called WorkoutTimer that connects to the workout screen, and that screen is accessed via the HomeScreen. Inside the WorkoutTimer, I have an exerciseCount useState() that counts every time the timer does a complete loop (onto the next exercise). I have a different screen called StatsScreen which is accessed via the HomeScreen tab that I plan to display (and save) the number of exercises completed.
What I've done: I have quite literally spent all day researching around this, but it seems a bit harder with unrelated screens. I saw I might have to use useContext() but it seemed super difficult. I am fairly new to react native so I am trying my best haha! I have attached the code for each screen I think is needed, and attached a screenshot of my homeScreen tab so you can get a feel of how my application works.
WorkoutTimer.js
import React, { useState, useEffect, useRef } from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Button,
Animated,
Image,
SafeAreaView,
} from "react-native";
import { CountdownCircleTimer } from "react-native-countdown-circle-timer";
import { Colors } from "../colors/Colors";
export default function WorkoutTimer() {
const [count, setCount] = useState(1);
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
const exercise = new Array(21);
exercise[1] = require("../assets/FR1.png");
exercise[2] = require("../assets/FR2.png");
exercise[3] = require("../assets/FR3.png");
exercise[4] = require("../assets/FR4.png");
exercise[5] = require("../assets/FR5.png");
exercise[6] = require("../assets/FR6.png");
exercise[7] = require("../assets/FR7.png");
exercise[8] = require("../assets/FR8.png");
exercise[9] = require("../assets/S1.png");
exercise[10] = require("../assets/S2.png");
exercise[11] = require("../assets/S3.png");
exercise[12] = require("../assets/S4.png");
exercise[13] = require("../assets/S5.png");
exercise[14] = require("../assets/S6.png");
exercise[15] = require("../assets/S7.png");
exercise[16] = require("../assets/S8.png");
exercise[17] = require("../assets/S9.png");
exercise[18] = require("../assets/S10.png");
exercise[19] = require("../assets/S11.png");
exercise[20] = require("../assets/S12.png");
exercise[21] = require("../assets/S13.png");
return (
<View style={styles.container}>
<View style={styles.timerCont}>
<CountdownCircleTimer
isPlaying
duration={45}
size={240}
colors={"#7B4FFF"}
onComplete={() => {
setCount((prevState) => prevState + 1);
setExerciseCount((prevState) => prevState + 1);
if (count == 21) {
return [false, 0];
}
return [(true, 1000)]; // repeat animation for one second
}}
>
{({ remainingTime, animatedColor }) => (
<View>
<Image
source={exercise[count]}
style={{
width: 150,
height: 150,
}}
/>
<View style={styles.timeOutside}>
<Animated.Text
style={{
color: animatedColor,
fontSize: 18,
position: "absolute",
marginTop: 67,
marginLeft: 35,
}}
>
{remainingTime}
</Animated.Text>
<Text style={styles.value}>seconds</Text>
</View>
</View>
)}
</CountdownCircleTimer>
</View>
</View>
);
}
const styles = StyleSheet.create({})
WorkoutScreen.js
import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import WorkoutTimer from "../components/WorkoutTimer";
export default function WorkoutScreen() {
return (
<View style={styles.container}>
<WorkoutTimer />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
HomeScreen.js
import React from "react";
import { StyleSheet, Text, View, SafeAreaView, Button } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { AntDesign } from "#expo/vector-icons";
import { Colors } from "../colors/Colors";
export default function HomeScreen({ navigation }) {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.pageRef}>SUMMARY</Text>
<Text style={styles.heading}>STRETCH & ROLL</Text>
<View style={styles.content}>
<TouchableOpacity
style={styles.timerDefault}
onPress={() => navigation.navigate("WorkoutScreen")}
>
<Button title="START WORKOUT" color={Colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={styles.statContainer}
onPress={() => navigation.navigate("StatsScreen")}
>
<AntDesign name="barschart" size={18} color={Colors.primary} />
<Text style={{ color: Colors.primary }}>Statistics</Text>
<AntDesign name="book" size={18} color={Colors.primary} />
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({})
StatsScreen.js
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
export default function StatsScreen() {
return (
<View style={styles.container}>
<Text display={exerciseCount} style={styles.exerciseText}>
{exerciseCount}
</Text>
<Text display={workoutCount} style={styles.workoutText}>
{workoutCount}
</Text>
</View>
);
}
const styles = StyleSheet.create({});
Home Screen Image
As far as I can tell, you're almost there! You're trying to get your 2 state
variables from the WorkoutTimer like this:
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
Unfortunatly this won't work :( . These two variables change throughout your
App's life-time and that kinda makes them "special".
In React, these kinds of variables need to be declared in a parent component
and passed along to all children, which are interested in them.
So in your current Setup you have a parent child relationship like:
HomeScreen -> WorkoutScreen -> WorkoutTimer.
If you move the variables to HomeScreen (HomeScreen.js)
export default function HomeScreen({ navigation }) {
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
you can then pass them along to WorkoutScreen or StatsScreen with something
like:
navigation.navigate("WorkoutScreen", { exerciseCount })
navigation.navigate("StatsScreen", { exerciseCount })
You'll probably have to read up on react-navigation's documentation for .navigate I'm not sure I remember this correctly.
In order to read the variable you can then:
export default function WorkoutScreen({ navigation }) {
const exerciseCount = navigation.getParam(exerciseCount);
return (
<View style={styles.container}>
<WorkoutTimer exerciseCount={exerciseCount} />
</View>
);
}
and finally show it in the WorkoutTimer:
export default function WorkoutTimer({ exerciseCount }) {
Of course that's just part of the solution, since you'll also have to pass
along a way to update your variables (setExerciseCount and setWorkoutCount).
I encourage you to read through the links I posted and try to get this to work.
After you've accumulated a few of these stateful variables, you might also want to look at Redux, but this is a bit much for now.
Your app looks cool, keep at it!
I ended up solving this problem with useContext if anyone is curious, it was hard to solve initially. But once I got my head around it, it wasn't too difficult to understand.
I created another file called exerciseContext with this code:
import React, { useState, createContext } from "react";
const ExerciseContext = createContext([{}, () => {}]);
const ExerciseProvider = (props) => {
const [state, setState] = useState(0);
//{ exerciseCount: 0, workoutCount: 0 }
return (
<ExerciseContext.Provider value={[state, setState]}>
{props.children}
</ExerciseContext.Provider>
);
};
export { ExerciseContext, ExerciseProvider };
and in App.js I used ExerciseProvider which allowed me to pass the data over the screens.
if (fontsLoaded) {
return (
<ExerciseProvider>
<NavigationContainer>
<MyTabs />
</NavigationContainer>
</ExerciseProvider>
);
} else {
return (
<AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} />
);
}
}
I could call it with:
import { ExerciseContext } from "../components/ExerciseContext";
and
const [exerciseCount, setExerciseCount] = useContext(ExerciseContext);
This meant I could change the state too! Boom, solved! If anyone needs an explanation, let me know!
I think you have to use Mobx or Redux for state management. That will be more productive for you instead built-in state.

RN - How to create components and put other components in it

I'm trying to create a custom component and put other components like or inside this component but it doesn't seem to be working (nothing shows up). Could someone be kind enough to provide an answer or perhaps correct my understanding where it's gone wrong?
For example, if I have a Home page and inside there's a Card component where within there's Text and View components.
import React from 'react'
import {Text, View} from 'react-native'
const CoolCard = ({props}) => {
return(
<View>
{props}
</View>
)
}
const Home = () => {
return(
<View>
<CoolCard>
<Text>This is a cool card!</Text>
</CoolCard>
</View>
)
}
export default Home
This doesn't work but if I do
const Home = () => {
return(
<View>
<CoolCard props = {
<Text>This is a cool card!</Text>
}/>
</View>
)
}
this works, which I understand. Is there a way for me to write the first example to make it work?
Thanks a lot!
You should use the 'children' prop to get the children
const CoolCard = ({children}) => {
return(
<View>
{children}
</View>
)
}
const Home = () => {
return(
<View>
<CoolCard>
<Text>This is a cool card!</Text>
</CoolCard>
</View>
)
}
export default Home
In order to "wrap" a component inside another you can use props.children this is how it looks in a react functional component :
Wrapper component:
const WrapComponent = ({children}) => (
<Text>
{children}
</Text>
)
Then you can wrap it around any valid JSX:
<WrapComponent> {/* put stuff here */} </WrapComponent>
You can find more in the official react documentation

How to use HoC with React Native

I have an listing app where users can add items for multiple categories, when they want to add new record, there are 3 related screens with this particular feature. All of those screens have <Header/> component, so i thought HoC would be nice here so that i can reuse it across 3 screens.
However, i could not accomplish it.
Here is what i tried so far:
This is my HoC class
import React, { Component } from 'react';
import { View, StyleSheet, Text, StatusBar } from 'react-native';
import Header from '../components/Header';
const NewAd = (WrappedComponent) => {
class NewAdHoc extends Component {
handleBackPress = () => {
this.props.navigation.navigate('Home');
StatusBar.setBarStyle('dark-content', true);
}
render() {
const {contentText, children} = this.props
return (
<View style={styles.container}>
<Header
headerText={'Yeni ilan ekle'}
onPress={this.handleBackPress}
/>
<View style={styles.contentContainer}>
<Text style={styles.contentHeader}>{contentText}</Text>
<WrappedComponent/>
</View>
</View>
);
}
}
return NewAdHoc;
}
this is my screen:
class NewAdScreen extends Component {
render() {
const Content = () => {
return (
<View style={styles.flatListContainer}>
<ListViewItem />
</View>
);
}
return (
NewAdHoc(Content)
)
}
}
after that i am getting error
TypeError: (0 , _NewAdHoc.NewAdHoc) is not a function(…)
and i have no idea how can i fix it because this is my first time using hocs on a react-native app. I have looked why this error is popping and they suggest import components in this way:
import {NewAdHoc} from '../hocs/NewAdHoc';
but even this is not solved it.
any help will be appreciated, thanks.
The main purpose of a HOC is to encapsulate and reuse stateful logic across components. Since you are just reusing some jsx and injecting nothing in WrappedComponent you should be using a regular component here:
const NewAd = ({ contentText, children }) => {
handleBackPress = () => {
this.props.navigation.navigate('Home');
StatusBar.setBarStyle('dark-content', true);
}
return (
<View style={styles.container}>
<Header
headerText={'Yeni ilan ekle'}
onPress={this.handleBackPress}
/>
<View style={styles.contentContainer}>
<Text style={styles.contentHeader}>{contentText}</Text>
{children}
</View>
</View>
);
}
And use it like this
return(
<>
<NewAd>
<Screen1 />
</NewAd>
<NewAd>
<Screen2 />
</NewAd>
<NewAd>
<Screen3 />
</NewAd>
</>
)

Categories