How to render a user defined react component? - javascript

In my electron application, I want to offer some kind of extensibility for the UI.
The users should be able to create a react component inside of a text editor inside of my application. Their code gets saved to a file. They also can define where the component should be placed inside my UI.
The problem is: I can't seem to figure out how to parse and inject the user-defined JSX in my existing react app.
I thought I try with babel transformSync, but I can't get it working. This is what I have so far:
const DynamicComponent = (props) => {
const reactCode = `
return (
<div>
<h1 >Hello, World!</h1>
</div>
)
`;
const transpiledCode = transform(reactCode, {
presets: ["#babel/preset-react"]
}).code;
console.log("transpiled", transpiledCode);
globalThis["React"] = React;
const Dynamic = new Function(`return ${transpiledCode}`)();
return <Dynamic />;
};
https://codesandbox.io/s/mobx-state-tree-todolist-3umd4?file=/components/TodoList.js
I'd like to have the "<h1>Hello, World!</h1>" rendered below my todo list.

This took sometime. Here are somethings to be aware of:
The #babel/core gives an invalid version error, so I used the #babel/runtime version since it's designed for use in the browser.
I found out that removing comments while using the #babel/core helped with automatic semicolon insertion problem with js.
You need to return your new DynamicComponent directly. Do not convert it to ReactElement by returning <Dynamic />.
See codesandbox here
const DynamicComponent = (props) => {
const reactCode = `
<div>
<h1>Hello, World!</h1>
</div>
`;
const transpiledCode = transform(reactCode, {
presets: [
"react",
{
comments: false
}
]
}).code;
globalThis["React"] = React;
return new Function(`return ${transpiledCode}`)();
};

Related

How can I use KaTeX in React project?

How can KaTex be used in a React project? I read through documentation on NPM and still don't get it.
I don't quite see how katex.render and katex.renderToString can be applied to React.
katex.render needs a DOM element to render in, you can get one with a useRef hook.
const KaTeXComponent = ({texExpression}) => {
const containerRef = useRef();
useEffect(() => {
katex.render(texExpression, containerRef.current);
}, [texExpression]);
return <div ref={containerRef} />
}

How can I dynamically import in a NextJs page data (array) not entire component

