React-Native Nested Navigation is causing 2 menus to appear - javascript

I am currently running into an issue trying to make a Shopping List app with React-Native. I am using React Navigation to handle the screens but I have run into an issue where if I click the Float Action Button on the Home Screen it will pop up the "I am the AddShoppingListForm", However when I open the next screen and click the button again that menu pops up as well as the menu that should pop up at the screen saying "I am the AddShoppingListItemForm" and they overlap each other.
How would I go about making sure the correct menu opens on the correct screen? I have the "isVisible" currently being stored in Redux to make it easier to change the value depending on how nested I am on the components, But I am not sure if this is correct way to go about doing this.
I tried to make a Overlay component that could act as a wrapper for the Menu component that gets passed there but I am not sure that is the correct way to go about doing that. Any advice would be greately appreciated.
Here is my snippets of code.
HomeScreen:
export default function HomeScreen({ navigation }) {
const isVisible = useSelector(selectOverlayVisiblity);
const dispatch = useDispatch();
const toggleOverlay = () => {
dispatch(
setOverlayVisible(!isVisible)
)
};
return (
<View style={styles.container}>
{/* Shopping List */}
<ShoppingList navigation={navigation} />
<PopupMenu menu={<AddShoppingListForm />} isVisible={isVisible} />
{/* Action Button */}
<ActionButton buttonColor="rgba(231,76,60,1)" onPress={toggleOverlay} />
</View>
);
}
ListScreen:
export default function ShoppingListDetailScreen({ navigation }) {
const currentlistitems = useSelector(selectCurrentList);
const isVisible = useSelector(selectOverlayVisiblity);
const dispatch = useDispatch();
const toggleOverlay = () => {
dispatch(
setOverlayVisible(!isVisible)
)
};
return (
<View style={styles.container}>
{currentlistitems.map(function (item) {
return <ListItemComponent key={item.id} title={item.title} avatar_url={item.avatar_url} />;
})}
<PopupMenu isVisible={isVisible} menu={<AddShoppingListItemForm />} />
{/* Action Button */}
<ActionButton buttonColor="rgba(231,76,60,1)" onPress={toggleOverlay} />
</View>
);
}
Popup Menu:
export default function PopupMenu({ navigation, isVisible, menu }) {
const dispatch = useDispatch();
const toggleOverlay = () => {
dispatch(setOverlayVisible(!isVisible));
};
// console.log('MENU >>>', menu);
return (
<View style={styles.container}>
{/* Overlay */}
{Platform.OS === "web" ? (
<WebModal isVisible={isVisible} menu={menu} />
) : (
<MobileModal isVisible={isVisible} menu={menu} />
)}
{/* End of Overlay */}
</View>
);
}
ShoppingListForm:
function AddShoppingListForm() {
const route = useRoute();
console.log('AddShoppingListLitem >>', route.name);
return (
<View>
{route.name === 'Home' &&
<Text style={{color: "#fff"}}>I am the AddShoppingListForm</Text>
}
</View>
)
}
ShoppingListItemForm:
function AddShoppingListItemForm() {
const route = useRoute();
console.log('AddShoppingListLitem >>', route.name);
return (
<View>
{route.name === 'List' &&
<Text>I am the ShoppingListItemForm</Text>
}
</View>
)
}
Here are example images so you can see what I mean:

I have fixed the issue I was having by removing the isVisible from Redux and rather using useState to just store the isVisible in that specific screen instead of across the app.

Related

Modal not updating to new item in array,firebase/react native state

