React component is not working - javascript

I have a problem. So, I'm making react component and I need tooltip with button. Tooltip is working, but I can't place it where I want(I mean in the centre of the button and above that).
When I consoled log that, it's showing mne that e.target.offsetLeft and e.target.offsetTop are 0, but I gave it margin from both sides.
But actually when I place this code which have to place tooltip, then whole tooltip is not displayed:
tooltip.style.left = options.x + (options.w / 2) - (tooltip.offsetWidth / 2) + "px";
tooltip.style.top = (options.y - tooltip.offsetHeight - 10) + "px";
And it's my whole code:
import React from 'react';
import ReactDOM from 'react-dom';
import Style from 'style-it';
var Ink = require('react-ink');
import FontIcon from '../FontIcon/FontIcon';
var IconButton = React.createClass({
getInitialState() {
return {
iconStyle: "",
style: "",
cursorPos: {},
};
},
render() {
var _props = this.props,
...
globalTooltip = null,
...
function createTooltip(options) {
var tooltip = document.createElement("div");
tooltip.className = "tooltip";
tooltip.appendChild(document.createTextNode(_props.tooltip));
document.body.appendChild(tooltip);
tooltip.style.left = options.x + (options.w / 2) - (tooltip.offsetWidth / 2) + "px";
tooltip.style.top = (options.y - tooltip.offsetHeight - 10) + "px";
globalTooltip = tooltip;
console.log(options);
};
function showTooltip(e){
var options = {
w: e.target.offsetWidth,
x: e.target.offsetLeft,
y: e.target.offsetTop,
};
createTooltip(options);
};
function removeTooltip(e){
globalTooltip.parentNode.removeChild(globalTooltip);
};
return(
...
);
}});
ReactDOM.render(
<IconButton ... tooltip="aaaaa" />, document.getElementById('app')
);
And at this moment I can't even console log the options object :/

This is not a fix to the bug in your code, but I'm outlining some React principles and features that will help you solve your problems with just React (instead of mixing native DOM APIs and React APIs).
It is not advised to directly access the DOM elements using native DOM APIs when you are using React. Handling DOM is the job of React. That is what React is for. So if you modify/remove/insert elements from/into elements created using React, you are losing the whole advantage of that powerful library; minimal DOM change.
In simple words, if we modify the DOM elements created by React, and when React comes back and looks again to the DOM for performing its diffing algorithm, it is now something else, someone has altered it without React's knowledge; and React gets confused. Thus React fails do its optimization magics for what it is famous for.
To handle DOM nodes, React has a feature called Refs, which are essentially references to original DOM nodes. But you need to define it if you want to use it.
Example usage of ref:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
this.textInput.focus();
}
render() {
return (
<input ref={(input) => { this.textInput = input; }} />
);
}
}
In the above example, if you want the offsetWidth, offsetHeight or any other DOM properties of <input> element, you can access it by this.textInput.offsetWidth, this.textInput.offsetHeight etc. But treat them as read-only.
If you want to alter the styles:
add a style attribute to the element in your JSX and modify the inline styles using React State and Lifecycle methods.
<input
style={{ left: this.state.offsetTop, top: this.state.offsetTop }}
ref={(input) => { this.textInput = input; }}
/>
I also saw in your code that you're using .removeChild and .appendChild in order to hide/show tooltip. Instead of that make use of React's Conditional Rendering.
example:
render() {
return (
<div>
{this.state.showToolTip ? <Tooltip ... /> : null}
{/* ... other stuff ... */}
</div>
);
}
If we are using React, then we should use it for a purpose, rather than just to say we are using it.

Related

Why my component don't change even when render function it's being recalled? React

