React native launches first launch screen repeatedly - javascript

I have created app in react native with a screen that will appear only on first app entry.
Other times it just load it AsyncStorage value to check if the app has been launched before.
The screen does appear, but on every app launch. I want it to appear only on first launch.
As far as I know the following code should work, but something collapsing here with the async probably..
export default function App() {
const [selected, setSelected] = useState(false);
const verifyHasLaunched = async () => {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
if(hasLaunched){
setSelected(true);
}
setSelected(false);
} catch (err) {
setSelected(false);
}
};
useEffect(() => verifyHasLaunched, []);
//The rest of the code - irrelevant
//By using Selected state I show different screen and not first launch screen.
CheckIfFirstLaunch function
//Save the language as AsyncStorage for other times the user will open the app
async function setAppLaunched(en) {
AsyncStorage.setItem(HAS_LAUNCHED, "true");
AsyncStorage.setItem(en ? ENGLISH : HEBREW, "true");
if(await AsyncStorage.getItem(HEBREW)){
i18n.locale = "he";
I18nManager.forceRTL(true);
}
else{
i18n.locale = "en";
I18nManager.forceRTL(false);
}
}
//If first launch show this screen
function CheckIfFirstLaunch({ onSelect }) {
const navigation = useNavigation();
const selectLaunched = (value) => {
if(value){
i18n.locale = "en";
I18nManager.forceRTL(false);
}
else{
i18n.locale = "he";
I18nManager.forceRTL(true);
}
setAppLaunched(value);
onSelect();
navigation.navigate('Login');
};
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => selectLaunched(false)} title="Hebrew"/>
<Button onPress={() => selectLaunched(true)} title="English"/>
</View>
);
}
Expected behavior
CheckIfFirstLaunch() should appear only once on first launch.
Current behavior
CheckIfFirstLaunch() appears on every launch.
How can I create React Native first launch screen correctly?

if(hasLaunched){
setSelected(true);
} else {
setSelected(false);
}

Related

Expo Barcode Scanner stop working after navigate screen

So, I'm facing a problem when I navigate to my scanner screen and go back the previous screen, then navigate again to my scanner screen, barcode scanner does not working. even console logs does not working. I have to clear cashe and all data from expo app in order to work scanner screen again. I really don't know what causing the porblem but highly suspicious about Navigation. Can anyone help me pls?
Im adding my Scanner Screen right below.
import React, { useState, useEffect } from "react";
import {
Text,
View,
FlatList,
Button,
Modal,
Pressable,
Alert,
StyleSheet,
} from "react-native";
import { BarCodeScanner } from "expo-barcode-scanner";
import axios from "axios";
import { localIP, ngrokServer } from "../constants";
import allStyles from "../components/molecules/Styles";
const styles = allStyles;
export default function ScannerScreen({ navigation }) {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
useEffect(() => {
setReset(false);
});
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
}, []);
const handleBarCodeScanned = async ({ type, data }) => {
setScanned(true);
console.log("Data: ", data);
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.scannerScreenContainer}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
{scanned && reset && (
<Button title={"Tap to Scan Again"} onPress={() => setScanned(false)} />
)}
</View>
);
}
I'm using axios.post and thought maybe that was the cause of problem but when I removed that code block and run again it doesn't scan the QR code.
I had the same issue and I fixed it by adding a listener for focus events (emitted when the screen comes into focus).
This is what it looks like:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something - for example: reset states, ask for camera permission
setScanned(false);
setHasPermission(false);
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);
Try using this code and see if it works.
Source: https://reactnavigation.org/docs/navigation-events/#navigationaddlistener

Component not updating after fireEvent.click

