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

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;

Related

mock value inside the component file

I would like to ask if I have the variable useState in the component which is used as a condition that determines the element inside it will appear or not. How to mock the variable? So that I can test the element inside the condition if the value is 'login'.
const [dataHistory,seDataHistory] = useState("")
const [data,setData] = useState("firstTimeLogin")
const func2 = () => {
getData({
item1: "0",
}).
then((res) => {
funct();
})
}
const funct = () =>
{setData("login")}
return
{data === "firstTimeLogin" && (
<div><button onClick="funct2()">next</button></div>
)}
{data === "login" && (
<div>flow login</div>
)}
Firstly, you need to add data-testid for your button
{data === "firstTimeLogin" && (
<div><button onClick="funct2" data-testid="next-button">next</button></div>
)}
You called onClick="funct2()" which is to trigger funct2 immediately after re-rendering, so I modified it to onClick="funct2".
Note that you also can use next content in the button for the event click but I'd prefer using data-testid is more fixed than next content.
In your test file, you should mock getData and call fireEvent.click to trigger funct2()
import { screen, fireEvent, render } from '#testing-library/react';
//'/getData' must be the actual path you're importing for `getData` usage
jest.mock('/getData', () => {
return {
getData: () => new Promise((resolve) => { resolve('') }) // it's mocked, so you can pass any data here
}
})
it('should call funct', async () => {
render(<YourComponent {...yourProps}/>)
const nextButton = await screen.findByTestId("next-button"); //matched with `data-testid` we defined earlier
fireEvent.click(nextButton); //trigger the event click on that button
const flowLoginElements = await screen.findByText('flow login'); //after state changes, you should have `flow login` in your content
expect(flowLoginElements).toBeTruthy();
})
You can create a button and onClick of the button call funct()

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.

React native launches first launch screen repeatedly

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

Query Doesn't Re-run after navigation

I have a screen where I am using a query like this:
export const AllFriends: React.FunctionComponent = () => {
const navigation = useNavigation();
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo')
},
});
Every time I visit this page from the home-page, I think the query re-runs as I always see the hellolog. Similarly, from this page, I visit another screen like this:
<Text
onPress={() => navigation.navigate('PendingRequests')}>
Pending Requests (
{data
? data.me.pendingUserRelationsRequestsReceived.totalCount
: ''}
)
</Text>
Every time I visit this screen, I see the hellooo from pending again. This screen looks like this:
export const ReceivedPendingRequests: React.FunctionComponent = () => {
const navigation = useNavigation();
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo from pending')
},
});
return (
<SafeAreaView style={styles.safeView}>
<Container style={styles.container}>
<Text
style={styles.backText}
onPress={() => navigation.navigate('AllFriends')}>
Zurück
</Text>
</Container>
</SafeAreaView>
);
};
Now the problem is that when I navigate back to AllFriends, the query should re-run and I should see the hello log again just like I see when I come from the Homepage to AllFriends. But I don't.
However, if I come back from AllFriends to PendingRequests, I see the log hellooo from pending again.
Edit:
useFocusEffect(()=>{
getMyProfile()
},[]);
const getMyProfile = () => {
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo')
},
//fetchPolicy: 'network-only',
});
}
You have to call refetch it will re-run the query for you. You can pass it to other screens aswell.
You can get it like this:
const {loading, data, refetch} = useQuery(Query, {})
Now in you useFocuseEffect just call this:
useFocusEffect(()=>{
React.useCallback(() => {
refetch();
}, [])
},[]);
when using navigation.navigate the screen doesn't "unmount", so it will not be reinitialized as fresh from the start as the screen does when you're using navigation.replace
Try using navigation.replace instead of navigation.navigate. I hope it helps, if not, let me know.

How to pass data back to previous screen in react native navigation v5?

I just updated to react native navigation version 5. Now I am trying to send data back to previous screen on goBack() call.
I push next view with
const onSelectCountry = item => {
console.log(item);
};
navigation.navigate('SelectionScreen', {
onSelect: onSelectCountry});
And making move back after selecting item from FlatList with call:
function onSelectedItem(item) {
route.params.onSelect(item);
navigation.goBack();
}
But by sending function over with params I get a warning: Non-serializable valuse were found in the navigation state...
Can someone please tell me correct way to do this.
heres is an implementaion
scereen A
const Screen1 = ({navigation, route}) => {
const [item, setItem] = useState(null);
useEffect(() => {
navigation.addListener('focus', () => {
console.log(route.params)
})
}, [])
const onPress = () => {
navigation.navigate('Screen2', {onReturn: (item) => {
setItem(item)
}})
}
return (
// Components
)
}
Screen2:
const Screen2 = ({navigation, route}) => {
useEffect(() => {
navigation.addListener('focus', () => {
console.log(route.params)
})
}, [])
// back Press
const onPress = () => {
route.params.onReturn(item);
navigation.goBack()
}
return (
// Components
)
}
navigation send data to screens.
onPress={() => {
// Pass params back to home screen
navigation.navigate('Home', { post: postText });
follow official documentation React Native
I visited this post because I wanted to use the same common component in 2 stacks. How to know how to go back and pass params?
I solved it by passing first a parameter to go there, which will identify where the component is accessed from.
Then, instead of using goBack(), which doesn't accept parameters, I navigate to the previous route and pass the parameter.
//in Stack1
navigation.navigate('commonComponent', isStack1: true)
//in Stack2
navigation.navigate('commonComponent', isStack1: false)
//in CommonComponent, instead of goback(), use navivation.navigate
function CommonComponent({ navigation, route }) {
const {isStack1} = route.params
const customRoute = isStack1 ? 'routeNameInStack1' : 'routeNameInStack2'
return (
<Button title="Go back" onPress={() => navigation.navigate('customRoute', {myParams: 'blabla...') />
)
}

Categories

Resources