How to import node module in React-Kotlin? - javascript

I created an app using the create-react-kotlin-app command and it loads in Chrome fine. I added the React Material UI package via NPM and that was successful. Now how do I use the Material UI module in my component?
Normally with JavaScript, it's a simple import Button from '#material-ui/core/Button' at the top of the component's file, but Kotlin doesn't like that.
How do I translate that line to Kotlin? I am not using Gradle.

I have been struggling with this problem for days now. I came up with the following solution. First we will see multiple ways to declare external modules, then I will show how to use them
.
Consider the following javascript code
import Button from '#material-ui/core/Button' // this means button is exported as default
This will be imported in kotlin in the following ways
Button.kt
#file:JsModule("#material-ui/core/Button")
#file:JsNonModule
package com.mypckage.mykillerapp
import react.Component
import react.RProps
import react.RState
import react.ReactElement
#JsName("default") // because it was exported as default
external val Button : RClass<RProps>
// way 2
#JsName("default")
external class Button : Component<RProps,RState> {
override fun render(): ReactElement?
}
But again, if the statement intend for kotlin has to match the javascript import statement bellow,
import { Button } from "material-ui" // not exported as default
We use the following approach: Button.kt
#file:JsModule("material-ui")
#file:JsNonModule
package com.mypckage.mykillerapp
import react.Component
import react.RProps
import react.RState
import react.ReactElement
// way 1
#JsName("Button") // because it was exported as default
external val Button : RClass<RProps>
// way 2
#JsName("Button")
external class Button : Component<RProps,RState> {
override fun render(): ReactElement?
}
once you have declared on how to use your components, you can just use them as follows:
//way 1:
fun RBuilder.render() {
div {
Button {
attrs.asDynamic().className="submit-button"
+"Submit"
}
}
}
//way 2:
fun RBuilder.render() {
div {
child(Button::class) {
attrs.asDynamic().className="submit-button"
+"Submit"
}
}
}
great. you have imported your component. But until then your are not relying on kotlin type safety and even code completion, to achieve that, you have to go to extra length
as shown bellow
external interface ButtonProps: RProps {
var className : String
var onClick: (Event?)->Unit
var color: String
// . . .
var href: String
}
then go ahead and declare your button as
#JsModule("#material-ui/core/Button")
#JsNonModule
#JsName("default") // because it was exported as default
external val Button : RClass<ButtonProps>
and you can now use it with type safety and code completion as shown bellow
fun RBuilder.render() {
div {
Button {
attrs {
className = "submit-button"
onClick = {
window.alert("Vois La")
}
}
+"Submit"
}
}
}
Hope this helps. Happy coding
EDIT:
There is a community wrapper for material-ui components here
HINT:
Use way 1, as you can see, it is less verbose

The Kotlin way for importing dependencies is close to standard JS importing:
import React from 'react';
export function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Based on Creating a simple React component with Kotlin.
package hello
import react.*
import react.dom.*
fun RBuilder.hello(name: String) {
h1 {
+"Hello, $name"
}
}
Usually (as Kotlin is Java-based) it uses Gradle tool to handle dependencies:
// part of build.gradle
kotlinFrontend {
// ...
npm {
// ...
dependency("react")
dependency("react-dom")
dependency("react-router")
dependency("react-markdown")
devDependency("css-loader")
devDependency("babel-core")
// ...
}
And are referenced like above:
HomeView.kt:
// https://github.com/Kotlin/kotlin-fullstack-sample/blob/master/frontend/src/org/jetbrains/demo/thinkter/HomeView.kt
import kotlinx.html.*
import org.jetbrains.demo.thinkter.model.*
import react.*
import react.dom.*
import kotlinx.coroutines.experimental.launch
ReactMarkdown.kt:
// https://github.com/Kotlin/kotlin-fullstack-sample/blob/master/frontend/src/org/jetbrains/demo/thinkter/ReactMarkdown.kt
package org.jetbrains.demo.thinkter
import react.*
private val ReactMarkdown: dynamic = runtime.wrappers.require("react-markdown")
Based on: kotlin-fullstack-sample
In create-react-kotlin-app additionally faced the possibility of importing with #JsModule() annotation, while dependencies managing is handled in standard way via package.json:
// src/logo/Logo.kt (outcome of creating new app)
package logo
import react.*
import react.dom.*
import kotlinext.js.*
import kotlinx.html.style
#JsModule("src/logo/react.svg")
external val reactLogo: dynamic
#JsModule("src/logo/kotlin.svg")
external val kotlinLogo: dynamic
And can be also successfully used for JS libraries importing.
Another way would be to use kotlinext.js.*:
// index/index.kt
import kotlinext.js.*
fun main(args: Array<String>) {
requireAll(require.context("src", true, js("/\\.css$/")))
// ...
}
Which provides also require(module: String) function.

Related

How to dynamically import an object exported from another file?

In my node_modules, I have a folder with an index.js which exports an object which contains a bunch of icon objects.
export { arrow1, arrow2, arrow3 .. }
I want to somehow dynamically import an icon object from another component. I am using Stencil.js, which is similar to React, and I need to use the icon string passed as a prop to dynamically import that particular icon object. How do I do that? The issue is that the import statement must be at the top of the page, but the prop is defined below.
Is there a way to import an exported object without using the import statement?
I tried with fetch() but it wasn't working. It kept returning status not ok.
I don't see point how importing all icons is a performance issue.
Here is small example, but it depends on what format are the imported icons.
import { IconNames } from '../icon/types';
#Component({
tag: 'custom-component'
})
export class CustomComponent {
#Prop() iconName: keyof IconNames;
render() {
const unicodeIcon = String.fromCharCode(parseInt(getIconHexCharCode(this.iconName), 16));
return (
<span>{unicodeIcon}</span>
)
}
}