I'm new to Jest and the testing library. I have a NavbarContainer component that renders one button or another depending on a variable (menuOpen) that it's being changed with a function. I have already checked that the variable changes its value after the fireEvent, however, it seems like the component it's not updating. What am I doing wrong?
Here there is my component
export const NavbarContainer = ({functionality, menuOpen, activeLink}) => {
return (
<div className="navbar-container">
{ menuOpen && <Navbar functionality={functionality} activeLink={activeLink}/> }
{ menuOpen && <Button text="navbar.back" functionality={functionality}></Button> }
{ !menuOpen && <Button text="navbar.menu" functionality={functionality} modificator="back"></Button> }
</div>
);
};
Here is the button
export const Button = ({ text, type = "button", functionality, disabled = false}) => {
return (
<button onClick={functionality} type={type} disabled={disabled}>{i18n.t(text)}</button>
);
};
Here are the values and functions I am passing to the NavbarContainer component
const [menuOpen, setMenuOpen] = useState(false);
const [activeLink, setActiveLink] = useState("");
const openMenu = (link) => {
setMenuOpen(!menuOpen);
setActiveLink(link.toString());
};
Here are my tests
describe("NavbarContainer", () => {
i18n.changeLanguage('cimode');
let component;
let menuOpen = true;
const openMenu = () => {
menuOpen = !menuOpen;
};
beforeEach(() => {
component = render(
<BrowserRouter basename="/">
<NavbarContainer menuOpen={menuOpen} functionality={openMenu} activeLink=""/>
</BrowserRouter>
);
});
it("after click the menu button is shown", async () => {
const backButton = component.queryByText("navbar.back");
await fireEvent.click(backButton);
const menuButton = await component.queryByText("navbar.menu");
expect(menuButton).toBeInTheDocument();
});
});
Here is the error I'm getting
expect(received).toBeInTheDocument()
the received value must be an HTMLElement or an SVG element.
Received has value: null
Since it might take a few seconds for your navbar.menu to appear you after your click, you need to use findBy to try and select your item. This adds a timeout of 5 seconds to try and find the item. If it still hasn't appeared after 5 seconds, then there is probably something else going on with your code.
If you want your test to be async, then I would recommend that your first call also be a findBy rather than a queryBy since queryBy isn't an asynchronous call.
it("after click the menu button is shown", async () => {
const backButton = await component.findyByText("navbar.back");
await fireEvent.click(backButton);
const menuButton = await component.findByText("navbar.menu");
expect(menuButton).toBeInTheDocument();
});
You also probably don't need that last expect call because if it can't find the navbar.menu item via the previous call, then it would fail anyway. Same if it finds it.

How to make a Modal Toggle when using Route and Navigation?

I have four pages, "ReadyScreen" "RunningScreen" "ListScreen" and "CheckScreen"
To start a run, the user navigates from the "ReadyScreen" to the "ListScreen" to the "CheckScreen" and lastly to the "RunningScreen"
ReadyScreen -> ListScreen -> CheckScreen -> RunningScreen
At the end of the user's run, they are navigated back to the "ReadyScreen" from the "RunningScreen"
I want a Modal to toggle with their running info, when the user navigates from the RunningScreen to the ReadyScreen. I have been told I can do this using a route, but I have been having trouble properly setting it up. Here is what I have so far:
function RunningScreen({navigation, route}){
const checkFinish = () => {
onPress: () => navigation.navigate('ReadyScreen, {
didComeFromRunningScreen: true
})
}
...
useFocusEffect(
React.useCallback(()=>{
....
if(didComeFromRunningScreen){
toggleModal()
}
}, [])
}
I am also stuck on how to toggle this in the ReadyScreen
If you are going back to the previous screen you cant pass params like that. or if you forcefully push the previous screen to open again it will create a new stack. You have to pass a callback function from the ready screen to the running screen and in the running screen when your check the finish button press you will call your callback function.
Here is the code example:
ReadyScreen
const ReadyScreen = ({ navigation }) => {
const toggleModal = () => {
// Your modal method to open modal
};
// callback func
const openModalCallBack = () => {
toggleModal();
};
const gotoRunningScreen = () => {
navigation.navigate("RunningScreen", { openModalCB: openModalCallBack }); // passing callback func
};
return (
<View>
<Button onPress={gotoRunningScreen} />
</View>
);
};
export default ReadyScreen;
RunningScreen
const RunningScreen = ({ navigation, route }) => {
const checkFinish = () => {
const { openModalCB } = route?.params;
openModalCB(); // Here you calling the callback func
navigation.goBack(); // or pop()
};
return (
<View>
<Button onPress={checkFinish} />
</View>
);
};
export default RunningScreen;

On page refresh, how does the session storage work?

