How can I use React Hooks with ReactJS.NET? - asp.net-mvc

We are currently migrating our frontend from jQuery to Reactjs.NET. We are using React 16.8 which allows us to use React Hooks instead of classes.
We setup our project successfully and tried it first with classes and server side rendering which worked well, but my team rather use React Hooks. I tried using Webpack + Babel to transpile .jsx files since it didn't work anymore using only razor helper #Html.React(), but I still get the same error from my component.
We are using Asp.NET 4.x and NET framework 4.7.
This is my view children.cshtml
#Html.React("ChildrenForm", new {
familyTiesId = Model.FamilyTiesId
},
serverOnly:true
)
This is my ReactConfig.cs:
namespace Nop.Web
{
public static class ReactConfig
{
public static void Configure()
{
// If you want to use server-side rendering of React components,
// add all the necessary JavaScript files here. This includes
// your components as well as all of their dependencies.
// See http://reactjs.net/ for more information. Example:
ReactSiteConfiguration.Configuration
.AddScript("~/Scripts/Components/Customer/ChildrenForm.jsx");
JsEngineSwitcher.Current.DefaultEngineName = V8JsEngine.EngineName;
JsEngineSwitcher.Current.EngineFactories.AddV8();
}
}
}
My component:
import React, { useState, useEffect } from 'react';
const ChildrenForm = (props) => {
const [ familyTiesId, setFamilyTiesId ] = useState(props.familyTiesId);
...
}
It should work, but instead I get:
SyntaxError: Unexpected identifier
Line 20: #Html.React("ChildrenForm", new {
Line 21: ddtl = Model.DDTL,
Line 22: listFamilies = Model.ListFamilies,
...
[JsCompilationException: SyntaxError: Unexpected identifier
at ChildrenForm.jsx:6:8 -> import React, { useState, useEffect } from 'react';]
JavaScriptEngineSwitcher.V8.V8JsEngine.InnerExecute(String code, String documentName) +258
React.ReactEnvironment.EnsureUserScriptsLoaded() +548
It seems like we cannot import files when using razor helper #Html.React and server side rendering.
How can I do an import and use React Hooks while server side rendering?

Instead of having to import it, you can just use:
const [ familyTiesId, setFamilyTiesId ] = React.useState(props.familyTiesId);
Just call useState directly instead of importing.

Related

Use Vaadin badge in lit element

I'm trying to use vaadin badges in lit element.
The documentation is mentioning to "To use these classes in your application, enable them in your theme’s theme.json" but I don't have such a file so it is really confusing to me. Most of the documentation focuses on Java so I am guessing this is where the confusion comes from. So far I have only installed some components via NPM.
I tried to create a frontend/themes/common-theme/theme.json file anyways, but without success so far.
Here is how my element looks like at the moment :
import {LitElement, html} from 'lit';
import '#vaadin/vertical-layout';
import '#vaadin/horizontal-layout';
import '#vaadin/vaadin-lumo-styles/badge.js';
export class PaymentLink extends LitElement {
static properties = {
version: {},
link : { Object}
};
constructor() {
super();
}
render() {
return html`
<vaadin-horizontal-layout>
${this.link.id}
<span theme="badge">Pending</span>
</vaadin-horizontal-layout>
`;
}
}
customElements.define('payment-link', PaymentLink);
Could someone please show me the light? Here is a stackblitz example : https://stackblitz.com/edit/lit-element-jjzdpa?file=src/index.js
The #vaadin/vaadin-lumo-styles/badge.js module only exports the styles, just importing it will not automatically create a style tag with the respective CSS. With Vaadin Flow or Hilla applications that can done automatically by configuring said theme JSON file.
If you want to use badge standalone in a Lit app, the best approach is probably to add the badge styles to the styles of your root / application Lit component:
import { badge } from '#vaadin/vaadin-lumo-styles/badge.js';
class MyLitApp extends LitElement {
static get styles() {
return [badge, /* ...other app styles */]
}
}
Note that if one of your components using badge uses a shadow root, then you need to add the badge styles to that component in the same manner.

Rails 7 + importmap + fullcalendar

