Proper way to navigate with React Native, Redux, and Navigator - ios

I have a React Native app using the Redux framework and I'm using the Navigator component to handle navigation. I've had a little bit of trouble getting the navigation working and I'm not able to find any good examples of how to do it correctly so I'm looking for some help and clarification.
Here's the gist of what I currently have, which is working but I don't know if I'm doing it right:
Root Component
...
renderScene(route, navigator) {
console.log('RENDER SCENE', route);
const Component = route.component;
return (
<Component navigator={navigator} route={route} {...this.props} />
)
}
render() {
return (
<Navigator
renderScene={(route, nav) => this.renderScene(route, nav)}
initialRoute={{ name: 'Signin', component: Signin }} />
)
}
Signin Component
...
componentWillReceiveProps(nextProps) {
if (!this.props.signedIn && nextProps.signedIn) {
console.log('PUSHING TO MAIN');
nextProps.navigator.push({ name: 'Main', component: Main });
}
}
Questions:
1: My first thought here is that I should probably move the navigator.push code into an action. However is componentWillReceiveProps the right place to call the action? When the Signin component is loaded it fires an action to sign in the user if they already have an active session. By default they are not signed in so when the next props come through I check if it changed and then push to Main.
2: In my console log immediately after 'PUSH TO MAIN' is logged I see two 'RENDER SCENE' logs:
[info] 'RENDER SCENE', { name: 'Signin', component: [Function: Signin] } (EXPECTED)
[info] 'PUSHING TO MAIN'
[info] 'RENDER SCENE', { name: 'Signin', component: [Function: Signin] } (WHY?)
[info] 'RENDER SCENE', { name: 'Main', component: [Function: Main] }
I'm curious as to why RENDER SCENE is called twice (the first one being the Signin component) if I'm only pushing the Main component.
Also originally in the componentWillReceiveProps method I was only checking:
if (nextProps.signedIn) {
nextProps.navigator.push({ name: 'Main', component: Main });
}
but this caused the Main component to be pushed twice.

NOTE: The GitHub link below is now deprecated, in the comments the
author suggested react-native-router-flux which is by the same
author.
I've just added support for Redux, with my new component https://github.com/aksonov/react-native-redux-router with makes navigation pretty easy, like calling Actions.login

This is not a redux implementation but for routing I found react-native-router-flux to be really useful.
You can do things like call
Actions.login
to navigate to the login view. The routes are defined in your index file and have optional schemas to define navbar elements.
https://github.com/aksonov/react-native-router-flux

1) Yes, move it to method, componentWillReceiveProps is probably not correct. It’s difficult to refactor that part for you as I would not have that logic from that method on that component, i.e. the signinComp should receive state of whether it has an auth session or not (and not figure it out for itself). It leads to it getting processed for no reason; since if he is logged in you redirect. I would personally do that check in renderScene, since the nav gets passed down you can just make a push/pop on child components and handle all your logic in one renderScene.
2) See the navigation stack like a deck of cards. When you set the scene it is one card, but when you push it adds another card to the pile. So when you push and have two cards, it checks to make sure that all the cards are face up and rendered so that when you press back or pop it moves back to a completed card or scene. Imagine pushing 5 scenes immediately onto the stack. So when you change the stack it checks to ensure everthing is rendered and ready for that back button. Personally I don’t find that optimal (but it has to since you can pass different properties with each scene that you push).
You can probably change this line to:
renderScene={this.renderScene}

Question1:
I recommend you process navigator logic in shouldComponentUpdate method, like below,
shouldComponentUpdate(nextProps, nextState){
if(nextProps.isLoggedIn != this.props.isLoggedIn && nextProps.isLoggedIn === true){
//will redirect
// do redirect option
return false;
}
return true;
}
The Question 2:
I think this is the bug of react-native.

Related

Playwright - not working with zoomed out site

our team decided to zoom out the whole site. So they did this:
This is breaking my PW tests while clicking on the button.
I get this in the inspector:
selector resolved to visible <button id="add-to-cart-btn" data-partid="04-0001" data-…>ADD TO CART</button>
attempting click action
waiting for element to be visible, enabled and stable
element is visible, enabled and stable
scrolling into view if needed
done scrolling
element is outside of the viewport
I found this is an issue in PW https://github.com/microsoft/playwright/issues/2768
My question is: how can I bypass this in the most efficient way?
Is there a way to override a playwright function that sets the initial loading of the page and set my zoom there?
Since if I do it via JavaScript, then I have to do it every time my page reloads, and that can be really tedious and error-prone in the tests.
This is what I have now, but it is really a hack a like solution:
async removeZoomOutClassFromBodyElement() {
await this.#page.evaluate(() => {
const body = document.querySelector('body');
if (body) {
// Removes the class only if it exists on the body tag
body.classList.remove('zoom-out');
} else {
throw Error(ErrorMessage.BODY_NOT_FOUND);
}
});
}
Can you please advise what would be the best approach here?
Thanks!