my current issue with my react native app is that when a user wants to open a lesson (from the lessons array with each object being a lesson with a title,description,img url etc)to make it bigger through a modal, its state does not update. What i Mean by this is that the books title,description,and other attributes won't change if you press on a new lesson. What would be the solution to this?
export default function Learn() {
const [modalVisible, setModalVisible] = useState(false);
const [lessons,setLessons] = useState()
useEffect(() => {
async function data() {
try {
let todos = []
const querySnapshot = await getDocs(collection(db, "lessons"));
querySnapshot.forEach((doc) => {
todos.push(doc.data())
});
setLessons(todos)
console.log(lessons)
}
catch(E) {
alert(E)
}
}
data()
}, [])
return (
<View style={learnStyle.maincont}>
<View>
<Text style={{fontSize:28,marginTop:20}}>Courses</Text>
<ScrollView style={{paddingBottom:200}}>
{lessons && lessons.map((doc,key) =>
<>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
setModalVisible(!modalVisible);
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Image source={{
uri:doc.imgURL
}} style={{width:"100%",height:300}}/>
<Text style={{fontWeight:"700",fontSize:25}}>{doc.title}</Text>
<Text style={{fontWeight:"700",fontSize:16}}>{doc.desc}</Text>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={() => setModalVisible(!modalVisible)}
>
<Text style={styles.textStyle}>Hide Modal</Text>
</Pressable>
</View>
</View>
</Modal>
<LessonCard setModalVisible={setModalVisible} title={doc.title} desc={doc.desc} img1={doc.imgURL} modalVisible={modalVisible}/>
</>
)}
<View style={{height:600,width:"100%"}}></View>
</ScrollView>
</View>
</View>
)
}
What it looks like:
**image 1 is before you press the modal and the 2nd one is after
**the main issue though is that if you press cancel and press on another lesson the modal that opens has the the same state(title,imgurl,anddesc) as the first lesson and does not change.
The problem is that you create a lot of modal windows through the map function, I suggest making one window and passing the key as a parameter and using it to search for a specific array of data that is shown to the user (photo, title, etc.)
The problem is that all 3 Modals are controlled by the one state variable. So when the code sets modalVisible to true, all 3 modals are being opened at once.
You can fix this in a few ways, but a simple way would be to move the Modal and its state into the LessonCard component. This way each modal will have its own state that's only opened by its card. So the loop in Learn will just be:
{lessons && lessons.map((doc,key) => (
<LessonCard lesson={doc} key={key} />
)}
Adding to address question in comments
LessonCard should not accept setModalVisible or modalVisible props. The
const [modalVisible, setModalVisible] = useState(false);
should be inside LessonCard, not Learn. That way each Card/Modal pair will have its own state.
Additionally, although React wants you to pass the key into LessonCard in the map function, LessonCard should not actually use the key prop for anything. See https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys
So, the LessonCard declaration should just be something like
export default function LessonCard({lesson}) {

How to add View in react native during runtime?

I'm really confused on how to add (and delete) View and other such components during runtime,
for example in vanilla JavaScript you can use document.querySelector('query-here').appendChild(element);
but how do I achieve the same thing using react native? for example:
<Pressable onPress={()=>{addElement(element)}}>
<View>
//add elements here
</View>
I know how to achieve it directly like this:
<View>
{
[...Array(23)].map((el, index) => {
return(
<View key={index}>
<Text>added new element</Text>
</View>
)});
}
</View>
could someone please point me in the right direction?
#cakelover here how you can add item and remove items based on component's state.
import { Button } from 'react-native';
const [loader, setLoader] = React.useState(false); //donot show loader at initial
const showLoader = isShowLoader => { // based on this function you can add or remove single loader from UI
setLoader(isShowLoader);
}
return (
<View>
{loader && <LoaderComponent/>}
<Button
onPress={() => setLoader(!loader)}
title="Toggle Loader Component"
color="#841584"
/>
</View>
)
If you want to add or remove multiple same components like list you should use arrays of items for that.
I'm not sure but maybe you could try something like this
export default function App() {
const [num, setNum] = useState(() => 0);
const [renderTasks, setRenderTasks] = useState(()=>taskcreate(0));
function taskcreate()
{
let i=num;
setNum(i+1);
return(
<View>
{
[...Array(i)].map((el, index) => {
return (
<View key={index}>
<Text>hello there</Text>
</View>
)
})
}
</View>
)
}
return (
<View style={styles.container}>
<Pressable style={{ height: 50, width: 50, backgroundColor: 'orange' }} onPress={() => { setRenderTasks(taskcreate()) }}></Pressable>
{ renderTasks }
</View>
);
}

undefined function when passing navigation AND props to a react native component (basic disconnect button)

I'm a complete newbie and I'm trying to have a stack navigator only accessible if the user is logged, which works, but I can't manage to have a correctly working disconnect button.
I'm using a simple bool so far to grant access. The function used to disconnect, passed as a prop, is not found when I'm using the disconnect button.
App/Login screen :
const Stack = createStackNavigator();
export default function App() {
const [userIsLogged, setUserLog] = useState(false);
if (!userIsLogged) {
return <LoginScreen setUserLog={setUserLog}/>;
} else {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home">
{props => <Home {...props} setUserLog={setUserLog}/>}
</Stack.Screen>
<Stack.Screen name="Rooms" component={Rooms}/>
</Stack.Navigator>
</NavigationContainer>
);
}
}
Where the disconnect button is called:
const Home = ({navigation}, props) => {
return (
<View style={styles.container}>
<Text>Home</Text>
<TouchableOpacity onPress={() => props.setUserLog(false)}>
<Text>DISCONNECT</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate('Rooms')}>
<Text>ACCESS TO ROOMS</Text>
</TouchableOpacity>
<StatusBar style="auto" />
</View>
);
};
(Code has been simplified heavily to only highlight my issue)
Change
const Home = ({navigation}, props) => {
to
const Home = (props) => {
const {navigation} = props;
return /* REST OF CODE */
({navigation}, props) doesn't split the props into two groups, it defines two arguments to your function. Which means props will always be undefined since you never pass a second argument in this case.

React Native state and props, callbacks problem

I'm having problems with passing props and this whole stuff. Apparently i did something wrong maybe someone can tell me what should i fix there.
So these are states and callbacks that i use:
const [isVisible, setIsVisible] = useState(false);
const visibleCallBack = () => {
setIsVisible(false);
};
const [budynekText, setBudynekText] = useState("budynek");
const budynekCallBack = (propFromChild) => {
setBudynekText(propFromChild);
};
isVisible should decide whether to render or not a certain component:
<TouchableOpacity onPress={() => setIsVisible(true)}>
<View style={styles.budynekContainer}>
<Text style={styles.budynekTekst}>{budynekText}</Text>
{isVisible ?
<Rozwijana callBack={visibleCallBack} budCallBack={budynekCallBack}/>
: null}
</View>
Here I'm passing these callback functions as a props to "Rozwijana",
"Rozwijana" looks like that:
export const Rozwijana = ({callBackProp, budCallBackProp}) => {
return (
<View style={styles.rozwijanaPoz}>
<Pusty/>
<Box name={"budynek 1"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
<Box name={"budynek 2"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
<Box name={"budynek 3"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
</View>
);
}
Then i pass these callbacks down to "Box" component:
const Box = ({name, callBackProp, budCallBackProp}) => {
return (
<>
<TouchableHighlight onPress={() => {callBackProp(); budCallBackProp(name);}}>
<View style={styles.inputBox}>
<Text style={styles.testText}>{name}</Text>
</View>
</TouchableHighlight>
</>
);
}
The result is, that when i click first time at "budynek" button it works as expected, it opens my "list" but after that, when i try to click any of "Boxes" nothing happens. Is there something wrong? If so, how do i go about making this work? Thanks

Bind a object value into a component when TouchableOpacity Avatar pressed

My goal is to display the overlay bound to that button when the baby avatar is clicked.
Let me know how to fix it.
const OverlayForm = ({ baby }) => {
return <BabyProfile baby={baby} />;
}
return (
<React.Fragment>
<View style={styles.main}>
{Babies.map((baby: Baby, index) => (
<View>
<TouchableOpacity
style={styles.button}
onPress={() => {
toggleOverlay;
myValue = baby;
}}
key={index}
>
<AvatarData baby={baby} />
</TouchableOpacity>
</View>
))}
<Overlay isVisible={visible} onBackdropPress={toggleOverlay}>
{OverlayForm(myValue)}
</Overlay>
</View>
</React.Fragment>
);
}
I solved as this by Quentin Grisel's advice.
You should handle the overlay inside your Baby avatar component and simply change its state to display/hide the overlay.
export default function App() {
return <BabyAvatar overlay={false} />;
}
const BabyAvatar = () => {
const [isOverlayed, setIsOverlayed] = React.useState(false);
const displayOverlay = () => setIsOverlayed(!isOverlayed);
return (
<>
<Text>Baby overlay: {isOverlayed ? 'true':'false'}</Text>
<Button onPress={() => displayOverlay()} title="Change overlay" />
</>
)
}

Categories

Resources