React Native - Navigate after an async action - ios

I'm developing a mobile app with React Native and Redux and I'm facing a software design problem.
I want to call a REST API (async operation) for login and navigate to main view if that operation was successful.
I'm using redux and thunk so I already have the async actions implemented so my main doubt is: Where should I put the logic to navigate to main view?
Can I access the navigator object directly from an action and perform the navigation there?
Should I do that in the Login Component? (As I'm doing it already - check the code below).
componentWillReceiveProps(nextProps){
if(nextProps.errorLoginMsg){
Alert.alert("Login Failed", nextProps.errorLoginMsg);
}
else if(!nextProps.user.isNull()){
this.props.navigator.replace({name: 'main'});
}
}
I'm not confident of having that logic in the component. Does not seem a good practice. Are there any other way to do this?
Thanks

Here is the code how I do it:
const resetAction = NavigationActions.reset( {
index : 0,
actions: [
NavigationActions.navigate( { routeName: 'Home' } )
]
} );
this.props.requestDeleteEvent( {
id: eventID
} ).then( () => {
this.props.navigation.dispatch( resetAction );
} );
And inside function requestDeleteEvent:
export function requestDeleteEvent(requestData) {
const id = requestData.id;
return (dispatch, getState) => {
return fetch(Config.event + id, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
})
.then((response) => getResponseJson(response))
.then((responseJson) => {
if (responseJson.isSuccess) {
return dispatch(deleteEvent(id));
}
else {
return dispatch(triggerToast({type: 'alert', content: ERROR}));
}
}
);
}
}

This is one of the hardest problems in react native with the current Navigator API. I would suggest having a route store which holds the current route and have the component which includes the Navigator connected to this store and having a navigation triggered on componentWillReceiveProps.

Related

My Vue PWA app shows white blank page on iOS