Testing-library unable to find rc-menu

I'm trying to implement integration tests on our React frontend which uses Ant design. Whenever we create a table, we add an action column in which we have a Menu and Menu Items to perform certain actions.
However, I seem unable to find the correct button in the menu item when using react-testing-library. The menu Ant design uses is rc-menu and I believe it renders outside of the rendered component.
Reading the testing-library documentation, I've tried using baseElement and queryByRole to get the correct DOM element, but it doesn't find anything.
Here's an example of what I'm trying to do. It's all async since the table has to wait on certain data before it gets filled in, just an FYI.
Tried it on codesandbox
const row = await screen.findByRole('row', {
name: /test/i
})
const menu = await within(row).findByRole('menu')
fireEvent.mouseDown(menu)
expect(queryByRole('button', { name: /delete/i })).toBeDisabled()
the menu being opened with the delete action as menu item
I had a same issue testing antd + rc-menu component. Looks like the issue is related to delayed popup rendering. Here is an example how I solved it:
jest.useFakeTimers();
const { queryByTestId, getByText } = renderMyComponent();
const nav = await waitFor(() => getByText("My Menu item text"));
act(() => {
fireEvent.mouseEnter(nav);
jest.runAllTimers(); // ! <- this was a trick !
});
jest.useRealTimers();
expect(queryByTestId("submenu-item-test-id")).toBeTruthy();

Trigger AngularComponent-constructor on startup with PreLoadingStrategy

The goal: trigger a component residing in a module, so my subscription in the ctor of the component is activated.
I'm using PreloadAllModules as a preloadingStrategy. But it's not happening.
I need to subscribe to some events in de constructor of my FriendsComponent.
the setup is like this:
FriendsComponent is shown in the template of the SocialComponent, which is part of the SocialModule.
social.component.html
<div>
<friends-component></friends-component>
</div>
the SharedModule declares the FriendsComponent.
AppModule imports SharedModule,
RouterModule for AppModule is like this:
{
path: 'social',
component: SocialModule,
children: [
{
path: 'friends',
component: FriendsComponent
}
]
},
I think the problem is because the FriendsComponent is not part of a router-outlet?
Can it be done without a router-outlet?
If a module would be pre- or eager loaded, would it automatically trigger the constructors (and the subscription)?
Is the issue with my preloading strategy?
I have tried adding: data:{preload:true} to the paths declared in routermodule.
Everything works fine, when the user activates the SocialModule (for instance by clicking on a button with a routerLink to social/friends), but I want it activated on startup (just not shown on any html)
I'm working with Angular Ivy, but think I'm still missing the points. Any help is appreciated
You need to handle your initial subscriptions in a service and have the component subscribe to that service. You won't need to touch the routes. It what services are for.
You subscribe to the value you need in your FriendService and have FriendComponent subscribe to your FriendService.

Angular 7 - Multiple outlets : Error: Cannot activate an already activated outlet

Here is the issue that I'm encountering with Angular 7 :
I have two outlets : the main app router outlet, and a secondary outlet named 'administration'.
When I want to navigate through any administration link at start, it works fine. But next time, when I try to navigate, angular throws this error message :
Error: Cannot activate an already activated outlet
So, can someone explain me why ? I haven't found any solution on forums...
Here is a stackblitz : https://stackblitz.com/edit/angular-osnnd4
Thank you all everybody :)
The problem occurs when lazyloading child routes. You have to manually deactivate the outlet everytime you change a route.
I have modified your AdministrationComponent to workaround as follow. It should be able to work for now, until Angular have a way to solve the problem.
import { Component, OnInit, ViewChild } from '#angular/core';
import { RouterOutlet, Router, ActivationStart } from '#angular/router';
#Component({
selector: 'app-administration',
templateUrl: './administration.component.html',
styleUrls: ['./administration.component.css']
})
export class AdministrationComponent implements OnInit {
#ViewChild(RouterOutlet) outlet: RouterOutlet;
constructor(
private router: Router
) { }
ngOnInit(): void {
this.router.events.subscribe(e => {
if (e instanceof ActivationStart && e.snapshot.outlet === "administration")
this.outlet.deactivate();
});
}
}
This might be silly, but for anyone looking for a solution to this:
Make sure that in there's no undefined variable in any of the inner or children outlets.
I fixed this in my project and everything is back to normal.
My problem:
Basically my named outlet wasnt deactivating, in my case I would use it for a bunch of different modals, and while manually deactivating my modal outlet would work, it didnt work when loading a different path and the same component into the outlet so I could not recycle my componet, in my case the modal component.
So as pointed by #Andreas in here (https://github.com/angular/angular/issues/20694#issuecomment-595707956). I didn't have any asigned components for an empty path, so I am guessing it couldnt deactivate the outlet somehow because of that, so I just asigned an emptyComponent for path: "".
The first way: to fix this I will be using an empty component for "" or non truthy routes
app-router-module.ts
...
{
outlet: "modal",
path: "",
component: EmptyComponent,
},
...
Another way: If you want, you can do this instead when closing a view
model.component.ts
this.router.navigate([
{
outlets: {
modal: null,
},
},
]);
either way the router deactivates, better yet I think you should use the second example as it deactivates and destroys the view. in my case I had a json with non truthy routes so I just had to map those to null values.
Check thoroughly if you have imported a lazy-loaded module in its parent module or some other module irrespective of the routing. If so, remove its import from there.
That was it in my case.
My problem was that I have a login guard which performs a redirect via this.router.navigate, and the guard was returning true after redirects.
When I changed the code to return false in the case of a redirect, the issue has been gone.