TypeError: X is not a constructor

I am learning React and ES6. I Created a React ES6.js for using and learning es6 features comfortably and cleanly.
Then I imported this js file in index.js and used that way:
// Get Started
import React from 'react';
import ReactDOM from 'react-dom';
const myfirstelement = <h1>Hello React!</h1>
ReactDOM.render(myfirstelement, document.getElementById('root'));
// React ES6 (ES6 stands for ECMAScript 6.)
// eslint-disable-next-line
import { Car } from './ReactES6'
var mycar = new Car("Ford");
mycar.present();
document.write(mycar.brand);
Also ./ReactES6:
// eslint-disable-next-line
class Car {
constructor(name) {
this.brand = name;
}
present() {
return 'I have a ' + this.brand;
}
}
I am getting an error in the title of this question while running but compiler doesn't gives any error.
What I am doing wrong, please help me experienced friends.
Probably something with the way you are exporting Car from ReactES6. It doesn't show in the code if you are exporting it. You could export it using
export default Car
at the bottom of the file. Then in the main method import it using
import Car from './ReactES6'
Edit: Notice that I removed the curly braces from the import statement { }. If you want to import something like { Car } you need a named export. A file can have multiple named exports, but only one default export. Try reading about Named Exports vs Default exports.
Here's a relevant SO thread

Vue error with Rangy library: "..." is not a function (but only inside hooks)

For a Vue component, I use the Rangy library to highlight single words inside a text. This is working fine if Rangy is called from any method inside the methods object:
let applier = rangy.createClassApplier('some-class');
applier.toggleRange(range);
But sometimes I need to restore a certain state on page load, so I tried using the same method inside the created and mounted hooks. This doesn't work.
Right now, the first lines of the script in my component look like this:
import _ from 'lodash';
import rangy from 'rangy';
import 'rangy/lib/rangy-classapplier';
export default {
mounted: function() {
// Leave function if there is no data in DB to be restored
if (_.isEmpty(this.mostRecentAnswers))
return;
else {
// ERROR
let applier = rangy.createClassApplier('some-class');
// ...
}
}
// ...
}
The error message is TypeError: rangy__WEBPACK_IMPORTED_MODULE_2___default.a.createClassApplier is not a function at VueComponent.mounted
I use Vue with Laravel and Laravel Mix (which is a wrapper for Webpack).
What's wrong with my code? Note I also import Lodash and use it inside the hook without any errors.
I would recommend making a Vue plugin out of this to make it easier:
// plugins/vue-rangy.js
import rangy from 'rangy'
const VueRangy = {
install (Vue, options) {
if (options.hasOwnProperty('init') && options.init) {
rangy.init()
}
Vue.prototype.$rangy = rangy
}
}
export default VueRangy
Then use it like any other plugin:
import VueRangy from 'plugins/vue-rangy'
Vue.use(VueRangy, {
init: true
})
Then just use this.$rangy in your components.
I had to use rangy.init() which initializes Rangy if it has not already been initialized:
https://github.com/timdown/rangy/wiki/Rangy-Object#rangyinit

Why is Rollup not treeshaking my library?

I have written an es6 library that compiles to produce the following:
// All the library code, then...
export { default as Campaign } from './modules/campaign.js';
export { default as Triggers } from './modules/triggers.js';
export { default as Asset } from './modules/asset.js';
export { default as AssetSet } from './modules/assetSet.js';
I am then trying to import only Triggers from the library in another project
import { Triggers } from '../../my-library/lib/library.es.js';
When I inspect the bundle it contains all the code for every module exported from my library, rather than just the code from Triggers. I understood that treeshaking would allow me to import just the Triggers code. What am I doing wrong?
My library does not export a default, it only exports the named exports I have written above.
Edit - campaign.js imports asset.js so I would expect to get both when I import Campaign, however Triggers does not reference anything else.

Import components for server-side rendering in ES6

I've got a nice little ES6 React component file (simplified for this explanation). It uses a library that is browser-specific, store This all works beautifully on the browser:
/app/components/HelloWorld.js:
import React, { Component } from 'react';
import store from 'store';
export default class HelloWorld extends Component {
componentDidMount() {
store.set('my-local-data', 'foo-bar-baz');
}
render() {
return (
<div className="hello-world">Hello World</div>
);
}
}
Now I'm trying to get it to render on the server as follows, using babel-register:
/server/routes/hello-world.js:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import HelloWorld from '../../app/components/HelloWorld'
export default function(req, res) {
res.render('root', {
reactHTML: ReactDOMServer.renderToString(<HelloWorld />),
});
}
I get an error from the node server saying "window is not defined" due to importing 'store'. Ideally I could conditionally import by detecting the environment (node vs browser), but conditional imports aren't supported in ES6.
What's best way to get around this? I don't actually need to execute the browser code (in this case componentDidMount won't be called by ReactDOMServer.renderToString) just get it running from node.
One way would be using babel-rewire-plugin. You can add it to babel-register via the plugins option
require('babel/register')({
plugins: ['babel-rewire-plugin']
});
Then rewire your store dependency to a mocked store:
HelloWorld.__Rewire__('store', {
set: () => {} // no-op
});
You can now render HelloWorld from the server peacefully.
If you want to suppress the load of some npm module, you can just mock it.
Put this on your node.js application setup, before the HelloWorld.js import:
require.cache[require.resolve('store')] = {
exports: {
set() {} // no-op
}
};
This value will be used instead of the real module, which doesn't need on your purposes. Node.js module API is stable, so this behavior will not be broken and you can rely on it.

Categories

Resources