Im changing a child component state from its parent using this method:
From parent:
this.changeBottomToolbar(newTool)
Method declared on Child's class
changeBottomToolbar(newToolbarName){
this.setState({selectedToolbar: newToolbarName})
}
It's state change and it re-renders as I check with the console.
The render it's a conditional render that uses a function to get what it should render
getBottomToolbar(){
switch(this.state.selectedToolbar){
case "SelectorAndText":
console.log("Devolviendo el selector")
return <TextBottomToolbar
ref={this.bottomToolbarRef}
fontSizeUpdater = {this.fontSizeUpdater}
fontColorUpdater = {this.fontColorUpdater}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
fontFamilyUpdater = {this.fontFamilyUpdater}
alignmentUpdater = {this.alignmentUpdater}
/>
break
case "FreeLine":
console.log("Devolviendo el Line")
return <LineBottomToolbar
ref={this.bottomToolbarRef}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
shadowColorUpdater={this.shadowColorUpdater}
shadowSizeUpdater={this.shadowSizeUpdater}
/>
break
}
}
And i call it on the render:
render(){
const { classes } = this.props
console.log(this.state.selectedToolbar)
return (
<React.Fragment>
{
this.getBottomToolbar()
}
</React.Fragment>
);
}
As you can see in this image, the code it's been executed correctly and it returns the other component when the state it's changed
But the component ITS NOT CHANGING even tho the render it's been called again and it's state it's changing, im completely shocked, I have no clue on why this happens, please help!!
So the problem was that i wrongly copy-pasted the import (facepalm)
This was the import I was doing so I thought it wasn't chaning
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/TextBottomToolbar'
This corrected the problem:
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/LineBottomToolbar '

Append react component on click

I'm trying to write a Tooltip component that appears on click on element. I've seen implementation of this kind of component where tooltip is kind of wrapped around another element, but that is not suitable for me.
I was thinking that maybe since I can access event.target I'll just select target's parent, then create new instance of a tooltip component with absolute positioning calculated from position of a target and append it to a parent, but I get an error that new instance of my Tooltip is not of node type and so it's not a valid child.
TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
Tooltip is a class component and it returns just a <div> with some text:
class Tooltip extends React.Component {
constructor(props) {
super(props);
}
basicStyle = {
width: 80,
height: 30,
backgroundColor: '#333333',
position: 'absolute',
left: this.props.tooltipLeft,
right: this.props.tooltipTop,
};
render() {
return <div style={this.basicStyle}>tooltip</div>;
}
}
And here's code for this demo app:
import React from 'react';
import Tooltip from './Tooltip';
import './App.css';
function App() {
const clickHandler = (event) => {
//get coordinates to position a Tooltip
const pos = event.target.getBoundingClientRect();
const tooltipLeft = pos.left + pos.width / 2;
const tooltipTop = pos.top - 20;
const tooltip = new Tooltip(tooltipLeft, tooltipTop);
const parent = event.target.parentNode;
parent.appendChild(tooltip);
};
return (
<div className='wrapper'>
<div className='box one' onClick={clickHandler}></div>
<div className='box two' onClick={clickHandler}></div>
<div className='box three' onClick={clickHandler}></div>
</div>
);
}
export default App;
I've also tried do the same thing with useRef and ReactDOM through use of portals (not sure if it's right way of using them), but it didn't help, even though error message no longer show up on click, just nothing happens.
In this case the code I used is following:
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import Tooltip from './Tooltip';
import './App.css';
function App() {
const parentRef = useRef(null);
const clickHandler = (event) => {
//get coordinates to position a Tooltip
const pos = event.target.getBoundingClientRect();
const tooltipLeft = pos.left + pos.width / 2;
const tooltipTop = pos.top - 20;
const tooltip = new Tooltip(tooltipLeft, tooltipTop);
ReactDOM.createPortal(tooltip, parentRef.current);
};
return (
<div className='wrapper' ref={parentRef}>
<div className='box one' onClick={clickHandler}></div>
<div className='box two' onClick={clickHandler}></div>
<div className='box three' onClick={clickHandler}></div>
</div>
);
}
export default App;
I'm feeling like I'm missing something obvious here but I can't get it.
Can someone explain to me what I'm doing wrong in each of two attempts?
UPDATE:
I realized I should have said a bit more:
A little explanation of what I'm trying to achieve. Basicly I'm building a site with the ability to customize every element on it and that is the role of this tooltip component. I'm not using conditional rendering here because that would require me to manually add hooks/conditions to every possible place a Tooltip could be called or use just one instance of it with different possitions and content. I need to be able to show multiple Tooltips for a big number of elements and they should be able to be opened at the same time, this is why I went with class component and appendChild().
You should use the clickhandler to update your state, then conditionally draw the tooltip according to the state. I admit that I haven't used portals before so I'm not sure about that, but the "state" way is pretty straightforward.
Basically something like this:
const [pos,updatePos] = useState(null)
const clickHandler = (event) => {
updatePos(event.target.getBoundingClientRect())
}
return (
<div className='wrapper' ref={parentRef}>
<div className='box one' onClick={clickHandler}></div>
<div className='box two' onClick={clickHandler}></div>
<div className='box three' onClick={clickHandler}></div>
{ pos && (<ToolTip position={pos}/>) }
</div>
);
So it looks like you can't just stright up append react component using appendChild(), since appendChild() is a native javascript function and return value of react component(or in case of classes - of its render() method) is just object and not a node in any way (since react manages DOM for you). But I can't figure why portals don't work since I don't really know much about them.
If anybody interested, I also managed to write a custom hook/component that controls tooltips. I ended up using useState hook to store array all of the active tooltips and array of elements on which those tooltips was called. It's just a raw prototype, but it's working. I'll add some functionality and polish it a bit later, but for now it's just a tooltip dummy.
https://codepen.io/ClydeTheCloud/pen/MWKJQza

