Fetch API not working with Rails + React from Webpacker - ruby-on-rails

how to get work Fetch API with Rails and React.
Setup: Rails 5, Sprockets and React from Webpacker.
What I'm trying to achieve here is use Fetch API to fetch and load data to ReactBootstrapTable API.
import React from 'react';
import {BootstrapTable, TableHeaderColumn} from 'react-bootstrap-table';
import 'whatwg-fetch'
class ExampleTable extends React.Component {
constructor() {
super();
this.state = {
datatable: [], // I'm not sure if I can use array to json data.
};
}
// I'm new to React, so I'm not sure if I'm passing props right
componentDidMount() {
console.log('start componentDidMount');
fetch('https://www.test.com/test.json')
.then(function(response) {
return response.json();
}).then(function(response) {
let datatable = response;
this.setStates({ datatable });
}).catch(function(ex) {
console.log('parsing failed', ex);
});
}
render() {
return (
<div>
<BootstrapTable
data={this.states.datatable} //Here, I'm passing data to the table
pagination
striped hover condensed
options={ { noDataText: 'Empty Table' } }>
<TableHeaderColumn isKey dataField='id' width='80px' dataAlign='center'>
ID
</TableHeaderColumn>
<TableHeaderColumn dataField='test'width='300px' dataAlign='left'>
Test
</TableHeaderColumn>
</BootstrapTable>
</div>
);
}
}
export default ExampleTable;
The main problem here is that I'm not able to use Fetch API and also I'm not totally sure about the way I'm passing data to the table (basically how to use react)
I'm getting a error/warning from my IDE
fetch is not defined
And another error message from the Firefox console:
The above error occurred in the component: in ExampleTable
TypeError: this.states is undefined
I installed the Fetch API through yarn and checked the node_modules/whatwg-fetch folder. As described from the github API, I imported 'whatwg-fetch' as showed in the code above.
I don't know what else I should do. I installed ReactBoostrapTable in the same way and got succeeded doing it. Everything else is working fine.

I looked at the installation instructions of fetch for use with webpack, it says to add this to your webpack configuration:
entry: ['whatwg-fetch', ...]
Also, you mentioned the other error TypeError: this.states is undefined. It looks like you have a typo. In React, if you're trying to refer to state, you use it like this:
this.state.datatable
In your render method, you refer to this.states.
Also, when you're making your call with fetch, you're trying to call this.setStates. You should instead be using it like this:
this.setState({ somestate: "xyz"})

Related

Passing global constants to angular 4 from ASP.NET MVC

I am shifting from Razor views to Angular 4, and trying to figure out how to pass global constants from the server to Angular without relying on Ajax calls.
So the server constants will be transaction status for example:
Id: 1->Active
Id: 2-> Inactive
Id: 3->Cancelled etc
So these statuses are saved in the db and are used to query various transactions, Thus will be required in lots of components
In Razor views, I used to pass these values together with the viewmodel. But in Angular currently I can see two options:
Make Ajax calls in ngOnInit of each component that requires these constants
Make a static model to hold these values
Option 1 increases the number of server calls by quite a bit -> so I am trying to avoid this.
Option 2 will require me to change status in multiple places in my application if a new status is added for example, which i am also not fond of.
I am looking for a way to send all my constants to Angular as the application loads or page is reloaded for example.
You need to use ReplaySubject
as per rxjs documentation
ReplaySubject:Represents an object that is both an observable sequence as well as an observer. Each notification is broadcasted to all subscribed
Look at this code snippet
export class GlobalConstants{
Status:number[];
}
import { Observable, ReplaySubject } from 'rxjs';
import { GlobalConstants } from '../models/GlobalConstants';
#Injectable()
export class YourService {
//This line will cache the latest list you will get from the server
private dataSubject = new ReplaySubject<GlobalConstants>();
//you will use this observer in your components to subscribe to the getStatus result
yourStatusList$: Observable<GlobalConstants> = this.dataSubject.asObservable();
constructor(private http: Http) {
this.getStatus()
}
getStatus() {
return this.http.get('url').subscribe(res => {
this.dataSubject.next(res);
})
}
export class ExampleComponent {
public statusList;
public constructor(private _yourService: YourService) {
this.getStatus();
}
getStatus(): void {
this._yourService.yourStatusList$.subscribe(
result => {
this.statusList = result;
}
)
}
}
what will happen is when angular create the service it will call getStatus method one time per the app life cycle and then fetch your status list from the server then u will need to subscribe in your components to yourStatusList$ , for each subscrbition you will get latest cached list and if the list changed in your server u just need to call YourService.getStatus then u will fetch the status list again and all component subscribed to this observer will get notified by the new list
let's take your two challenges
1-Make Ajax calls in ngOnInit of each component that requires these constants
-by using this code your app will make one call to the server to fetch status list so u don't need to make Ajax call in ngOnInit of each component
2-Make a static model to hold these values will require me to change status in multiple places in my application if a new status is added
-if new status is added you just need to call YourService.getStatus one time in any place in your code and all components subscribed to your yourStatusList will get notified by the new status list
NOTE: you must n't use providers: [yourService] in your component cause if u used it it will create a new object and will not use the global object , just add your service in #NgModule providers and use component constructor to inject the service object
It may be best to have a service cache the information in a local variable. Then, when you inject the service into your components, and one calls a service function, the service checks the local variable. If something is in the variable, use it, if not, load the data and cache it for later use.
Since the service is a singleton, the data should only load once unless you create some mechanism to timeout the value. So, the first time the service is called, the data will be fetched. After that, the local variable (below called globals) should be used.
Service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class GlobalsService {
private globals: any;
constructor(private httpClient: HttpClient) { }
getGlobals(): any {
if (this.globals) {
return this.globals;
} else {
// call your API to get global data from DB
this.httpClient.get<any>('...').subscribe((data: any) => {
this.globals = data;
return this.globals;
});
}
}
}
Component using the service:
import { GlobalsService } from './../globals.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-tester',
templateUrl: './tester.component.html',
styleUrls: ['./tester.component.css']
})
export class TesterComponent implements OnInit {
constructor(private globalsService: GlobalsService) { }
ngOnInit() {
// Do something here with the globals from the service
const gbls = this.globalsService.getGlobals();
if (gbls) {
// ... maybe put these in a variable for later use, what ever you need
}
}
}
Doing this will keep you from having to do the Ajax call you mention, and avoid you have to keep code in more than one place. The service pattern offers a nice central place to keep this data for the lifetime of the application. All you need to do is inject the service into the component, or other services, where it is needed.
You can add you constants as attributes on your app element inside you razor view
<app someatt="{ your json data here }">Loading...</app>
then on you app's root component access them like this:
export class AppComponent implements OnInit {
constructor(
private el: ElementRef
) {
}
ngOnInit() {
console.log(this.el.nativeElement.attributes["someatt"].value);
}
}
then you can have a global service with its statuses data set here on ngOnInit and consumed in all your components