i want to show a dialog on clicking add button. and not display it for the session (using sessionstorage) when hide button is clicked.
below is my code,
function Parent() {
const DialogRequest = useDialogRequest();
const onAdd = () => {
DialogRequest(true);
}
render = () => {
return (
<button onClick={onAdd}>Add</button>
);
}
}
function Dialog(onHide) {
return(
{showDialog?
hide : null
}
);
}
const dialogRequestContext = React.createContext<ContextProps>({});
export const DialogRequestProvider = ({ children }: any) => {
const [showDialog,setShowDialog] = React.useState(false);
const onHide = () => {
setDialogOpen(false);
};
const setDialogOpen = (open: boolean) => {
if (open) {
const sessionDialogClosed = sessionStorage.getItem('dialog');
if (sessionDialogClosed !== 'closed') {
setShowDialog(open);
sessionStorage.setItem('dialog', 'closed');
}
} else {
setShowDialog(open);
}
};
return (
<DialogContext.Provider
value={{ setDialogOpen }}
>
{children}
<Dialog onHide={onHide}
showDialog={showDialog}
/>
</DialogContext.Provider>
);
};
export function useDialogRequest() {
const dialogRequestContext = React.useContext(
dialogRequestContext
);
return function DialogRequest(open: boolean) {
if (dialogRequestContext &&
dialogRequestContext.setDialogOpen
) {
dialogRequestContext.setDialogOpen(open);
}
};
}
This code works.but when page reloads then the dialog is not opened again even though hide message is not clicked before page reload.
i have tried to console log the value of dialog like below after page reload.
if (open) {
const sessionDialogClosed = sessionStorage.getItem('dialog');
console.log('sessiondialogclosed', sessionDialogClosed); //this gives closed
if (sessionDialogClosed !== 'closed') {
setShowDialog(open);
sessionStorage.setItem('dialog', 'closed');
}
} else {
setShowDialog(open);
}
Even though i dint click the hide button before page reload.....this gives me the ouput closed for the sessionstorage item dialog.
Not sure if this is the way it should behave. If not could someone help me fix this to get it right.
thanks.
As expressed in my comment:
Values in useState persist for the life of the page. They won't be kept across a page refresh. If you need to do that, you might consider creating a hook that wraps useState which persists the values into localStorage, and then retrieves the initial value on page load.
This is a basic example of such a hook.
function usePersistentState(defaultValue, key) {
let initVal;
try {
initVal = JSON.parse(localStorage.getItem(key));
} catch {}
if (initVal === undefined) initVal = defaultValue;
const [state, setState] = useState(initVal);
function persistState(value) {
if (typeof value === "function") value = value(state);
localStorage.setItem(key, JSON.stringify(value));
setState(value);
}
return [state, persistState];
}
Here's a sample of how it works. If you refresh the page after toggling the value, it will restore the previous state from localStorage.
There are a few caveats about how it works. If you've got a really complex state you shouldn't use this, you should do basically the same thing but with useReducer instead. But in a simple example like this should be fine.

Custom hook function not being called in React

I am trying to call my fetchPlants function, but I cannot see to figure out why it is NOT being called.
/screens/RecipeScreen.js
import usePlants from '../hooks/usePlants';
// Call our custom hook
const [fetchPlants, plantResults] = usePlants();
// ...other code...
<RecipeSearch
recipeSearch={recipeSearch}
onRecipeSearchChange={setRecipeSearch}
onRecipeSearchSubmit={() => fetchPlants(recipeSearch)}
/>
/components/RecipeSearch.js
const RecipeSearch = ({
onRecipeSearchChange,
onRecipeSearchSubmit,
recipeSearch,
}) => {
return (
console.log(recipeSearch); // This prints out nicely...
<View>
<View>
<TextInput
placeholder='Find a plant...'
value={recipeSearch}
onChangeText={onRecipeSearchChange}
onEndEditing={onRecipeSearchSubmit}
/>
</View>
</View>
);
};
/hooks/usePlants.js
import { useState, useEffect } from 'react';
import plantsApi from '../api/plants';
export default () => {
const [plantResults, setPlantResults] = useState([]);
const fetchPlants = async searchTerm => {
console.log('searchTerm... HERE IS THE QUERY', searchTerm); // this never gets hit
try {
const response = await plantsApi.get('').then(response => {
console.log(response);
setPlantResults(response);
});
} catch (err) {
console.log(err);
}
};
return [fetchPlants, plantResults];
};
I initially thought that maybe I was calling fetchPlants() too early (before recipeSearch had any state), but I don't think so, because it is still able to console.log(searchRecipe) properly.
Update it was working ALL along. When I was testing it with the iOS simulator I needed to hit the "ENTER" key on my computer because I am using the React Native onEndEditing prop.

Categories

Resources