Hi all I followed the next tutorial
https://nextjs.org/docs/advanced-features/dynamic-import
and works great for components
But I need to fetch data dynamically.
Situation:
I have one simple component named MyItems that receives as props items which is a list of elements title, category.
I want to dynamically import these lists from typescript files stored in page-data/myitems/de|en.ts and so on
So these ts files export array after doing some calculations that is why i don't import json dynamically or search for other solution. I need them to have code and export an array
export default [{name: 'somename', title: somemagic()]
I have this page in pages/page
const Page = async ({ t, ...props }: Props) => {
const locale = props._nextI18Next.initialLocale;
const items = (await import(`../page-data/myitems/${locale}`)).default;
console.log(items); // HERE I SEE OUTPUT FINE
return (
<>
<Head>
<title>dynamic test</title>
</Head>
{/*#ts-ignore*/}
<MyPage items={items} />
</>
);
};
The error I get is that cannot return promise to react
So my understanding is that I cannot export async component.
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
That is all fine, so my question is howcan I solve this situation then ?
The goal is still the same (items are fetched fine now but the component is returned as promise due to async and rest of react magic fails)
I have solved this issue myself and am posting the solution for anyone facing the same issue.
So summarized
You still import dynamically one react component, but the "localized" react component relies on the common page. For example page-en.tsx imports page.tsx (see below)
This will be your NextJs main page:
import React from 'react';
import dynamic from 'next/dynamic';
const Page = ({ t, ...props }: Props) => {
const locale = props._nextI18Next.initialLocale;
const nt = scopedT(t, T_NAMESPACE);
const DynamicComponent = dynamic(() => import(`../page-data/mypage/${locale}`));
return (
<>
<Head>
<title>{nt('pageTitle')}</title>
</Head>
{/*#ts-ignore*/}
<DynamicComponent />
</>
);
};
and this will be your page-data/mypage/en|de|whateverlang.tsx
const En: React.FC = () => {
return <MyPage items={getItemsForLocale('en')} />;
};

react code splitting: is the argument for the import() function not a string

This is very bizarre. During my attempt at code-splitting, I encountered this:
const g = "bi";
const importStr = `react-icons/${g.toLowerCase()}`;
console.log(importStr);
console.log(importStr === `react-icons/bi`);
import(importStr).then(module => {
console.log(module);
});
import(`react-icons/bi`).then(module => {
console.log(module);
});
In the above code, if I import "importStr", then the import throws an error:
Uncaught (in promise) Error: Cannot find module 'react-icons/bi'
But if I directly import "react-icons/bi", then there is no issue. As you see,
importStr === `react-icons/bi`
Why and how do I fix this? I can't actually directly use "react-icons/bi" because g is dynamic and could take other values.
I quote from the following comment
Webpack performs a static analyse at build time. It doesn't try to infer variables which that import(test) could be anything, hence the failure. It is also the case for import(path+"a.js").
Because of the tree shaking feature webpack tries to remove all unused code from the bundle. That also means something similar could work
import("react-icons/" + g)
Edit: as per your suggestion I updating this to
import("react-icons/" + g.toLowerCase()+ '/index.js')
An easy way to implement code splitting in React is with lazy loading, as seen here (this might be easier than importing a dynamic string):
const OtherComponent = React.lazy(() => import('./OtherComponent'));
This implementation will load the bundle with OtherComponent only when it is first rendered.
Example from reactjs.org:
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
More info on React.lazy

Can't get MDX to work in a JavaScript Component. Just want Markdown to be turned to react components

Trying to make a react component with MDX that will turn markdown into react components. I want this so I can transform a blog article with embedded components (such as special article toolings and diagrams) from markdown stored in the DB into something presented on the page.
Been working on it for a few hours now and even my best attempts just result in literally nothing being displayed on the page. Where replacing the 'content' with a string displays.
I'm 100% baffled, lost and confused and really would appreciate help.
Original problem: https://www.reddit.com/r/reactjs/comments/mbdz7k/reactmarkdown_custom_component_declaration_how/
Working solution (but I am struggling to get it to work on my end). Provided by cmdq
https://codesandbox.io/s/dazzling-wescoff-ib8mv?file=/src/index.js
I've basically just moved the entire particular component file into being a JavaScript file but unfortunately I'm still running into many issues-- probably from lack of familiarality.
My current code is this in the file:
import ReactDOM from 'react-dom'
import renderToString from 'next-mdx-remote/render-to-string'
import hydrate from 'next-mdx-remote/hydrate'
import React, { Component } from 'react'
const exampleMdx = `
# h1
**foo**
<MyCustomComponent text="hell yeah" />
a
bar
`
const components = {
h1: (props) => <h1 style={{ color: 'tomato' }} {...props} />,
MyCustomComponent: ({ text }) => <marquee>{text}</marquee>,
}
const functionThing = async () => {
const mdxString = await renderToString(exampleMdx, {
components,
})
return hydrate(mdxString, { components });
}
export const ArticleTextTrial = () => {
let content = functionThing;
console.log(functionThing());
return (
<div className="articleMainTextSectionBody">{exampleMdx}
</div>
)
}

Passing component as a function argument

I have a function that take a component as argument, and return another, enhanced component:
import React from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Layout, DarkBar } from 'SharedComponents/Layouts';
const myCreationFunction = ({
component,
}) => {
const Route = (props) => {
// Some code here
return (
<Layout>
<div><Link to={props.path}>LinkTitleHere</Link></div>
{React.createElement(component, {
...props,
...someOtherPropsHere,
})}
</Layout>
);
}; // The error points here
const mapStateToProps = () => ({});
const enhance = compose(
connect(mapStateToProps, someActionsHere),
)(Route);
return enhance;
};
I invoke that function in this way:
import MyComponent from './MyComponent';
import myCreationFunction from './HOC/myCreationFunction';
const Route = myCreationFunction({
component: MyComponent,
});
When I run it in the development mode, it runs smoothly. But when trying to build the app using npm run build and going through webpack, I get:
Module parse failed: Unexpected token (35:47)
You may need an appropriate loader to handle this file type.
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
> var createListRoute = function myCreationFunction((_temp = _ref, _ref2 = <_Layouts.DarkBar>
| <_Breadcrumbs2.default />
| <_Layouts.RoundAddButton path={addPath} color="white" />
What am I doing wrong?
Edit #1
It seems that the <Link> is causing the problem. Removing it fixed the problem. Also, when trying to replace it with a tag, I get the same issue. Weird.
Edit #2
I have not resolved this issue because of lack of time. I was trying for 1 hour without any progress and decided to go with button tag and onClick method that uses history to push the new url.
It was and is really weird to me, that a Link or <a> tag can break something during the build process itself. I will definitely jump deeper into it in some free time.

Categories