Binding from mvc model to angular2 component (Components in mvc project)

I could use some help, figure out how to pass model data from mvc application to a angular2 component running inside mvc.
Lets say I have a cs.html file that has an component
<my-app></my-app>
This will load the angular2 component. I need to generate some binding to keep mvc models intact with my angular2 models.
First of all, I'm trying to pass a model to the component via the Input property.
CSHTML file:
In the top of my cshtml file, I have:
#model MainModel
<script>
var model = #Html.Json(Model.Form.PassengerModel);
</script>
I want to pass this model to my angular2 component.
What I have tried are:
<my-app passengerModel="model"></my-app>
Angular2 component:
import { Component, Input } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './Content/Scripts/angular2components/app.component.html',
})
export class AppComponent {
#Input() passengerModel: PassengerModel;
constructor() {
console.log("Model loaded?: " + this.passengerModel);
}
}
export class PassengerModel {
constructor(
public Adults: number,
public Children: number
) { }
}
The problem is that the model is undefined always. Is there any way to pass a model in to the component?
The problem you have outlined above is that your binding is not correct for the context you are attempting to use it in.
<my-app passengerModel="model"></my-app>
The above line is telling Angular to bind passengerModel inside your my-app component to a property on the host component named model. This means the page (component) which hosts your my-app component should be a component with a property named model. You have created a global variable which is not in the scope of your host component. Angular2 specifically isolates the scope of each component so that you do not accidentally introduce unwanted side effects.
Save yourself some pain an anguish and embrace Angular fully. Ditching your MVC Page Controllers and moving to WebApi service calls will yield better results and save you the need to translate the model manually among other issues you will run into going down the mixed route.
Considerations:
#Html.Json will ultimately cause your data to be exposed directly in your script tag. This could be a security risk if the data is sensitive and if you start using MVC Model bindings in the page alongside Angular bindings they will fight each other.
Basically these approaches are diametrically opposed as ASP.NET MVC is a server side approach and Angular is a client side approach. Mixing them in the same application will always be awkward at best.
WebApi gives you the JSON serialization more or less for free. Your MVC model is automatically serialized to JSON by the framework when returning an HttpAction. If you are trying to avoid converting your ASP.NET MVC views to Angular Components then I understand. You may not have a choice but if you do I would steer clear of mixing these two.
[HttpGet]
[AcceptVerbs["GET"]
[Route("passengers/{id:int}")]
public IHttpActionResult GetPassenger(int id)
{
// For illistration...
try
{
var passengerModel = PassengerService.LoadPassenger(id);
return Ok(passenger);
}
catch(Exception e)
{
return InternalServerError(e);
}
}
https://angular.io/docs/ts/latest/tutorial/toh-pt6.html
// I would normally put this in the environemnt class..
private passengerUrl = 'api/passengers'; // URL to web api
constructor(private http: Http) { }
getPassenger(id: number): Promise<Passenger> {
return this.http.get(`this.passengerUrl/${id}`)
.toPromise()
.then(response => response.json() as Passenger)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}

How to temporarily handle routing when migrating a rails app to react?

I'm getting ready to migrate a monolithic rails app to a react app with a rails backend and json api. For now we've integrated the react application as a static asset in our rails app, and are slowly transitioning every page to be rendered by react.
Problem is, that the react app doesn't do the routing (because the rails app handles it currently). Only when all pages have been transferred do we want to transition the frontend completely to react.
However, the react app shouldn't render the same content on every page of course. It should render the appropriate content, based on the page on which it is initiated (and it would reinitiate for every pageload, but the script for the app itself would remain the same).
So the question is, what would be the recommended way to enable react to render the appropriate content. Does it make sense to use react-router, and use it only to render the content based on the url, but not have it handle links?
So in your route file:
export default function(requirePlanData) {
return (
<Route path="/">
<Route path="(:lang/)reading-plans" component={PlansView}>
<Route path=":id(-:slug)" component={AboutPlanView} onEnter={requirePlanData} />
</Route>
)
}
Here you can see that on render of '/en/reading-plans/903' the 'AboutPlanView' component will be rendered, and the 'requirePlanData' function will be called to populate the app state required for the component view.
In your main js file with your requirePlanData you can do call your action creators for your API calls and populate state with your reducers before the view is loaded:
function requirePlanData(nextState, replace, callback) {
const { params } = nextState
var idNum = parseInt(params.id.toString().split("-")[0])
const currentState = store.getState()
if (currentState && currentState.plansDiscovery && currentState.plansDiscovery.plans && currentState.plansDiscovery.plans.id === idNum) {
callback()
} else if (idNum > 0) {
store.dispatch(PlanDiscoveryActionCreators.readingplanInfo({ id: idNum, language_tag: window.__LOCALE__.planLocale }, store.getState().auth.isLoggedIn)).then((event) => {
callback()
}, (error) => {
callback()
})
} else {
callback()
}
}
Now your action creators will return, fire off your reducer to populate state, and your component will be able to render with all the required data–based off of the url/route you defined.
Does this help you?

How to push objects to `Store` globally using Emberjs

I have a Message model with a value and author properties. I can do this inside the Ember codes (controller, model, views, etc):
this.store.push('message', msgObj)
However, the following does not work at the Global scope, say putting that inside <script src="websocket_processor.js"> like:
msgObj = {value: 'Hello!', author: 'Jules'}
//I've tried the following but does not work
this.store.push('message', msgObj) //`this` doesn't point to ember
store.push('message', msgObj) //Console error: undefined store
App.Store.push('message', msgObj) //Uncaught TypeError: Object function () {if (!wasApplied) {...
I want this to be outside ember because I am using websocket-rails gem from which I use the following function
dispatcher.bind('add_message', function(data) { //add_message is just a method param from server
//Code where I need to use Ember to store say
this.store.push('message', data)
}
I'm stuck with this for hours now. Any help would be appreciated. Thanks!
You can use Application initializer to do the job and lookup for store after it has been registered so you can use it in external component.
Ember.onLoad('Ember.Application', function (Application) {
Application.initializer({
name: "websocket-rails",
after: "store",
initialize: function (container, application) {
var store = container.lookup('store:main');
// Now you can inject store to component outside of Ember
}
});
});

TypeScript with Angular.JS and web API

I'm working on an asp.mvc3 web api project. in this project I use TypeScript and Angular.js
and I need to access the business layer from TypeScript through the Web API. I called the Web API inside the constructor method in TypeScript using the
code given below.
constructor($scope, $http: any) {
$scope.VM = this;
$http.get("/API/PrivateAPI/Test/1").success((a) => { this.Tiles = a });
var bb = this.Tiles;
}
However, when trying to get the object list from the business layer, the Tiles array is empty. I debugged the code and found out the Web API is called after passing the last line of the constructor and does return results. I need to call that method inside the constructor and get object list to the Tiles array.
Does anyone know how to do so?
First of, I think you should do the following (notice .data) :
$http.get("/API/PrivateAPI/Test/1").success((response) => { this.Tiles = response.data });
Anyways, $http only supports async http requests. What you want can be done by a synchronous XHR request and that is considered bad UI experience, since the browser window freezes till the XHR request completes, and therefore $http doesn't support it (configuration docs).
What you can do is something like :
call another function from response e.g.
(response) => { this.Tiles = response.data; this.continueWithProcessing(); }
Or, Setup a variable to hide a preloader when the response comes back:
(response) => { this.Tiles = response.data; this.allDone=true; }
Where you have an ng-show on something like:
<div ng-show="!VM.allDone">Loading the data....</div>
Or both :)
Note: An async setting is supported in underlying browsers native XHR object and therefore in $.ajax which is the jquery ajax function : http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings. However it is a horrible UI experience + if you use it from angular you are responsible for scope.apply.

Resources