Using React and Web Components - javascript

In our project we are using React and Web Components to develop reusable UI components (which in turn will be used by various dev teams internally). Components are developed in React and registered as custom HTML elements through Web Components. We need a way through which we can define the props in HTML custom tag and access all the props in our React Component.
The HTML would be like
<custom-element props1='pageInfo' props2='mediaInfo'></custom-element>
pageInfo and mediaInfo will be JS Objects which will be declared in global window scope or they can be inside some other NameSpace/Object, in that case the HTML would be something like
<custom-element props1='NS.pageInfo' props2='NS.mediaInfo'></custom-element>
OR
<custom-element props1='NS.Page.pageInfo' props2='NS.Media.mediaInfo'></custom-element>
So, we need a way to get all the props defined in the HTML and resolve them as Objects and pass it on to ReactDOM.render
Currently the code to render and register custom element is,
class RegComponent extends HTMLElement {
constructor() {
super();
}
createdCallback() {
ReactDOM.render(<App props1={eval(this.getAttributes('props1'))}/>, this);
}
}
document.registerElement('custom-element', RegComponent);
We want to get rid of eval and all the declared props should be fetched from HTML and passed on to ReactDOM.render. Looking for something like,
ReactDOM.render(<App {getAllProps()}/>, this);
where getAllProps() should return all the props name & their value. Remember that I'm using ES6. Any help would be appreciated!

What about instead of using JSX:
ReactDOM.render(<App props1={eval(this.getAttributes('props1'))}/>, this);
Use React directly, with an adapter transforming attributes into props:
ReactDOM.render(React.createElement(App, {...getAllProps(this.attributes)}), this);
function getAllProps(attributes) {
var props = {};
for (var i = 0; i < attributes.length; i++) {
props[attributes[i].nodeName] = attributes[i].nodeValue;
}
return props;
}

If getAllProps() returns an object, and each property in the object is the prop you wanted, you should only need to update you render to use the spread operator(...). This will deconstruct your object so that each property is passed to the App as a prop.
Here is what it would look like:
ReactDOM.render(<App {...getAllProps()}/>, this);

Related

What's the purpose of using classes in React?

I mostly see JavaScript use classes as a constructor as following:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
What's the reason React uses classes without using the contructor() function, such as following? I don't see classes being used to create instances.
class App extends Component {
render() {
return (
<div className="app-content">
</div>
)
}
}
Right now you should use classes in React if you need to use "advanced" component lifecycle methods like shouldComponentUpdate() or such.
Previously class components were used to handle local state in them. Right now we have Hooks API which allows to use state in a more elegant way and without need of class components.
If you want more details, you can read the article by Dan Abramov: How Are Function Components Different from Classes?.
Regardless your example, you're right, this code:
class App extends Component {
render() {
return (
<div className="app-content">
</div>
)
}
}
can be written as:
function App() {
return <div className="app-content"></div>
}
What's the reason React uses classes without using the contructor() function
From the JavaScript class doc:
If you do not specify a constructor method, a default constructor is used.
So a constructor exists for every class whether a constructor method is specified or not.
I don't see classes being used to create instances.
React components implemented as classes get instantiated by React as part of the rendering process.
Specifically, in the new React Fiber creating an instance of a React class component happens on this line of the source code.
But yes, #vicondin is right that the simple component from the question can be implemented as a function component, that class components used to be the only way to maintain state, implement lifecycle methods, etc., and that the new Hooks makes it possible to...
use state and other React features without writing a class.
In React, state is used in a React class component. There you can set initial state in the constructor of the class, but also access and update it with this.state and this.setState, because you have access to the class instance by using the this object.
If you use class in React component, even without using constructor() you can set initial state like below:
class App extends Component {
state = {
hello: 'Hello'
};
onClickHello = value => {
this.setState({ hello: 'Why did you clicked?' });
};
render() {
return (
<div className="app-content" onClick={this.onClickHello}>
{this.state.hello}
</div>
)
}
}
Another advantage is you can make use of all the React lifecycle methods
Update: After React16, you can use the lifecycle events even in function components using react hooks
Also biggest reason is the handling of state and lifecycle(componendDidMount ..etc) , class can do everything functions can , but at the cost of readability and statelessness . But in the most cases I rarely use classes only if I need a complex parent component with lifecycle

