Access to internal function from externally loaded HTML in React component - reactjs

I have the following use case.
Some HTML from a third party source is loaded inside my React component:
class MyComponent extends Component {
render() {
return (
<div
dangerouslySetInnerHTML={{ __html: this.props.externalHTML }}
/>
);
}
}
Inside the externally loaded HTML a click event exists for a specific span, which is supposed to call a callback function that exists in my application.
<span onclick="myCallback(param1='asd', param2=123, param3='asdas')">
Click me!
</span>
Where should I put this myCallback function?
If I place it inside the component class I get the following error when clicking the span, because as I understand the function is not visible to the externally loaded HTML: Uncaught ReferenceError: myCallback is not defined at HTMLSpanElement.onclick
My other idea was to add the function to the window object window.myCallback = ... inside my main index.js file to be loaded every time the app loads. This way it works but I have two issues.
My understanding is that this is not the correct React way to do it.
Whenever I click the span element the callback function is triggered twice and I cannot understand why.
Any suggestions?

Using "dangerouslySetInnerHTML" is ..."dangerous" as its name ^^, which is actually not pure React way, either.
However, If you have to do it, you can do something like this (take advantage of built-in jQuery inside React be default)
=====
EDITED VERSION FROM HERE: (use only 1 component)
export default class MyComponent extends Component {
componentDidMount() {
// using jQuery to manipulate DOM element form third-party source
// NB. you may think of using setTimeout here, to wait for the external source to be fully loaded here, of course it's not so safe
// but anyway, we are using "dangerouslySetInnerHTML" anyway => quite dangerous, though ^^
// setTimeout(function(){
$(document.findElementsByTagName("span")[0]).click(function(e){
// or perhaps $("#spanID").click if you can, just be careful between DOM element and react component
e.preventDefault();
// DO SOMETHING HERE, just like you do in the window.onload function
// or maybe you also need to get param values by getting $(this).data("...") or $(this).attr("ATTRIBUTE-NAME")
return false;
});
// });
}
render() {
return (
<div
dangerouslySetInnerHTML={{ __html: this.props.externalHTML }}
/>
);
}
}
=====
OLD ANSWER FROM HERE: (use 2 components)
ParentComponent:
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.callbackOnThisComponent = this.callbackOnThisComponent.bind(this);
}
callbackOnThisComponent(param1, param2, param3) {
// do whatever you like with the above params
}
render() {
return (
<ChildComponent triggerCallbackOnParent={this.callbackOnThisComponent} />
);
}
}
ChildComponent:
export default class ChildComponent extends Component {
componentDidMount() {
// using jQuery to manipulate DOM element form third-party source
let that = this;
// NB. you may think of using setTimeout here, to wait for the external source to be fully loaded here, of course it's not so safe
// but anyway, we are using "dangerouslySetInnerHTML" anyway => quite dangerous, though ^^
$(document.findElementsByTagName("span")[0]).click(function(e){
// or perhaps $("#spanID").click if you can, just be careful between DOM element and react component
e.preventDefault();
that.props.triggerCallbackOnParent(param1, param2, param3);
// or maybe you need to get param values by getting $(this).data("...") or $(this).attr("ATTRIBUTE-NAME")
return false;
}, that);
}
render() {
return (
<div
dangerouslySetInnerHTML={{ __html: this.props.externalHTML }}
/>
);
}
}
I just use the main React's idea, which is passing props downward to children components, and when you want to trigger a function upward from child component, create a callback function on parent. For your or anyone else's reference, this is my demonstration on how to pass callback function from parent to multi-level-children components:
Force React container to refresh data
Re-initializing class on redirect
if this doesn't work yet, feel free to show me some error logs, thanks

Related