How do I style a div inside a component without passing props to that component (I'm using a package)

I'm using the react-scrollbar package to render a scrollbar for my my content. What I also want is a arrow button that, on click, moves to a certain scrollbar area. The problem is, I'm trying to style (marginTop) a class inside my component.
This is my attempt:
// MY COMPONENT
scrollToNextUpload = () => {
const NextUpload = 400
this.setState({ marginTop : this.state.marginTop + NextUpload }, () => document.getElementsByClassName('scrollarea-content')[0].style.marginTop = "'" + this.state.marginTop + "px'")
}
// MY RENDER
render () {
<ScrollArea>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
What is actually rendered
<div class='scrollarea'>
<div class='scrollarea-content'>
// my content
<div onClick={this.scrollToNext}></div>
</div>
</div>
What I want
To make my area with the scrollbar scroll, I have to add a marginTop style to the 'scrollarea-content'. I could do this by passing props to the < ScrollArea > and then use them inside the installed package; but I'm trying to avoid altering the original package content.Also, is there another way how I could scroll by click and is there someone else who's experienced with that NPM Package?
Most libraries give props to apply style to child components, in this library you can pass a className to the contentClassName or use inline style in contentStyle prop :
<ScrollArea contentStyle={{ marginTop: 10 }}>
An another way is to write css to add style to the scrollarea-content class.
In a .css file :
.scrollarea-content {
margin-top: 10px;
}
Edit: In your case you can programatically change the marginTop style by using the props like this :
scrollToNextUpload = () => {
const NextUpload = 400;
this.setState(prevState => ({ marginTop : prevState.marginTop + NextUpload }));
}
render () {
<ScrollArea contentStyle={{ marginTop: this.state.marginTop }}>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
Note the use of a functional setState to prevent inconsistencies when next state value depends on the previous state.

How to combine JSX component with dangerouslySetInnerHTML

I'm displaying text that was stored in the database. The data is coming from firebase as a string (with newline breaks included). To make it display as HTML, I originally did the following:
<p className="term-definition"
dangerouslySetInnerHTML={{__html: (definition.definition) ? definition.definition.replace(/(?:\r\n|\r|\n)/g, '<br />') : ''}}></p>
This worked great. However there's one additional feature. Users can type [word] and that word will become linked. In order to accomplish this, I created the following function:
parseDefinitionText(text){
text = text.replace(/(?:\r\n|\r|\n)/g, '<br />');
text = text.replace(/\[([A-Za-z0-9'\-\s]+)\]/, function(match, word){
// Convert it to a permalink
return (<Link to={'/terms/' + this.permalink(word) + '/1'}>{word}</Link>);
}.bind(this));
return text;
},
I left out the this.permalink method as it's not relevant. As you can see, I'm attempting to return a <Link> component that was imported from react-router.However since it's raw HTML, dangerouslySetInnerHTML no longer works properly.
So I'm kind of stuck at this point. What can I do to both format the inner text and also create a link?
You could split the text into an array of Links + strings like so:
import {Link} from 'react-router';
const paragraphWithLinks = ({markdown}) => {
const linkRegex = /\[([\w\s-']+)\]/g;
const children = _.chain(
markdown.split(linkRegex) // get the text between links
).zip(
markdown.match(linkRegex).map( // get the links
word => <Link to={`/terms/${permalink(word)}/1`}>{word}</Link> // and convert them
)
).flatten().thru( // merge them
v => v.slice(0, -1) // remove the last element (undefined b/c arrays are different sizes)
).value();
return <p className='term-definition'>{children}</p>;
};
The best thing about this approach is removing the need to use dangerouslySetInnerHTML. Using it is generally an extremely bad idea as you're potentially creating an XSS vulnerability. That may enable hackers to, for example, steal login credentials from your users.
In most cases you do not need to use dangerouslySetHTML. The obvious exception is for integration w/ a 3rd party library, which should still be considered carefully.
I ran into a similar situation, however the accepted solution wasn't a viable option for me.
I got this working with react-dom in a fairly crude way. I set the component up to listen for click events and if the click had the class of react-router-link. When this happened, if the item has a data-url property set it uses browserHistory.push. I'm currently using an isomorphic app, and these click events don't make sense for the server generation, so I only set these events conditionally.
Here's the code I used:
import React from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router'
export default class PostBody extends React.Component {
componentDidMount() {
if(! global.__SERVER__) {
this.listener = this.handleClick.bind(this);
window.addEventListener('click', this.listener);
}
}
componentDidUnmount() {
if(! global.__SERVER__) {
window.removeEventListener("scroll", this.listener);
}
}
handleClick(e) {
if(_.includes(e.target.classList, "react-router-link")) {
window.removeEventListener("click", this.listener);
browserHistory.push(e.target.getAttribute("data-url"));
}
}
render() {
function createMarkup(html) { return {__html: html}; };
return (
<div className="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-3 col-lg-8 col-lg-offset-2 post-body">
<div dangerouslySetInnerHTML={createMarkup(this.props.postBody)} />
</div>
);
}
}
Hope this helps out!

Why doesn't React cache html elements of child components?

Hiello!
I'm wondering whats wrong in the React example bellow or if React works differently than I thought?
I'm looking for a way to reuse the underlying html element for a child react component, when the parents are two different components.
In the example bellow, I would like the inside the Circle component to have the same element after renderC1 and renderC2 is called. For instance so that I could apply a transition css property to animate the color switch, like they would if I e.g. just changed the style directly on the element.
When I render the bellow, React always seems to generate different HTML elements, ref, key or id on the DIV (in the render function of Circle) doesn't help much.
So my questions: is it possible to get React to just reuse the DIV that gets rendered via C1 when C2 is rendered? I thought this was how React should work, optimizing the underlying HTML elements?
Something like:
var C1 = React.createClass({
render: function () {
return (
<Circle background="deeppink" onClick={renderC2}/>
);
}
});
function renderC1 () {
React.render(
<C1 />,
document.getElementById('mount-point'));
}
var C2 = React.createClass({
render: function () {
return (
<Circle background="salmon" onClick={renderC1}/>
);
}
});
function renderC2 () {
React.render(
<C2 />,
document.getElementById('mount-point'));
}
var Circle = React.createClass({
styler: {
width: "100px",
height: "100px",
mozBorderRadius: "50%",
webkitBorderRadius: "50%",
borderRadius: "50%",
background: 'hotpink'
},
componentWillMount: function() {
if (this.props && this.props.background &&
this.props.background !== this.styler.background) {
this.styler.background = this.props.background;
}
},
render: function() {
return (
{/* tried adding key, ref and id, but does not reuse element */}
<div onClick={this.props.onClick} style={this.styler}></div>
);
}
});
renderC1();
This is impossible. The DOM does not allow one element to be in two places at once. Attempting to put a DOM element in a new location will automatically remove it from the old location.
You can see that here. (or more visually, here)
var parent1 = document.createElement('div'),
parent2 = document.createElement('div'),
child = document.createElement('div'),
results = document.createElement('span');
document.body.appendChild(results);
parent1.appendChild(child);
results.textContent += child.parentNode === parent1; //true
parent2.appendChild(child);
results.textContent += ', ' + (child.parentNode === parent1); //false

Categories