React access DOM element instead of object thru refs

I'm trying to access DOM element in react because it need for third party library.
And I'm able to do it with refs for built in react elems.
Like <div ref={this.someRef} <span ref={this.otherRef} etc.
And I can access DOM elem thru this.someRef.current
But when I'm trying to do same trick for custom elements <SomeCustomElem ref={this.anotherRef}, this.anotherRef.current returns me an object of values and I dont see any way to access DOM elem with custom components.
Is there any chance to get access to DOM of custom elem?
You can use react-dom to access any type of DOM element which is shipped with react.
To access the DOM pass a ref with the react element and latter access it with findDOMNode method.
Example:
import ReactDOM from 'react-dom';
...
let reactElement = ReactDOM.findDOMNode(this.refs.refName)
...
<Component ref='refName'/>
This depends on what kind of component SomeCustomElem is.
For <SomeCustomElem ref={this.anotherRef}/>, ReactDOM findDOMNode can be used:
findDOMNode(this.anotherRef.current);
This cannot be done if SomeCustomElem is functional component. Neither ref nor findDOMNode will work on it.
ReactDOM.findDOMNode(<Any React Element>) //Returns a DOM node
//eg. Using "this" inside a react el.
ReactDOM.findDOMNode(this).scrollIntoView()
Make sure to try-catch it as it might return null
findDOMNode is suggested to avoid:
findDOMNode is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.
You can forward ref instead.
But just think about that in a way "ref links you to given component". So maybe you just use separated prop like that:
class CustomComponent extends Component {
render() {
return (<div ref={this.props.outerDivRef}> .... </div>);
}
}
class Parent extends Component {
constructor() {
this.innerRef = React.createRef();
}
render() {
return (<CustomComponent outerDivRef={this.innerRef} />);
}
}
Besides last pattern is older than ref forwarding feature but it looks like more flexible one.

Getting data attribute of html element in react.js context

The CMS passes a variable as data-rest-url attribute to the React.js App:
<div id="reactjs-root" data-rest-url="http://my-ip-addess:8080/Rest-api-here">...</div>
If I add jQuery to my React.js App, then I can simply:
componentWillMount() {
const $reactRoot = $('#reactjs-root');
const restUrl = $reactRoot.attr('data-rest-url');
}
But adding jQuery just for this? How would you pass some variable from a CMS to your Single Page React App and read / parse / get it with react.js?
Consider passing your data attributes to your component as props instead of hard coding the root element ID within the component itself.
Rendering:
var rootElement = document.getElementById('reactjs-root');
ReactDOM.render(
<YourComponent resturl={rootElement.getAttribute('data-rest-url')}></YourComponent>,
rootElement
);
Within the component you can access the injected url:
componentWillMount() {
console.log(this.props.resturl)
}
This makes for a more reusable component that is decoupled from a specific element ID.
const reactRoot = document.getElementById('reactjs-root');
const restUrl = reactRoot.getAttribute('data-rest-url');
Also, avoid using $ in your variable name. You're likely to run into a lot of libraries that conflict with the $ you have used as a variable.

Is it possible to pass context into a component instantiated with ReactDOM.render?

