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>
)
}
}
Related
I'm ultimately trying to dynamically create an object of imported components to export all at once. But in my simple example below, I can't even export an object with 1 component. Why is that?
// index.js
// Card is a standard ReactJS component, exported with export default Card
import Card from './Card';
let Components = {};
Components['Card'] = Card;
// this also doesn't work
// Components['Card'] = require('./Card').default
export default Components;
// Error message: "Attempted import error: 'Card' is not exported from './index.js'"
As the error says, you don't have a named export Cards. You have a default export, Components, which is an object with a property Cards.
If you want to have named export Cards then do
export {Cards};
Or if you don't even need the Components object, you can re-export the component directly with
export {default as Card} from './Card';
However if you really do want to export a default object that holds one or more components as properties, then import it accordingly:
import Components from './index.js';
// use Components.Cards were necessary
See the export documentation on MDN for more information.
Two solution here. Firstly when you assign a component into the Components object you can assign Card as a component like below.
Components['Card'] = <Card/>
and then use it like this
<div>{Components.Card}</div>
Secondly and My favorite is, whatever you have done in the index.js file to store the component is ok. But when you are using it use it like below.
<Component.Card/>
Example
I need to add to ALL of my React function components the possibility to add [data-test-id] attribute for testing purposes. To achieve that I created withTestId() HOC which adds optional prop testId to wrapped component and when it's defined it adds [data-test-id] to final HTML.
So when I define component like:
<ExampleComponent testId="example" />
it returns:
<div data-test-id="example" />
The only problem I have is to apply it to every component without the necessity to wrap it individually in every component. So instead of writing code like:
function ExampleComponent() { ... }
export default withTestId(ExampleComponent)
I would like to wrap all of my exports in my index.ts file, which right now looks like this:
export { default as ExampleComponent } from "./ExampleComponent";
export { default as ExampleComponent2 } from "./ExampleComponent2";
...
How can I achieve this?
I see two ways of doing this; One dynamic way, making the user-code of your library a bit more convoluted. with you being able to change the implementation easily and another one with a bit more boilerplate code, keeping the user-code as it is.
I haven't tested their behavior regarding tree-shaking when bundling the code.
Using destructing in user-code
This allows to add / remove things from your main component export file without having to worry about additional boilerplate in your library. The higher-order-component can be switched on/off easily. One caveat: The user code needs to use destructuring to retrieve the components.
Your new index.ts file would look like this, while I've called your previous index.ts file components.ts in the same directory:
import * as RegularComponents from "./components";
import withTestId from "./with-test-id";
const WithTestIdComponents = Object
.keys(RegularComponents)
.reduce((testIdComps, key) => {
return {
...testIdComps,
[key]: withTestId(RegularComponents[key])
};
}, {});
export default WithTestIdComponents;
To use it in your application code:
import MyComponents from "./components/tested";
const { Component1, Component2, Component3, Component4 } = MyComponents;
This uses the default export to make it look like you have all components in one place, but since you cannot destructure exports directly, you need this second step to get the correct components out of it.
Add boilerplate to the export file
Since there is an index.ts file with all the components exported in the library, one could import/rename each component and re-export them with withTestId and their name:
import withTestId from "./with-test-id";
import { default as TmpComponent1 } from "./component1";
import { default as TmpComponent2 } from "./component2";
import { default as TmpComponent3 } from "./component3";
import { default as TmpComponent4 } from "./component4";
export const Component1 = withTestId(TmpComponent1);
export const Component2 = withTestId(TmpComponent2);
export const Component3 = withTestId(TmpComponent3);
export const Component4 = withTestId(TmpComponent4);
This way, imports can be used as before:
import {
Component1,
Component2,
Component3,
Component4
} from "./components";
I'd argue that using index files already is some kind of boilerplate and this approach adds to it. Since the user code does not need any changes, I'd favor this approach.
In one of our projects, we have used a custom takeoff script to create this kind of boilerplate for us, whenever we generate a new component.
Examples
Here is a code sandbox to see both approaches.
Preface
I'm using create-react-app to generate an application.
Problem
TodoList === undefined
Code
components/index.js
export { default as App } from './App/App.js';
export { default as TodoList } from './TodoList/TodoList.js';
containers/index.js
export { default as VisibleTodoList } from './VisibleTodoList.js';
components/App/App.js
import { VisibleTodoList } from '../../containers/index.js';
containers/VisibleTodoList.js
import { TodoList } from '../components/index.js';
components/TodoList/TodoList.js
export default function TodoList({ todos, onTodoClick }) { ... }
TodoList is now undefined. I believe it may have something to do with the fact that I have some sort of circular issue.
The thing is, if inside containers/VisibleTodoList.js I import using the following method, everything works fine.
import TodoList from '../components/TodoList/TodoList.js';
What is so special that breaks the import, if I try to import using a 'middleman' (the components/index.js file).
Full code
I have created a CodeSandbox that contains my full code, as it stands in my application. The application is pretty simplistic, but more complicated than I have outlined here.
https://codesandbox.io/s/m54nql1ky9
The problem is caused by the order of exports in your components/index.js file.
export { default as App } from './App/App.js';
export { default as TodoList } from './TodoList/TodoList.js';
Since App.js imports VisibleTodoList which needs to import TodoList and pass it to the redux connect function before it can export itself, you end up with a conflict.
I'm not sure if this is a implementation quirk of babel, or if this is a logical result from how the ES import spec is defined.
In any case, changing the order of exports fixes the bug.
export { default as TodoList } from './TodoList/TodoList.js';
export { default as App } from './App/App.js';
As a rule of thumb, if you can't refactor your files to avoid the import loop, you should put the outer layer component last in the list, since it might rely on imports higher up in the list.
Full working codesandbox here: https://codesandbox.io/s/74mlwnwyy1
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.
I am using Styled Components but getting a problem trying to style a component of my own creation.
It is exported in the normal way from a separate npm package.
Package
FormComponents.js
export SubmitButton from './_submit-button';
_submit-button.js
export styled.button`somestyles`;
components.js
import * as Form from './form-components';
export { Form }
index.js
import * as Components from './components'
export const FormComponents = Components.Form;
Then in my actual component I want to use it I have:
import { FormComponents as Form } from 'packagename';
const Button = styled(Form.SubmitButton)...
and I'm receiving the error cannot read SubmitButton of undefined.
If however I put the const Button = ... in the render method, it finds everything fine. So I assume that the component isn't being instantiated until React kicks in.
It's possible with other elements however, for example if I change it to use:
const Button = styled(Link)
(Link from react-router-dom)
it finds it fine.
Am I exporting wrong?