Why doesn't React provide a transpiler? - reactjs

I'm learning about implementing React projects without create-react-app.
In every example I've studied, Babel is used to transpile JSX into JavaScript.
It seems strange to me that the React team would rely entirely on a third-party tool for their framework to function. (I am aware one can utilize the React library by writing "vanilla" JavaScript code, but that is not the common practice.)
There had to be a way to transpile React prior to the React team relying on Babel
Is there no other way to transpile JSX into JavaScript?
Why doesn’t the React team provide a tool to transpile JSX into JavaScript?

I will try to answer both questions. Since I write various code generators, I think my reasoning will be useful.
Why doesn't React provide a transpiler?
I believe that the main reason for this is not even so much that JXS appeared earlier. The main problem with the code now is its standards, which are still not fully supported by browsers. Thus, Babel is needed primarily for interpreting JS and only then JSX. And since he is so needed, why not delegate this responsibility to it.
Is there no other way to transpile JSX into JavaScript?
On the one hand - no, but not all that bad. I think the React team could quickly write their own transpiler. I say this because although there are some difficulties, they are so insignificant that it is not possible or too hard. Of course, these words can be perceived as unsubstantiated, so I chose a middle ground between writing a transpiler here and giving an example of code.
Of course, code transpilation is usually done at the development stage and not at runtime, but I will give exactly the transpilation runtime in the snippet. This is similar to when you wrap your JSX code in a script tag with type="text/babel",
and for this also includes the babel file
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
In order to get rid of goal, you can make another type of script and throw out babel.
Here is an example that I will try to create and I came up with a new type of script "text/react" and everything so that it does not start ahead of time with errors:
<script type="text/react">
const App = () => {
const clickHanler = ()=> {
alert("you clicked me")
}
return (
<button onClick={clickHanler} style={{color:'green'}}>Click me</button>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
</script>
Now you need to go through a few steps.
1 Find this script on the page.
2 Transpile it.
3 Run for execution.
To do this, in another script, I will place several functions, very simplified.
window.addEventListener('DOMContentLoaded', function () {
return transformScriptTags();
}, false);
We need to catch the moment when we can start transpiling by running transformScriptTags.
function disableScriptTags() {
window.removeEventListener('DOMContentLoaded', transformScriptTags);
}
We will eventually disable this handler.
The transpiler function will contain:
1 The way to extract tags, in fact, I used recursion for this, but the given code will be without it.
2 Next, we transform the result into an object tree with the structure:
{
tagName : ''
children : [
...
],
attributes : {}
}
3 Then we recursively put everything together in also tree like this:
React.createElement(
'h1',
{className: 'greeting'},
'Hello world'
);
4 We replace in the old script and insert it as a new one.
That's all, here's an example:
function transformScriptTags(scriptTags) {
//https://stackoverflow.com/questions/68607607/why-my-state-is-not-re-rendering-when-my-state-changes-in-this-reducer-in-redux
const code = document.querySelector('#code');
const oldScript = document.querySelector('script[type="text/react"]');
const reTag = /<(?:([A-Za-z0-9]*) ([^>]*)>(.*?)<\/\1>|[A-Za-z0-9]* [^>]*\/>)/;
const unwrap = e => e.match(/^\{.*\}$/) ? e.slice(1, -1) : e;
const wrapText = e => e.match(/^\{.*\}$/) ? e : `'${e}'`;
const ObjectToReact = (json) =>{
const result = ''
const {tagName, attributes, children} = json;
return `React.createElement(
'${tagName}'${!attributes || Object.entries(attributes).length === 0
? ', null'
: `, {${Object.entries(attributes).reduce((acc,[key, value]) => acc += `${key}:${unwrap(value)},`, '')}}`}
${children ? children.map(e => e.match(reTag) ? `,${ObjectToReact(e)}`: `,${wrapText(e)}` ) :''})`
}
const allScriptText = oldScript.innerHTML
const matched = allScriptText.match(reTag)
const element = {
tagName : matched[1],
children : [matched[3]],
attributes : matched[2].split(' ')
.reduce((acc,e) => ({...acc, [e.split('=')[0]]:e.split('=')[1]}), {})
}
const reactElement = ObjectToReact(element)
const newScriptText = allScriptText
.replace(matched[0], reactElement)
.replace('<App />', 'App()')
const newScript = document.createElement('script');
newScript.innerHTML = newScriptText;
disableScriptTags();
document.body.removeChild(oldScript);
document.body.appendChild(newScript);
code.textContent=newScriptText;
}
function disableScriptTags() {
window.removeEventListener('DOMContentLoaded', transformScriptTags);
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin ></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin ></script>
<div id="root"></div>
<pre id="code"></pre>
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', function () {
return transformScriptTags();
}, false);
</script>
<script type="text/react">
const App = () => {
const clickHanler = ()=> {
alert("you clicked me")
}
return (
<button onClick={clickHanler} style={{color:'green'}}>Click me</button>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
</script>

The underlying reason is fairly simple, JSX predates React. JSX was made public along with React, but originally it was used (in a slightly different form) inside Facebook before React ever came along.
As to why the JSX team chose to use Babel, while this is subjective speculation, building a good transpiler is really hard, and at that time Babel was already very common. Adding new functionality by integrating with Babel is considerably less work than building a transpiler from scratch. Likewise, it's less effort for the library consumers: adding a new plugin to your existing setup is easier than integrating with a whole separate toolchain.
From the planning perspective, Facebook has contributed both to Babel's finances and the project itself considerably, reducing worries that the project might simply disappear from underneath them.

Related

I want to write other languages code in react js

I am working on building a website.
I made all things, but now I'm stuck in adding code to the website.
I want to put some codes inside the JSX component but it is having some problems with adding { <<these types of symbols.
Is there any way I can write the C++ code or C code inside the react element?
import React from 'react'
const Template = () => {
return (
<div>
<h1></h1>
</div>
)
}
export default Template
The JSX won't appreciate the "{ <<", but if you want a quick in on this, you may try something like this -
const SomeCode = ()=><code>{`#include <stdio.h>;`}</code>
That might not be sufficient - you may need proper highlighting with specific programming language, formatting, etc. for what you might be building. But just for getting literals such as << working with JSX - you may take the above example as base.
In JSX, which is what react usees, brackets will be parsed.
Therefore, strings should be inside {`content`}, or you can define that code as a string, and place it inside jsx as below
const SomeComponent = ()=>{
const codeSnippet = `{ << whatever code blahblah`
return <div>
{codeSnippet}
</div>
}

Modal Enzyme mount unit test: MutationObserver is not defined [duplicate]

I wrote a script with the main purpose of adding new elements to some table's cells.
The test is done with something like that:
document.body.innerHTML = `
<body>
<div id="${containerID}">
<table>
<tr id="meta-1"><td> </td></tr>
<tr id="meta-2"><td> </td></tr>
<tr id="meta-3"><td> </td></tr>
<tr id="no-meta-1"><td> </td></tr>
</table>
</div>
</body>
`;
const element = document.querySelector(`#${containerID}`);
const subject = new WPMLCFInfoHelper(containerID);
subject.addInfo();
expect(mockWPMLCFInfoInit).toHaveBeenCalledTimes(3);
mockWPMLCFInfoInit, when called, is what tells me that the element has been added to the cell.
Part of the code is using MutationObserver to call again mockWPMLCFInfoInit when a new row is added to a table:
new MutationObserver((mutations) => {
mutations.map((mutation) => {
mutation.addedNodes && Array.from(mutation.addedNodes).filter((node) => {
console.log('New row added');
return node.tagName.toLowerCase() === 'tr';
}).map((element) => WPMLCFInfoHelper.addInfo(element))
});
}).observe(metasTable, {
subtree: true,
childList: true
});
WPMLCFInfoHelper.addInfo is the real version of mockWPMLCFInfoInit (which is a mocked method, of course).
From the above test, if add something like that...
const table = element.querySelector(`table`);
var row = table.insertRow(0);
console.log('New row added'); never gets called.
To be sure, I've also tried adding the required cells in the new row.
Of course, a manual test is telling me that the code works.
Searching around, my understanding is that MutationObserver is not supported and there is no plan to support it.
Fair enough, but in this case, how can I test this part of my code? Except manually, that is :)
I know I'm late to the party here, but in my jest setup file, I simply added the following mock MutationObserver class.
global.MutationObserver = class {
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
};
This obviously won't allow you to test that the observer does what you want, but will allow the rest of your code's tests to run which is the path to a working solution.
I think a fair portion of the solution is just a mindset shift. Unit tests shouldn't determine whether MutationObserver is working properly. Assume that it is, and mock the pieces of it that your code leverages.
Simply extract your callback function so it can be tested independently; then, mock MutationObserver (as in samuraiseoul's answer) to prevent errors. Pass a mocked MutationRecord list to your callback and test that the outcome is expected.
That said, using Jest mock functions to mock MutationObserver and its observe() and disconnect() methods would at least allow you to check the number of MutationObserver instances that have been created and whether the methods have been called at expected times.
const mutationObserverMock = jest.fn(function MutationObserver(callback) {
this.observe = jest.fn();
this.disconnect = jest.fn();
// Optionally add a trigger() method to manually trigger a change
this.trigger = (mockedMutationsList) => {
callback(mockedMutationsList, this);
};
});
global.MutationObserver = mutationObserverMock;
it('your test case', () => {
// after new MutationObserver() is called in your code
expect(mutationObserverMock.mock.instances).toBe(1);
const [observerInstance] = mutationObserverMock.mock.instances;
expect(observerInstance.observe).toHaveBeenCalledTimes(1);
});
The problem is actually appears because of JSDom doesn't support MutationObserver, so you have to provide an appropriate polyfill.
Little tricky thought may not the best solution (let's use library intend for compatibility with IE9-10).
you can take opensource project like this one https://github.com/webmodules/mutation-observer which represents similar logic
import to your test file and make global
Step 1 (install this library to devDependencies)
npm install --save-dev mutation-observer
Step 2 (Import and make global)
import MutationObserver from 'mutation-observer'
global.MutationObserver = MutationObserver
test('your test case', () => {
...
})
You can use mutationobserver-shim.
Add this in setup.js
import "mutationobserver-shim"
and install
npm i -D mutationobserver-shim
Since it's not mentioned here: jsdom has supported MutationObserver for a while now.
Here's the PR implementing it https://github.com/jsdom/jsdom/pull/2398
This is a typescript rewrite of Matt's answer above.
// Test setup
const mutationObserverMock = jest
.fn<MutationObserver, [MutationCallback]>()
.mockImplementation(() => {
return {
observe: jest.fn(),
disconnect: jest.fn(),
takeRecords: jest.fn(),
};
});
global.MutationObserver = mutationObserverMock;
// Usage
new MutationObserver(() => {
console.log("lol");
}).observe(document, {});
// Test
const observerCb = mutationObserverMock.mock.calls[0][0];
observerCb([], mutationObserverMock.mock.instances[0]);
Addition for TypeScript users:
declare the module with adding a file called: mutation-observer.d.ts
/// <reference path="../../node_modules/mutation-observer" />
declare module "mutation-observer";
Then in your jest file.
import MutationObserver from 'mutation-observer'
(global as any).MutationObserver = MutationObserver
Recently I had a similar problem, where I wanted to assert on something that should be set by MutationObserver and I think I found fairly simple solution.
I made my test method async and added await new Promise(process.nextTick); just before my assertion. It puts the new promise at the end on microtask queue and holds the test execution until it is resolved. This allows for the MutationObserver callback, which was put on the microtask queue before our promise, to be executed and make changes that we expect.
So in general the test should look somewhat like this:
it('my test', async () => {
somethingThatTriggersMutationObserver();
await new Promise(process.nextTick);
expect(mock).toHaveBeenCalledTimes(3);
});

Replace string with JSX tags

I'm working with ReactJS and try to wrap specific words/phrases in JSX tags. I have a data.jsx where I have an array with objects like message: "{span}Hello{/span} what's up?". Now I'd like to replace {span}{/span} with the tags. Sadly I can't use .replace('{span}', <span>) for this task. Is there any way to do this?
This might be more complicated if I want to use {span}{/span} many times in the same string. But would be great if there's a way.
You may use dangerouslySetInnerHTML*:
* Use it with caution: if its content comes from user input, you'll have a severe security breach.
const obj = {
message: "{strong}Hello{/strong} what's {em}up{/em}?"
};
const content = obj.message
.replace(/{/g, '<')
.replace(/}/g, '>');
ReactDOM.render(
React.createElement('span', { dangerouslySetInnerHTML: { __html: content } }),
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
</div>
Assign your span data as Component and then you can use that as tag after like below,
let CustomTag = `${span}`;
Then use it as <CustomTag>Your data here..</CustomTag>.
Note: Component name first letter should be capitalised.
It's not safe to use dangerouslySetInnerHTML like was mentioned previously and I'm not agree with Sophie also in some way.
Parsing JSX at runtime is error-prone and slow, and can easily have security implications – it wouldn't inherently be any safer than dangerouslySetInnerHTML is. by Sophie Alpert (here)
So, I decided to use this package for this task - regexify-string
npm install --save regexify-string
regexifyString({
pattern: /\[.*?\]/gim,
decorator: (match, index) => {
return (
<Link
to={SOME_ROUTE}
onClick={onClick}
>
{match}
</Link>
);
},
input: 'Some initial string with [link]',
});

onClick not being called ReactJS

Before downvoting, I've been through plenty of other solutions on SO around this same issue and can't find an answer that resolves this issue.
I'm having trouble trying to get the onClick attribute to fire off my function, here's a piece from the problematic component:
Constructor:
constructor() {
super();
this.state = {
submissionFormCount: 0
}
this.addToSubmissionFormCount = this.addToSubmissionFormCount.bind(this);
}
render:
<div className="row">
<div className="col s12 m12 l12">
<h5 onClick={this.addToSubmissionFormCount} style={beatSubmissionStyles.colorize}><span>(Plus) </span>add another beat</h5>
</div>
</div>
clickHandler:
addToSubmissionFormCount() {
alert('Here');
this.setState({
submissionFormCount: this.state.submissionFormCount++
});
}
I'm rendering the app from an Express server using 'react-dom/server'
Here's how I am rendering the component:
exports.beatSubmission = (req, res) => {
const appString = renderToString(<App type="beatSubmission"/>);
res.send(beatSubmissionTemplate({
body: appString
}))
}
I think you're only rendering your react components on the server side. The reason I think this is because of the following code you've copied:
exports.beatSubmission = (req, res) => {
const appString = renderToString(<App type="beatSubmission"/>);
res.send(beatSubmissionTemplate({
body: appString
}))
}
You're rendering the component to a string and shipping the string to the frontend as static HTML. While this will indeed give you properly rendered markup, it will result in a non-interactive app.
To have click handlers work, you also need to compile your JS, and include it for use on the frontend like this:
https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/server.js#L76
renderToString(<Html assets={webpackIsomorphicTools.assets()} store={store}/>));
There are a few ways to do this. You can manually add a script file with your JS pre-packaged for the frontend in your template or you can use Webpack Isomorphic Tools.
I suspect you read a react tutorial that lead you down the isomorphic (server/client side rendered) path. You can run react on the server, the client side or both. Running it on both takes some work, but results in an app that "feels" faster.
I can't see any problem with the code. However, the only thing that comes to my mind is if you're rendering the <h5> in a function of some sort, maybe mapping and array for example. If so, you need to define var self = this in the render function before the return, then use self.addToSubmissionFormCount.
P.S. I don't recommend using onClick handler in <h5> tag
Change your h5 into an anchor tag. So replace:
<h5 onClick={this.addToSubmissionFormCount} style={beatSubmissionStyles.colorize}><span>(Plus) </span>add another beat</h5>
with:
<a onClick={this.addToSubmissionFormCount} style={beatSubmissionStyles.colorize}><span>(Plus) </span>add another beat</a>
You can style your anchor anyway you want afterwards. For example if you want to not have a cursor when hovering then add cursor: none
You should not mutate the state of react.
Change:
this.state.submissionFormCount++
To:
this.state.submissionFormCount+1

How do I convert a string to jsx?

How would I take a string, and convert it to jsx? For example, if I bring in a string from a textarea, how could I convert it to a React element;
var jsxString = document.getElementById('textarea').value;
What is the process I would use to convert this on the client? Is it possible?
You can consider using the React attribute dangerouslySetInnerHTML:
class YourComponent{
render() {
someHtml = '<div><strong>blablabla<strong><p>another blbla</p/></div>'
return (
<div className="Container" dangerouslySetInnerHTML={{__html: someHtml}}></div>
)
}
}
Personally, I love to do it just like in the previous answer which recommends the usage of dangerouslySetInnerHTML property in JSX.
Just for an alternative, nowadays there is a library called react-html-parser. You can check it and install from NPM registry at this URL: https://www.npmjs.com/package/react-html-parser. Today's weekly download statistic for that package is 23,696. Looks a quite popular library to use. Even it looks more convenient to use, my self, still need more read and further consideration before really using it.
Code snippet copied from the NPM page:
import React from 'react';
import ReactHtmlParser, { processNodes, convertNodeToElement, htmlparser2 } from 'react-html-parser';
class HtmlComponent extends React.Component {
render() {
const html = '<div>Example HTML string</div>';
return <div>{ ReactHtmlParser(html) }</div>;
}
}
Here's how you can do it, without using dangerouslySetInnerHTML.
import React from "react";
let getNodes = str =>
new DOMParser().parseFromString(str, "text/html").body.childNodes;
let createJSX = nodeArray => {
return nodeArray.map(node => {
let attributeObj = {};
const {
attributes,
localName,
childNodes,
nodeValue
} = node;
if (attributes) {
Array.from(attributes).forEach(attribute => {
if (attribute.name === "style") {
let styleAttributes = attribute.nodeValue.split(";");
let styleObj = {};
styleAttributes.forEach(attribute => {
let [key, value] = attribute.split(":");
styleObj[key] = value;
});
attributeObj[attribute.name] = styleObj;
} else {
attributeObj[attribute.name] = attribute.nodeValue;
}
});
}
return localName ?
React.createElement(
localName,
attributeObj,
childNodes && Array.isArray(Array.from(childNodes)) ?
createJSX(Array.from(childNodes)) :
[]
) :
nodeValue;
});
};
export const StringToJSX = props => {
return createJSX(Array.from(getNodes(props.domString)));
};
Import StringToJSX and pass the string in as props in the following format.
<StringToJSX domString={domString}/>
PS: I might have missed out on a few edge cases like attributes.
I came across this answer recently and, it was a good deal for me. You don't need to provide a string. Returning an array of JSX elements will do the trick.
We can store JSX elements in JavaScript array.
let arrUsers = [<li>Steve</li>,<li>Bob</li>,<li>Michael</li>];
and in your HTML (JSX) bind it like,
<ul>{arrUsers}</ul>
As simple as it is.
If you consider string
<div>Hello World</div>
If we are very strict, this actually is the valid JSX. The question is how to compile this JSX string into React code.
Easiest and the recommended way is to download some library like Babel and use it to transform the code. Babel can run in the Browser like the repl does.
It is also possible to transform JSX to other formats, but in this case you have to find a compiler or create one yourself.
The steps to create the JSX => React transformation yourself is:
transform the code string into AST representation
parse the AST and output code back to string
So you need somekind of AST parser like espree supporting JSX and then you can create a code which walks the AST tree and outputs something, like React -code out of it.
The AST tree of JSX data consists of normal JavaScript AST together with JSX nodes. The parser should walk through the tree and transform the JSX nodes into normal JavaScript code.
If you compile to React and encounter a JSX node with tag "div" you should compile that into React.createElement("div",... call with attributes and subnodes found under that AST node inserted as parameters of that call.
I have created a small AST Walker, which can process AST tree, ASTWalker, which can be used to transform the AST tree into some output format, like React or DOM.
On-line example of how to use it is here:
http://codepen.io/teroktolonen/pen/KzWVqx?editors=1010
The main code looks like this:
// here is the JSX string to parse
var codeStr = "<div>Hello world</div>";
var walker = ASTWalker({
defaultNamespace: "react",
});
// compile AST representation out of it.
var rawAST = espree.parse(codeStr, {
ecmaVersion: 6,
sourceType: "script",
// specify additional language features
ecmaFeatures: {
// enable JSX parsing
jsx: true
}
});
// then you can walk the walk to create the code
walker.startWalk( rawAST, {} );
var code = walker.getCode();
console.log(code);
document.getElementById("sourceCode").innerHTML = code;
DISCLAIMER: The library is not intented for compiling into React. It is mostly used with defaultNamespace: "DOM", using it to compile into plain JavaScript + DOM representation. Trying anything more complicated than simple tags may result as an error.
The important thing is to notice that React is not only possible output format for JSX.
I've been using html-to-react with some success (self closing tags cause a problem though, but a fix is in the pull requests...) to parse markup strings as DOM like objects, and in turn React elements. It's not pretty, and if you can avoid it, do so. But it gets the job done.
html-to-react at github: https://github.com/mikenikles/html-to-react
Use React-JSX-Parser
You can use the React-JSX-Parser library dedicated for this.
npm install react-jsx-parser
here is the repo
html-react-parser is what you need.
import parse from 'html-react-parser';
import React from 'react';
export default function YourComponent() {
someHtml = '<div><strong>blablabla<strong><p>another blbla</p/></div>'
return (
<div className="Container">{parse(someHtml)}</div>
)
}
Here's a little utility component for this:
const RawHtml = ({ children="", tag: Tag = 'div', ...props }) =>
<Tag { ...props } dangerouslySetInnerHTML={{ __html: children }}/>;
Sample usage:
<RawHtml tag={'span'} style={{'font-weight':'bold'}}>
{"Lorem<br/>ipsum"}
</RawHtml>
First you can change it to a non-string array and then use it as JSX
class ObjReplicate extends React.Component {
constructor(props) {
super(props);
this.state = { myText: '' };
}
textChange=(e) =>{this.setState({ myText: e.target.value })};
render() {
const toShow = this.state.myText;
var i=1;
var allObjs=new Array;
while (i<100){
i++;
allObjs[i] = <p>{toShow}</p>; //non-sting array to use in JSX
}
return (
<div>
<input onChange={this.textChange}></input>
{allObjs}
</div>
);
}
}
ReactDOM.render(<ObjReplicate/>,document.getElementById('root'));
You need to use babel with preset react
npm install --save-dev babel-cli babel-preset-react
Add the following line to your .babelrc file:
{
"presets": ["react"]
}
Then run
./node_modules/.bin/babel script.js --out-file script-compiled.js
You can find more info here

Resources