React: add route with mandatory query string - ruby-on-rails

I'm working on a project that uses Rails and React, with react-router version 4.2.0 and react-router-redux 5.0.0-alpha.9. It's the first time I use React and I'm having trouble routing.
In my routes.jsx file I have the following path:
const routes = [
{ path: /\/events\/(\d)$/, action: () => <EventForm /> },
];
When I type http://localhost:3000/events/2 in my browser I get the content back.
I want to modify my route so this link won't be valid unless there's a userToken appended to it as a query string. (I know this is not the best security practice but it's valid for the purpose of this project)
For example, the following link http://localhost:3000/events/2 should not work, but the link http://localhost:3000/events/2?userToken=abc should work.
I tried these options but it didn't work:
{ path: /\/events\/(\d)\?userToken\=(\w)$/, action: () => <EventForm /> }
{ path: /\/events\/(\d)\?userToken=[\w]$/, action: () => <EventForm /> }
Thanks!

One way is to check url param in componentDidMount lifecycle method of EventForm:
class EventForm extends Component{
componentDidMount(){
const {location, history} = this.props // these are by design in props when using react-router
if(!location.query.userToken){
history.push('/login') // or whatever route
}
}
render(){
return (<div>...</div>)
}
}
export default EventForm

Related

Cordova deeplinks - query parameter not detected from Universal Links

I am trying to add Universal Linking to a Cordova App using the ionic-plugin-deeplinks plugin.
According to this issue query parameters should work out of the box.
Universal Links for me work correctly except for links with query parameters.
Eg. https://my-site.com/?olddeeplinking=resetpassword&token=123
When I click on the link in an email the queryString field is always an empty string.
Am I missing something, do I need to enable the plugins to detect query params?
Here is the code that I'm using:
const deepLinkRoutes = {
'/user/login': {
action: 'showLogin',
resetUrl: '/',
},
'/user/forgot-password': {
action: 'showForgotPassword',
resetUrl: '/',
},
...
};
export const _getIonicRoutes = () => Object.keys(deepLinkRoutes)
.reduce((links, route) => {
links[route] = { target: '', parent: '' };
return links;
}, {});
export const handleUniversalLinks = () => {
const ionicRoutes = _getIonicRoutes();
const sy = obj => JSON.stringify(obj);
const matchFn = ({ $link, $route, $args }) => {
console.log('Successfully matched route', $link, $route, $args);
alert(`Successfully matched route: ${sy($link)}, ${sy($route)}, ${sy($args)}`);
return history.push($link.path);
};
const noMatchFn = ({ $link, $route, $args }) => {
console.log('NOT Successfully matched route', $link, $route, $args);
alert(`NOT Successfully matched route: ${sy($link)}, ${sy($route)}, ${sy($args)}`);
return history.push($link.path);
};
window.IonicDeeplink.route(ionicRoutes, matchFn, noMatchFn);
};
UPDATE:
It looks like the intent received on Android is always /user/login even though the Universal Link does not have it. What could be causing that?
2019-10-21 17:22:47.107 30389-30389/? D/MessageViewGestureDetector: HitTestResult type=7, extra=https://nj.us.gpd.my_company-dev.com/user/login
2019-10-21 17:22:47.139 1128-1183/? I/ActivityManager: START u0 {act=android.intent.action.VIEW dat=https://nj.us.gpd.williamhill-dev.com/... cmp=us.my_company.nj.sports.gpd/.MainActivity} from uid 10147
A clue:
It looks like the deeplinks plugin is using window.location.href to detect the query parameter.
Since I am using cordova-plugin-ionic-webview the href is always the alias used for localhost of the Ionic engine serving the App contents, so the query parameters are never found.
Deeplinks plugin code:
https://github.com/ionic-team/ionic-plugin-deeplinks/blob/master/src/browser/DeeplinkProxy.js#L40
function locationToData(l) {
return {
url: l.href,
path: l.pathname,
host: l.hostname,
fragment: l.hash,
scheme: parseSchemeFromUrl(l.href),
queryString: parseQueryStringFromUrl(l.href)
}
}
onDeepLink: function(callback) {
// Try the first deeplink route
setTimeout(function() {
callback && callback(locationToData(window.location), {
keepCallback: true
});
})
// ...
}
This is the problem, not sure on the solution yet though.

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

Ember.js Error | Cannot read property 'some' of undefined

I'm using Ember for the front end and I am doing basic testing to see if I can properly render my data before adding components. I have two resources 'Topics' and 'Ratings' and I have added both a route and a model hook for these resources. When I type http://localhost:4200/topics, I am able to see all of the topics being rendered on the template. However, when I type http://localhost:4200/ratings, I receive an error on the console saying:
ember.debug.js:32096TypeError: Cannot read property 'some' of undefined
at error (route.js:21)
at Object.triggerEvent (ember.debug.js:28580)
at Object.trigger (ember.debug.js:53473)
at Object.Transition.trigger (ember.debug.js:53287)
at ember.debug.js:53107
at tryCatch (ember.debug.js:53806)
at invokeCallback (ember.debug.js:53821)
at publish (ember.debug.js:53789)
at publishRejection (ember.debug.js:53724)
at ember.debug.js:32054
Which is strange because in my rails console, I am receiving a HTTP: 200 response. Is there some error within the code of my routes? I made sure to mirror ratings similar to topics. Or is this an association issue? Both a USER and a TOPIC have many ratings. I provided snippets of my code below:
Application Route:
import Ember from 'ember';
export default Ember.Route.extend({
auth: Ember.inject.service(),
flashMessages: Ember.inject.service(),
actions: {
signOut () {
this.get('auth').signOut()
.then(() => this.transitionTo('sign-in'))
.then(() => {
this.get('flashMessages').warning('You have been signed out.');
})
.catch(() => {
this.get('flashMessages')
.danger('There was a problem. Are you sure you\'re signed-in?');
});
this.store.unloadAll();
},
error (reason) {
let unauthorized = reason.errors.some((error) =>
error.status === '401'
);
if (unauthorized) {
this.get('flashMessages')
.danger('You must be authenticated to access this page.');
this.transitionTo('/sign-in');
} else {
this.get('flashMessages')
.danger('There was a problem. Please try again.');
}
return false;
},
},
});
Rating Model:
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
export default Model.extend({
score: attr('number'),
user: belongsTo('user'),
topic: belongsTo('topic')
});
Rating Route:
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.get('store').findRecord('rating', params.id);
},
});
```
Ratings Route:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.get('store').findAll('rating');
},
});
Router:
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType,
});
Router.map(function () {
this.route('sign-up');
this.route('sign-in');
this.route('change-password');
this.route('users');
this.route('topics');
this.route('topic', { path: '/topics/:id'});
this.route('ratings');
this.route('rating', { path: '/ratings/:id'});
// Custom route in topics controller that will call NYT API or generate random-show
//topic. This is a GET request essentially
this.route('random-show');
});
export default Router;
SOLVED! Read the DOCS, and used EXPLICIT INVERSNESS:
https://guides.emberjs.com/v2.5.0/models/relationships/
Apparently, Ember needs help understanding when you have multiple has Many or Belong to for the same type.

Backbonejs routing VS emberjs routing

This is how I do my routes in backbonejs where the routing and its params are obtained first before deciding which external template to call. I find this is quite flexible.
var Router = Backbone.Router.extend({
routes: {
//'': 'renderBasic',
':module/:method/': 'renderDynamicViewPageBasic',
':module/:branch/:method/': 'renderDynamicViewPageBranch',
':module/:branch/:method/set:setnumber/page:pagenumber/': 'renderDynamicViewPagePager',
':module/:branch/:method?set=:setnumber&page=:pagenumber': 'renderDynamicViewPagePager'
},
renderDynamicViewPageBasic: function (module,method) {
$(el).html(Handlebars.getTemplate('template1')(data));
},
renderDynamicViewPageBranch: function (module,branch,method) {
$(el).html(Handlebars.getTemplate('template2')(data));
},
renderDynamicViewPagePager: function (module,branch,method,setnumber,pagenumber) {
$(el).html(Handlebars.getTemplate('template3')(data));
}
});
How about in emberjs, can I do the same - do the rout and get its params afirst before deciding which external template to call?
I read the documentation and tested it. It seems to be less flexible - for instance,
App.Router.map(function() {
this.route("about", { path: "/about" });
this.route("favorites", { path: "/favs" });
});
Is it possible to get the route and params and then the controller before getting the template?
if not, it seems to be the same as case using Angularjs which I finally decided not to use it because it gets the template first before sorting out the params.
You can define the template "post params" in EmberJs using the renderTemplate hook, where you can customize which template you'd like to use.
http://emberjs.jsbin.com/oXUqUJAh/1/edit
App.Router.map(function() {
this.route('apple', {path: 'apple/:id'});
});
App.AppleRoute = Ember.Route.extend({
model: function(params) {
return {coolProperty: params.id};
},
renderTemplate: function(controller, model) {
// send in the template name
this.render(model.coolProperty);
}
});
You can pass a function together with $route params to get customized result in angularjs actually.
template: function($params) {
return app.$templateCache.get($params); // or make template yourself from another source
}

Resources