I am currently working one a Vuejs PWA app (using Vue CLI 3). My app works fine on Android, Windows, and macOS, but it only shows a white blank page on ios. More specifically, when I use an iPhone and access my app with Safari or Chrome, it all shows a white page. When I add my app to the home screen of the iPhone, when I open it up, it still shows a white page.
this is link to my app.
White blank screen
I have tried many workarounds here but it not work.
here are some parts of my code:
vue.config.js
module.exports = {
transpileDependencies: ['vuetify'],
pwa: {
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: 'src/config/firebase-messaging-sw.js',
exclude: [/\.map$/, /_redirects/],
},
manifestOptions: {
start_url: 'index.html',
},
name: 'AppName',
appleMobileWebAppCapable: 'yes',
},
};
router
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "RenterMainView",
component: RenterView,
children: [
{
path: "index.html",
name: "Home",
component: Home,
meta: { guest: true },
alias: ""
},
{
path: "detail/:typeId",
name: "HostelDetail",
component: HostelDetail,
meta: { guest: true }
}
]
}
]
});
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (window.$cookies.get('jwt') === null) {
// not logged in
next({
name: 'Login',
params: { nextUrl: to.path, preUrl: from.path },
});
} else {
// logged in
const role = window.$cookies.get('role');
if (to.matched.some((record) => record.meta.is_vendor)) {
if (role === 'vendors') {
next();
} else {
next(from.path);
}
} else if (to.matched.some((record) => record.meta.is_admin)) {
if (role === 'admins') {
next();
} else {
next(from.path);
}
} else if (to.matched.some((record) => record.meta.is_renter)) {
if (role === 'renters') {
next();
} else {
next(from.path);
}
} else {
next();
}
}
} else if (to.matched.some((record) => record.meta.guest)) {
// not require authen
next();
} else {
// not require authen
next();
}
});
Try changing the start_url
I found that on
Chrome Windows
Chrome MacOS
Android
I was OK to use quite a wide variety of start_url values, such as the "index.html" that you have, or "/index.html", etc.
However on iOS, I had to use
start_url: "."
The other values were fine on the other platforms, but gave a blank white screen on iOS.
Try creating a blank PWA with the Vue CLI
Does that work correctly on iOS?
Then step by step change it to contain your app.
Find out where it breaks.
That's how I found the start_url issue.
I hope that this could help someone. In this project, I make an PWA app using Vue, and I use Firebase Cloud Messagging to send notification from server to client. Unfortunaly, due to some restrictions on iOS, FCM doesn't work on it, that is why the application show a white page on iOS. So, the solution is to disable FCM on iOS
if (firebase.messaging.isSupported()) { // your code go here }
using above code to disable FCM on firebase service worker file

Is it possible to open a browser window from a aservice worker?

I would like to open a new browser window from inside a service worker, depending on the information in the http request that the service worker has intercepted, for example:
// inside service worker:
self.addEventListener('fetch', function (event) {
if (event.request.url.indexOf('trigger=') > -1) {
// OPEN A NEW BROWSER WINDOW...
event.respondWith(
new Response(JSON.stringify({ triggered: event.request.url }), {
headers: { 'Content-Type': 'application/json' },
}),
)
} else {
event.respondWith(
fetch(event.request).then(function (response) {
return response
}),
)
}
})
From what I have read, it seems that the only way to do this, is to click on a notification that is displayed as a result of having received a push message. That is to, you register a "notification click event" listener, which will allow you to pop open a new window? see here for more info.
Does anyone know if there is a way to do this without the need for any sort of push notification?
I figured it out, it's fairly simple actually:
// inside service worker:
self.addEventListener('notificationclick', e => {
e.notification.close()
clients.openWindow('http://localhost/')
})
self.addEventListener('fetch', function (event) {
if (event.request.url.indexOf('trigger=') > -1) {
self.registration.showNotification('Click here to open the app')
event.respondWith(
new Response(JSON.stringify({ triggered: event.request.url }), {
headers: { 'Content-Type': 'application/json' },
}),
)
} else {
event.respondWith(
fetch(event.request).then(function (response) {
return response
}),
)
}
})
You need to request permission to display notifications when registering the service worker of course, but otherwise it works.

axios-on-rails, how to request set of records

I'm creating an app in Rails with a ReactJS front-end. In my front-end I'm using the axios-on-rails yarn package to make all my requests to my Rails api back-end.
Heres what I'm trying to do: for the main page of the site I want to implement an infinite scroll feature. For that to work well I need to be able to request small sets of records as the page continues to scroll. The only way I know how to pass records to my front-end is using:
axios.get('/posts.json')
.then((response) => {
...
})
.catch(error => console.log(error));
This returns ALL posts though, which eventually will be thousands. I don't want that happening. So how do I modify this request so that I only get the first 20 records or so?
Answer Details
Okay so I took a second look at pagination as #Gagan Gupta suggested and after a few hours got it to work. Heres what I did.
yarn add react-infinite-scroll to get the component needed.
For my feed component I did...
import React from 'react';
import Post from './Post';
import { connect } from 'react-redux';
import { loadPosts } from '../actions/posts';
import InfiniteScroll from 'react-infinite-scroller';
import axios from 'axios-on-rails';
const node = document.getElementById('owc_feed_payload');
const numberOfPosts = JSON.parse(node.getAttribute('number_of_posts'));
class Feed extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: props.posts,
hasMoreItems: true,
page: 1
};
}
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
this.setState({ posts: this.props.posts, hasMoreItems: this.props.hasMoreItems });
}
}
loadMore = (page) => {
axios.get('/posts.json', {
params: { page: page }
})
.then((response) => {
console.log(response.data);
this.props.dispatch(loadPosts(response.data));
this.setState({ hasMoreItems: this.state.posts.length < numberOfPosts ? false : true, page: this.state.page + 1 });
})
.catch(error => console.log(error));
}
render() {
let items = [];
this.state.posts.map((post, index) => {
items.push(
< ... key={index}>
...
</...>
);
});
return (
<InfiniteScroll
pageStart={0}
loadMore={this.loadMore}
hasMore={this.state.hasMoreItems}
loader={<p>Loading...</p>}>
{ items }
</InfiniteScroll>
);
}
}
const mapStateToProps = (state) => {
return {
timestamp: state.timestampReducer,
posts: state.postsReducer
}
};
export default connect(mapStateToProps)(Feed);
I used redux to manage the state of my posts. Next I added gem 'kaminari' to my gem file and ran bundle installed then added this line to my controller's index action: #posts = Post.all.order(created_at: :desc).page params[:page] and this to my model: paginates_per 5.
Now it scrolls and loads as expected! Awesome.
The solution would be to use pagination.
Every request will be bring only a set of records you'll specify in the method.
you can perform using gems like will_paginate, kaminari & this is the new gem called as pagy and they claim that it's faster than the other two.
Just increment the page parameter in the url after every request till the last page and you'll get the output you need.
I'm glad my opinion helped you :)
Change your JS code to this:
axios.post('/posts.json', {
params: {
page: page
}
}).then((response) => {
console.log(response.data);
...
}).catch(error => console.log(error));
}
Take a look at console.log(response) after axios then method so you can see the array of objects returning from the server. After then you can set it with .length property of method like:
axios.get('/posts.json')
.then((response) => {
if(response.data.length > 20){
console.log(response.data.slice(0,20))
}
})
.catch(error => console.log(error));