In Geb, what is the difference between displayed and present?

I am writing functional tests and dealing with a modal window that fades in and out.
What is the difference between displayed and present?
For example I have:
settingsModule.container.displayed and settingsModule.container.present
where settingsModule represents my modal window.
When testing my modal window (the modal from Twitter's bootstrap), I usually do this:
def "should do ... "() {
setup:
topMenu.openSettingsModal()
expect:
settingsModule.timeZone.value() == "Asia/Hong_Kong"
cleanup:
settingsModule.closeSettingsModal()
}
def "should save the time zone"() {
setup:
topMenu.openSettingsModal()
settingsModule.timeZone = "Japan"
when:
settingsModule.saveSettings()
then:
settingsModule.alertSuccess.size() == 1
settingsModule.alertSuccess.text() == "Settings updated"
when:
settingsModule.saveSettings()
then:
settingsModule.alertSuccess.size() == 1
cleanup:
settingsModule.closeSettingsModal()
}
and on and on. In my modules, I have:
void openSettingsModal() {
username.click()
settingsLink.click()
}
void closeSettingsModal() {
form.cancel().click()
}
I always get a complain: "Element must be displayed to click".
In my openSettingsModal and closeSettingsModal, i tried many combination of waitFor with time interval and using present or not ... Can't figure it out.
Any pointers would be highly appreciated. Thanks!
I think the main difference is that present would check that there is an element in your DOM, whereas displayed checks on the visibility of this element.
Remember that webdriver simulates the actual experience of an user using and clicking the website using a mouse, so if the element is not visible to them, they will not be able to click on it.
I wonder if your issue has to do with settingsLink not being in the DOM when the page is first loaded. If you are waiting for a dialog to popup and a link to live in this dialog, then you probably want to set something like
content{
settingsLink( required: false ) { $( '...' }
settingsModal( required: false ) { $( '#modalDialog' ) }
}
your waitfor should look something like
username.click()
waitFor{ settingsModal.displayed }
settingsLink.click()
I would stick with the book of geb conventions and just use displayed all the time.
Geb Manual - Determining visibility
Thanks for your reply. I was actually able to resolve my issue.
The problem was that the modal window had an animation of 500ms. Opening and closing the window several times in my tests made them succeed/fail inconsistently.
What I ended up doing is hooking the the "shown" event provided by the plugin. I ended up adding a "shown" class to the modal and check for it every 100ms during 1s.
void openSettingsModal() {
username.click()
settingsLink.click()
waitFor (1, 0.1) { $("#settingsModal", class: "shown").size() == 1 }
}
void closeSettingsModal() {
form.cancel().click()
waitFor (1, 0.1) { $("#settingsModal", class: "shown").size() == 0 }
}
As a side note, the tests were failing in Chrome and Firefox BUT were passing in IE!! I am guessing that because IE 8 doesn't support animations that my tests were passing.
It is all good now.
I hope it will help someone someday!
Where we can use displayed?
If a particular element you are removing or deleting, if it is still there in DOM and not displayed in page, you can use assert thatelement.displayed == false which will make sure that element is not displayed in the page (but still it is present in DOM)
Where we can use present?
In the same example as above, after removing,if the element is not found at the DOM ,you should use present for verification
assert thatelement.present == false
Hope you understand....
Adding to the above, present takes more time in script execution

Resources