I've manage to include bootstrap 5 without any issues, but when I try to include fullcalendar I get this error on browser console:
Failed to load module script: Expected a JavaScript module script but
the server responded with a MIME type of "text/css". Strict MIME type
checking is enforced for module scripts per HTML spec. (main.css:1)
So it looks like the library is imported correctly but the css isn't
my stimulus controller:
import { Controller } from "#hotwired/stimulus"
import moment from "moment"
import { Calendar } from '#fullcalendar/core';
import dayGridPlugin from '#fullcalendar/daygrid';
import timeGridPlugin from '#fullcalendar/timegrid';
import listPlugin from '#fullcalendar/list';
export default class extends Controller {
static targets = [ "calendar" ]
connect() {
console.log(this.calendarTarget)
let calendar = new Calendar(this.calendarTarget, {
plugins: [ dayGridPlugin, timeGridPlugin, listPlugin ],
initialView: 'dayGridMonth',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,listWeek'
}
});
}
}
Any ideias what I'm doing it wrong?
EDIT: looks like is related to:
https://github.com/rails/rails/issues/44239
I have been fiddling with this problem for a while today. I too couldn't get past the issue with the JS trying to pull in the CSS and raising MIME type errors.
However, I have managed to get FullCalendar running with importmaps by vendoring the main.js file provided by the CDN (referenced here), and then manually editing the vendored file to export the FullCalendar function as the default.
main.js -> https://cdn.jsdelivr.net/npm/fullcalendar#5.11.0/main.min.js
Copy this into vendor/javascript/fullcalendar.js
Pin the file in importmap.rb:
pin "fullcalendar" # #5.11.0
At this point I have the following error:
Failed to register controller ... SyntaxError: The requested module 'fullcalendar' does not provide an export named 'default'
Edit your new vendor/javascript/fullcalendar.js file and append to the bottom:
export default FullCalendar;
And then in the stimulus controller:
import { Controller } from "#hotwired/stimulus"
import FullCalendar from 'fullcalendar';
export default class extends Controller {
connect() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth'
});
calendar.render();
}
}
Css is just brought in via application.scss as normal.

Lazy loading/react-loadable in React-on-Rails

I want to lazy load all of my top-level components to improve load time. I am using react-on-rails to integrate React and Ruby on Rails. To render the components, I have to register them like so in registration.jsx:
import ReactOnRails from 'react-on-rails';
import Component1 from '../components/Component1';
import Component2 from '../components/Component2';
ReactOnRails.register({Component1, Component2});
I tried two methods:
1. React.lazy:
import {lazy} from "react";
const Component1 = lazy(() => import("../components/Component1"));
But the browser throws an error...
Uncaught Error: Module build failed: SyntaxError:
.../app/registration.jsx: Unexpected token, expected ";" (20:113)
It seems that I cannot use lazy load in this file. So I tried it in a sub component within Component1. But it still doesn't work.
const SubComponent = lazy(() => import("./SubComponent"));
This returns:
Uncaught ReferenceError: SubComponent is not defined
2. react-lodable:
Official doc
This seems to be recommended by react-on-rails (see below), but I got a different error saying react-on-rails cannot find such components.
import ReactOnRails from 'react-on-rails';
import Loadable from "react-loadable";
const loading = () => {
return <div>Loading...</div>;
}
const Component1 = Loadable({
"loader": () => import('../components/Component1'),
"loading": loading
});
import Component2 from '../components/Component2';
ReactOnRails.register({Component1, Component2});
This is the error:
Uncaught ReferenceError: ReactOnRails encountered an error while rendering component: Component1.
Original message: React is not defined
I tried to use it on a sub-component, too. But it just doesn't render it.
import Loadable from "react-loadable";
const loading = () => {
return <div>Loading...</div>;
}
const SubComponent = Loadable({
"loader": () => import("./SubComponent"),
"loading": loading
});
class Component1 extends PureComponent {
render () {
return (
<SubComponent />
)
}
}
But this only returns "loading..."
Questions:
The only good news is that Webpack is correctly splitting the bundles into smaller bundles based on components, so I think react-loadable works, but was not rendered correctly.
Is it possible to lazy load components using react-on-rails? Or am I having a syntax error? It seems that react-on-rails controls how components are loaded.
Another idea: maybe I should only do lazy loading on subcomponents. But I wonder if this will really help decreasing initial load time. Any thoughts?

Using Razor to generate Angular2 templates