Vue - watch url without VueRouter

Is it possible to react on URL changes in a Vue component, without including VueRouter?
In case VueRouter is present, we can watch $route, however, my application does not rely on VueRouter.
export default {
watch: { '$route': route => console.log(window.location.href) }
}
Before I used vue router, I did something like this...
data: function() {
return {
route: window.location.hash,
page: 'home'
}
},
watch: {
route: function(){
this.page = window.location.hash.split("#/")[1];
}
}

OAuth2 in electron application in current window

I'm trying to implement OAuth2 authentication in Angular 2 ( Electron ) application.
I achieve that on the way with a popup that is called after user click on 'Sign In' button.
In popup user types their credentials and allows the access and on confirm code is returned and I'm able to catch redirect request which I can't do without popup.
Here is implementation that works:
return Observable.create((observer: Observer<any>) => {
let authWindow = new electron.remote.BrowserWindow({ show: false, webPreferences: {
nodeIntegration: false
} });
authWindow.maximize();
const authUrl = AUTHORIZATION_WITH_PROOF_KEY_URL
+ `?client_id=${CLIENT_ID}&response_type=code&scope=api_search&`
+ `redirect_uri=${REDIRECT_URL}&code_challenge=${challenge}&code_challenge_method=S256`;
if (this.clearStorage) {
authWindow.webContents.session.clearStorageData({}, () => {
this.clearStorage = false;
authWindow.loadURL(authUrl);
authWindow.show();
});
} else {
authWindow.loadURL(authUrl);
authWindow.show();
}
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
const code = this.getCode(newUrl, authWindow);
if (!code) {
this.clearStorage = true;
return;
}
this.requestToken({
grant_type: 'authorization_code',
code: code,
code_verifier: verifier,
redirect_uri: REDIRECT_URL
})
.subscribe((response: { access_token: string, refresh_token: string }) => {
observer.next(response);
});
});
// Reset the authWindow on close
authWindow.on('close', () => {
authWindow = null;
});
});
and as you can see in above code I'm creating new BrowserWindow with:
new electron.remote.BrowserWindow({ show: false, webPreferences: {
nodeIntegration: false
} });
and with that approach I'm able to catch up redirect request with a block of code that starts with:
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
....
}
but I'm not able to solve this without popup ( modal ).
Here is my attempt:
return Observable.create((observer: Observer<any>) => {
let authWindow = electron.remote.getCurrentWindow();
const authUrl = AUTHORIZATION_WITH_PROOF_KEY_URL
+ `?client_id=${CLIENT_ID}&response_type=code&scope=api_search&`
+ `redirect_uri=${REDIRECT_URL}&code_challenge=${challenge}&code_challenge_method=S256`;
if (this.clearStorage) {
authWindow.webContents.session.clearStorageData({}, () => {
this.clearStorage = false;
authWindow.loadURL(authUrl);
});
} else {
authWindow.loadURL(authUrl);
}
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
debugger;
// this is not called, I'm not able to catch up redirect request
});
// Reset the authWindow on close
authWindow.on('close', () => {
authWindow = null;
});
});
With my approach I get login screen from remote URL in a current window, but the problem is that I'm not able to catch redirect request with ('did-get-redirect-request') event.
I also tried with 'will-navigate' and many others.
Although I don't have a direct answer I thought I'd point you to Google's AppAuth-JS libraries, which cover OAuth based usage for Electron Apps.
My company have used AppAuth libraries for the mobile case and they worked very well for us, so that we wrote less security code ourselves and avoided vulnerabilities.
There is also an Electron Code Sample.

Resources