Creating A React Component That Can Be Shown With A Function Call (like react-toastify

Typically, when creating a reusable React component that we want to conditionally render, we'll either give it a prop to tell it whether or not to render itself:
function TheComponent(props) {
return(
props.isVisible?<div>...</div>:null
);
}
Or just render the whole component conditionally, from the outside:
function App() {
//...
return (
isVisible ? <TheComponent /> : null
);
}
Alternatively, if we want to make a component that we can show/hide from anywhere in our application - like a toast notification - we could wrap in a provider & make a custom hook to access its context; this would let us show/hide it from anywhere inside the provider, just by calling a function:
const App = () => (
<ToastProvider>
<OtherStuff />
</ToastProvider>
);
const OtherStuff = () => {
const { showToast } = useToast();
showToast();
return ...;
};
However, there's a really cool package, react-toastify, that I can't seem to wrap my head around how it's implemented. All you have to do is drop a <ToastContainer /> somewhere in your app, then from anywhere else, you can:
import { toast } from "react-toastify";
toast.info("this will show the component with a message");
Since this function can be called outside of a provider, I don't really understand how it's controlling the state of the component elsewhere in the tree. I've tried looking into its code, but as a React beginner, it's a bit over my head. I love the idea of a totally self-contained component that you can just stick somewhere in your app, and invoke by calling a function from anywhere. No Provider/wrapper or anything: just a function call, and out it pops.
Can someone help shed some light on how, fundamentally, a component like this could work? How can a function outside of a provider be controlling state inside another component?
Glancing at the react-toastify code you can see it’s using an event emitter pattern. The <ToastContainer /> listens for events that get dispatched (or emitted) when you call toast.info. When it gets an event it updates its internal state (presumably) to show the message.
TLDR: They're communicating indirectly through the eventManager, which exposes methods for 1) dispatching events and 2) registering listeners for those events.
It's similar to the way an onclick handler works in the DOM.
Here's a very rudimentary implementation of the basic pattern: It just appends a div to the document each time the button is clicked. (This isn't React- or toastify-specific. But it demonstrates the core idea.)
Notice that the button's click handler doesn't know anything about what happens. It doesn't append the div. It just emits an event via the EventBus instance described below.
The EventBus class provides an on method to register a listener. These are often called addEventListener or addListener, or they have an event-specific name like onClick, onChange, etc., but they all do the same basic thing: register a function to be invoked in response to an event. (This class is essentially a dumber implementation of react-toastify's eventManager.)
The on method adds the provided handler to an internal array. Then, when an event is fired (via emit) it just iterates over the array invoking each one and passing in the event information.
const container = document.getElementById('demo');
const button = document.querySelector('button');
class EventBus {
handlers = [];
on (handler) {
this.handlers.push(handler);
}
emit (event) {
this.handlers.forEach(h => h(event));
}
}
const emitter = new EventBus();
emitter.on((event) => {
container.innerHTML += `<div>${event}</div>`;
})
button.addEventListener('click', () => emitter.emit('Button Clicked'));
<button>Emit</button>
<div id="demo"></div>
With this setup you can add additional listeners to do other things without having to know where the event originates (the button click). The demo below is the same as above except it adds an additional handler to toggle "dark" mode.
Again, notice that the button doesn't know about dark mode, and the dark mode handler doesn't know about the button, and neither of them know about the div being appended. They're completely decoupled.
This is basically how the ToastContainer works with toast.info.
const container = document.getElementById('demo');
const button = document.querySelector('button');
class EventBus {
handlers = [];
on (handler) {
this.handlers.push(handler);
}
emit (event) {
this.handlers.forEach(h => h(event));
}
}
const emitter = new EventBus();
emitter.on((event) => {
container.innerHTML += `<div>${event}</div>`;
})
button.addEventListener('click', () => emitter.emit('Button Clicked'));
// add an additional handler
emitter.on(event => demo.classList.toggle('dark'));
.dark {
background: black;
color: white;
}
<button>Emit</button>
<div id="demo"></div>

Call Method on createClass result Should be simple

I would like to call a method on a random component after creating it but before rendering it. To perform child specific calculations for the parent prior to rendering the parent. A simple static should work.
class Container extends ReactWrapper{
render() {
const rClass = React.createClass(this.getCCArgs());
var newData = rClass.expectedUtilityFunction(data);
// render parent with new data.
return (<div {...this.props.data, ...newData}>
{rClass}
</div>);
};
};
Tried a number of ways and the utility method is always not found.
I could push things up the line logically and add a method to return the class used to create the react instance from the input data but I already have the react class instance.
React.createClass is deprecated and removed from React 16+. So I suggest stop tring to make it work.
Your code is good fit for High order component.
Below is sample code (I didn't tested it, so use it as hint only)
function WrappedComponent (Component, props) {
var newData = /* Here is good place for expectedUtilityFunction code. Don't put expectedUtilityFunction function into Component, put it here, in HOC body */
// render parent with new data.
return (<div {...props.data, ...newData}>
<Component/>
</div>);
}
And use HOC like this
class Container extends ReactWrapper{
constructor(props) {
supre(props);
// Assuming that this.getCCArgs() returns component
this.hoc = WrappedComponent (this.getCCArgs(), props);
}
render() {
return this.hoc;
}
}

Trying to render a new instance in ReactJS

As an example (real tried code)
I have a component of which I want to initiate a NEW instance for rendering.
import React, { Component } from 'react';
export default class TinyObject extends Component {
constructor(props) {
super(props);
console.log("TinyObject constructor");
}
render() {
console.log("TinyObject render");
return (
<div>HEY THIS IS MY TINY OBJECT</div>
);
}
}
Then in main App constructor I do the following:
var myTinyObject = new TinyObject();
var myArray = [];
myArray.push(myTinyObject);
this.state = {testing: myArray};
Then a created a function to render this:
renderTest()
{
const {testing} = this.state;
const result = testing.map((test, i) => {
console.log(test);
return {test};
});
}
And I call this from the App render function like this:
render() {
const { gametables, tableActive } = this.state;
console.log("render");
return <div><div>{this.renderTest()}</div></div>;
}
It runs, no errors.
I see console log of the following:
console.log("TinyObject constructor");
console.log(test);
But I don't see console log of the TinyObject render nor do I see the render output.
Thanks to lustoykov answer I got a little further
JSX: var myTinyObject = <TinyObject />;
works!
but in the real app I add a little more and don't know how to do it here.
return <GameTable key={'gt'+index} data={table} settings={this.settingsData} sendTableNetworkMessage={this.sendTableNetworkMessage} />
this is the way I was rendering; and I needed more instances of GameTable
now the question is; how do I add the arguments like data & settings to myTinyObject.
thanks for helping so far.
You don't manually instantiate react component, use JSX or createElement. For instance
via JSX
var myTinyObject = <TinyObject prop1={prop1} prop2={prop2} />;
via React.createElement
var myTinyObject = React.createElement(TinyObject, { prop1, prop2 }, null);
I would definitely check out some tutorials and how React works at a basic level. You aren't really going to call your react components like you would normally do in javascript since the render function returns jsx.
Fundamentally, React is what is called a single page application. That means that your browser will load up a single html file with a div. Now that div will be where React performs its magic by using Javascript to change stuff around.
It is easiest for me to think of React as a tree. You create these components that you place on the DOM or in your HTML and React will add and remove them downwards. For instance, take a look at this picture of twitter.
So first the Feed component is going to be put on the DOM. Then the Feed component will render the Tweet components. So as you can see the rendering goes in one direction, downwards.
Now, as you can see your render methods are not returning javascript. It is returning something that looks like HTML but we call it JSX. That means we want to render it a little differently with our react classes.
If we have a child component:
class Child extends React.Component {
render() {
return <h1>Hello, I am inside the parent component</h1>;
}
}
We can call the render method like this:
class Parent extends React.Component {
render() {
<Child /> //This is how I use the Child class
}
}
Now the reason why react is so performant is that the child cannot be re-rendered unless we do 1 of two things:
It is a component with a state and we call a method setState()
We pass down new props to a child component from the parent component
You can read about it here
Now the only way to get React to call that render function again is by doing those two things.

Get HTML tag name from React element?

Is it possible to get an HTML tag name from a React element (return from a component)? For example:
function Foo() {
return <span>Hello</span>;
}
The HTML tag name would be span. I know you can look into the type property of the React element, but it gets really difficult between SFC and normal components, and even harder when the component depth is rather large. For example:
function Bar() {
return <Foo />;
}
Should still return span.
No.
React Elements are a virtual construct, and they don't directly represent DOM elements. There are two types of React Elements - "regular" HTML DOM elements, and instantiations of a React Class. The first one has a "type" because it is extremely simple, stateless, and immutable, and is created only to render its corresponding DOM element (but don't confuse it with the DOM element itself!) An example of this would be <div>Foo</div> in JSX or React.createElement("div", null, "Foo") in JS.
However instantiations of a React Class don't have a "type" because they don't represent a typical DOM element. When we instantiate a React Class we call it a "Component" to identify it as a single instance of the Class, having a render method, encapsulated state, etc. It's impossible to retrieve the "type" of a Component because the actual DOM elements that it will render are completely dependent on its internal state and what the render method decides to return. An example of this would be a React Class defined somewhere and then instantiated with <Foo /> or React.createElement(Foo).
The only way to know what DOM elements a Component is actually rendering is to, well, mount and render it and see what it does. There is no "type" inherent to the component itself. You could conceivably mount a Component, use ref to capture the DOM element as it's rendered, and then use a callback function to pass this information back to parent(s).
var Child = React.createClass({
render: function() {
return <div ref={function(el){this.props.whatElementAmI(el)}.bind(this)} >Oh My!</div>
}
});
var Parent = React.createClass({
whatElementAmI: function(el) {
console.log(el.nodeName); // "DIV"
},
render: function() {
return <div><Child whatElementAmI={this.whatElementAmI.bind(this)} /></div>
}
});
https://facebook.github.io/react/docs/glossary.html#react-elements
Yes.
import { renderToString } from 'react-dom/server';
const Span = () => <span>hello</span>
renderToString(<Span />)
// "<span>hello</span>"
then use regex to get tag.
You can use React's Top-Level API to grab the DOM node. Two caveats: 1) this doesn't work on stateless functional components as you can't put refs on them, and 2) it only works if the component outputs a native DOM element, so it won't work in your second case where the component outputs another React component.
import ReactDOM from 'react-dom';
class MyComponent extends React.Component {
render() {
return <span>Hello</span>;
}
}
class Container extends React.Component {
componentDidMount() {
var domNode = ReactDOM.findDOMNode(this._component);
console.log(domNode.tagName); // "DIV"
}
render() {
return <MyComponent ref={(c) => this._component = c} />
}
}

How do I call the iScroll refresh methode in a parent react component?

I am struggling currenlty with iScroll in combination with reactJS.
This code samples are written in typescript.
I created a wrapper class for iScroll:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
var iScroll = require('iscroll');
...
componentDidMount() {
this._initializeIScrollInstance();
console.log(this);
}
_initializeIScrollInstance() {
setTimeout(() => {
let element = ReactDOM.findDOMNode(this);
const iScrollInstance = new iScroll(element, this.props.options);
this._iScrollInstance = iScrollInstance;
}, 100);
}
render() {
return (
<div style={ this.props.style } >
{ this.props.children }
</div>
)
}
}
Then I use it like this in my sidebar class for instance.
<ScrollWrapper>
<SidebarMenu toggle={ this.toggle.bind(this) } />
</ScrollWrapper>
The problem I am facing is when I toggle a menu inside my sidebar. This means the height changes so I need to call the refresh methode for iScroll.
But how can I achieve this?
Can I get the _iScrollInstance in my parent component?
Its possible to keep a state inside my sidebar and pass it down to the ScrollWrapper as a property and watch for it in componentReceiveProps.
But this sounds like a cheap solution to me.
Anyone maybe have a better solution for that kind of problem?
Based on your code :
<ScrollWrapper>
<SidebarMenu toggle={ this.toggle.bind(this) } />
</ScrollWrapper>
I see the child component action (toggle) needs to do something with the parent component (scroll). The idiomatic way to do this is for the parent to give the child component a function (callback) that the child calls when it wants something in the parent to change.
Note: The recommeded way is to use flux ... but thats a whole other topic (lookup redux which is what I use).

Resources