TL;DR Given the following example code:
ReactDOM.render(<MyComponent prop1={someVar} />, someDomNode);
Is it possible to manually pass React context into the instance of MyComponent?
I know this sounds like a weird question given React's nature, but the use case is that I'm mixing React with Semantic UI (SUI) and this specific case is lazy-loading the contents of a SUI tooltip (the contents of the tooltip is a React component using the same code pattern as above) when the tooltip first displays. So it's not a React component being implicitly created by another React component, which seems to break context chain.
I'm wondering if I can manually keep the context chain going rather than having components that need to look for certain data in context AND props.
React version: 0.14.8
No. Before react 0.14 there was method React.withContext, but it was removed.
However you can do it by creating HoC component with context, it would be something like:
import React from 'react';
function createContextProvider(context){
class ContextProvider extends React.Component {
getChildContext() {
return context;
}
render() {
return this.props.children;
}
}
ContextProvider.childContextTypes = {};
Object.keys(context).forEach(key => {
ContextProvider.childContextTypes[key] = React.PropTypes.any.isRequired;
});
return ContextProvider;
}
And use it as following:
const ContextProvider = createContextProvider(context);
ReactDOM.render(
<ContextProvider>
<MyComponent prop1={someVar} />
</ContextProvider>,
someDomNode
);
In React 15 and earlier you can use ReactDOM.unstable_renderSubtreeIntoContainer instead of ReactDOM.render. The first argument is the component who's context you want to propagate (generally this)
In React 16 and later there's the "Portal" API: https://reactjs.org/docs/portals.html

What actually happens when React component returns?

I have noticed a difference between the data before returning and after a return of a component.
class AComponent extends Component {
render() {
const body = <BComponent crmStatus={...}/>
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = <article className='large'>...</article>
debugger // log resultRender on the left
return resultRender
}
}
My former question was going to be "How to read rendered component's className?", but I have split the questions as answering what is actually happening and why is it like that really started to bug me and might even give me hints to solve my problem.
So the question is:
What is actually happening to the component and why is it like that? I can have really complicated logic in my render() function, but I guess working with the components isn't that easy.
const headerContact = isContactInCRM ? <p>..</p> : <div>..</div>
const headerCallBtnsOrInfo = isSipEnabled && <div>..buttons..</div>
const callTimer = callDuration && <span>{callDuration}</span>
const footerNotes = <footer>..</footer>
const someImportedComponent = <MyComponent />
const resultRender = <section>
{headerContact}
{headerCallBtnsOrInfo}
{callTimer}
{footerNotes}
{someImportedComponent}
</section>
// there is a difference in data between headerContact and someImportedComponent
// when traversing the resultRender's tree in console
Before answering the question, it's worth to look at what is JSX. It just provides syntactic sugar for the React.createElement(component, props, ...children) function.
<div>
<MyComponent/>
</div>
As an example, above JSX snippet will be transformed to following JavaScript code in the compilation process.
React.createElement(
"div",
null,
React.createElement(MyComponent, null)
);
You can try out this using Babel online repl tool. So if we rewrite your example code using normal JavaScript (after compiling JSX), it will be something like this.
class AComponent extends Component {
render() {
const body = React.createElement(BComponent, { crmStatus: '...' });
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = React.createElement('article',{ className: 'large' }, '...' );
debugger // log resultRender on the left
return resultRender
}
}
By looking at above code, we can understand that <BComponent crmStatus={...}/> doesn't create a new object of BComponent class or call render method of BComponent. It just create a ReactElement with BComponent type and crmStatus prop. So what is a ReactElement? ReactElement is a pain JavaScript object with some properties. I recommend you to read this post from official React blog to get an in-depth understanding of React components, elements, and instances.
An element is a plain object describing a component instance or DOM node and its desired properties. It contains only information about
the component type (for example, a Button), its properties (for
example, its color), and any child elements inside it.
Basically, what you have printed in the console is two React elements in different types. The left one is describing DOM node with type 'article' and the right one is describing BComponent type React component instance. So simply you can't expect them to be the same.
Then where does React create an instance of BComponent? Actually, this happens internally in the React code. Usually, we don't have access to these instances or what return by their render methods in our application code.
However, React still provide an escape hatch called 'refs' which you can explicitly access instances of child components. You might be able to use that approach to solve your original problem.
Hope this helps!

Categories