I am trying to use Razor to generate the html used in an Angular template. I am using Angular v 2.0.0. I have a Contract.cshtml that looks like:
<script>
System.import('contract.js').catch(function(err){ console.error(err); });
</script>
<my-contract>
<h1>Hello {{name}} this is a quick starter for angular 2 app</h1>
</my-contract>
A Contract.ts that looks like
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { ContractModule } from './app/Contract/contract.module';
platformBrowserDynamic().bootstrapModule(ContractModule);
A contract.component.ts that looks like:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { ContractComponent } from './contract.component';
#NgModule({
imports: [ BrowserModule ],
declarations: [ ContractComponent ],
bootstrap: [ ContractComponent ]
})
export class ContractModule { }
And a contract.component.ts that looks like:
import { Component } from '#angular/core';
#Component({
selector: 'my-contract'
})
export class ContractComponent {
public name = 'Nigel Findlater';
consturctor() {}
}
When I run this I get
Error: Error: No template specified for component ContractComponent
at DirectiveNormalizer.normalizeDirective (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:13476:21) at RuntimeCompiler._createCompiledTemplate (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:16869:210)
at eval (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:16807:43)
at Array.forEach (native)
at eval (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:16805:50)
at Array.forEach (native)
at RuntimeCompiler._compileComponents (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:16804:45)
at RuntimeCompiler._compileModuleAndComponents (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:16741:39)
at RuntimeCompiler.compileModuleAsync (http://localhost:2600/lib/#angular/compiler/bundles/compiler.umd.js:16732:23)
at PlatformRef_._bootstrapModuleWithZone (http://localhost:2600/lib/#angular/core/bundles/core.umd.js:6954:29)
Evaluating http://localhost:2600/contract.js
Error loading http://localhost:2600/contract.js
I think the error is in contract.component.ts, but I don't know how not to specify a template here
Angular2 is not designed to be used with Razor generated templates. It is best to allow Angular2 to handle all the routing and not use MVC because this adds an unnecessary dependency which makes the project harder to test.
I use Angular to offload a lot of work from the server to the client and host complex websites using much less resources. Those websites are not SPAs.
In that context it can be really useful to use ASP.NET router and razor generated templates (sometimes precompiled).
The only problem I faced in using razor generated templates in Angular was the way webpack was configured.
After removing the angular2-template-loader everything worked smoothly.

Angular 2 and ActionCable integration

I'm trying to make a Angular2 app (bootstrapped with angular-cli) work with Rails's ActionCable by integrating this lib on the frontend https://github.com/mwalsher/actioncable-js
I npm installed the lib,
added this to angular-cli-build.js
'actioncable-js/index.js',
and this in system-config.ts:
/** Map relative paths to URLs. */
const map: any = {
'moment': 'vendor/moment/moment.js',
'ng2-bootstrap': 'vendor/ng2-bootstrap',
'lodash': 'vendor/lodash',
'actioncable-js': 'vendor/actioncable-js'
};
/** User packages configuration. */
const packages: any = {
'ng2-bootstrap': {
format: 'cjs',
defaultExtension: 'js',
main: 'ng2-bootstrap.js'
},
'actioncable-js':{
main: 'index.js'
},
'moment':{
format: 'cjs'
},
'lodash':{
main: "lodash.js"
}
};
added this to my component:
import { ActionCable } from 'actioncable-js';
but the build errors with this message:
Cannot find module 'actioncable-js'.
anyone has any idea why?
My guess is typings are missing, but I'm not sure how to fix this.
Rails has since released an official actioncable package here:
https://www.npmjs.com/package/actioncable
No there is no problem with typings. What you are missing is how to use Javascript library in angular 2 typescript application. If you want to use JavaScript library in your TypeScript application, then you need to import the library import 'actioncable-js' and then you have to declare the variable. declare let ActionCable:any This tells typescript we have a global variable ActionCable present in our application. Now you can access it in your angular 2 component implementations and do whatever you want to do. You can read the discussion here.
angular-cli.build.js
vendorNpmFiles: ['actioncable-js/**/*.js']
systemjs.config.js
map:{ 'actioncable-js':'vendor/actioncable-js/dist/action_cable.js'}
package:{'actioncable-js': defaultExtension: 'js'} }
`
app.component.ts
import 'actioncable-js';
declare let ActionCable:any;
#Component({
....
})
export class AppComponent implements OnInit{
constructor(){}
ngOnInIt(){
//can access *ActionCable* object here
}
}

Resources