React: Accessing a target element as 'this' - javascript

This has probably been answered before, but I can't seem to google it as it overlaps a bit with .bind(this) questions.
What I want to do is to refer the calling element as this when I run a function e.g.
highlight()
{
this.select();
}
<input value={final_line} onClick={this.highlight} ></input>
However, this is undefined after clicking it. So what would the best way to do this? Right now I'm using event.target as a replacement, which works. I've also seen ref being used, but am not sure if this is applicable as I am returning an array of input.
So my question overall is: Is there an alternative to using event.target?

I agree with GG. - you should use event.target, but an alternative is to use refs:
import { Component } from 'react';
import React from 'react';
class MyComponent extends Component {
render() {
const { final_line } = this.props;
return <input value={final_line}
onClick={ this.highlight.bind(this)}
ref="inputToHighlight"
/>;
}
highlight() {
this.refs.inputToHighlight.select();
}
}

So what would the best way to do this?
There is no way to do this. Using event.target is actually the right way to achieve it.
If you have several inputs, you should use the name property to identify the targeted element:
inputs.map(input =>
<input name={input.name} onClick={this.highlight} />
)
highlight(event) {
this.setState({ [event.target.name]: 'clicked!' })
}

Use call() inside an arrow function as your onClick to set this to event.target like:
onClick={ (event)=>{onClick.call(event.target, contact.id) }

Related

How do you get an HTML string from JSX without React. I'm using SolidJS for example

I'm not using React, so this won't work
ReactDomServer.renderToString(<div>p</div>)
I'm currently rendering the jsx in a hidden div with an id on the browser and then using
document.getElementById(id).outerHTML
to get the HTML, but I'm wondering if there's a more elegant solution
In SolidJS components actually render as plain DOM nodes, so you can actually just use all of the DOM nodes properties
In your specific example that could be something like
const SomeComponent = <div></div>;
console.log(SomeComponent.outerHTML) //this will output -> "<div></div>"
I hope this helps!
I'm currently rendering the jsx in a hidden div with an id on the browser and then using
document.getElementById(id).outerHTML
to get the HTML, but I'm wondering if there's a more elegant solution
JSX is not directly supported by the browser so it requires a compiler to be compiled into proper HTML that means you have to use a library like Solid, React ect.
In Solid, you don't need to render it into a hidden div, just don't output it to the DOM.
import { render, renderToString } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
return (
<button type="button" onClick={increment}>
{count()}
</button>
);
}
function App() {
let el = <Counter />
const increment = () => {
console.log((el.outerHTML));
};
return (
<button type="button" onClick={increment}>
get content
</button>
);
}
render(() => <App />, document.getElementById("app")!);
There is renderToString in Solid to support server side rendering, but it appears it does not work in browsers.

How can React add and remove work together?

I am new to react and am trying to toggle a body class using two different buttons. One is supposed to add a class using an onClick event and the other is supposed to remove the class. Below is an example of my code.
Right now in the console I can see the event fire twice but the class remains. As I stated I am new to React so I know I may be doing this incorrectly.
bodyFixed() {
document.body.classList.add('body-fixed');
}
bodyRelative() {
document.body.classList.remove('body-fixed');
}
You are trying to modify the dom directly like you would with vanilla js or JQuery, but this is not how react is meant to be used. React creates a virtual dom that you create and manage, and then react handle changing the page for you.
I recommend following a guide like this one to learn basic setup and concepts (skip to the part where he uses JSX).
I can further point you in the right direction if you show your whole component file.
You want to toggle a className prop value in the React way.
The React way is having a state prop and having a handler function that will toggle the state value, rather than manipulating the DOM node directly (the way you're doing it).
I would suggest you to take a look at React Main Concepts: Handling events and later once you feel a little bit more comfortable to read about Virtual DOM and Reconciliation in React.
Here's how can you do it:
const { classNames } = window
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isToggled: true
}
this.toggleClass = this.toggleClass.bind(this)
}
toggleClass() {
const { isToggled } = this.state
this.setState({
isToggled: !isToggled
})
}
render() {
const { isToggled } = this.state
const className = classNames({
'body-fixed': isToggled
})
return <div className={className}>
<div>Current `className`: <b>{ className }</b></div>
<button onClick={this.toggleClass}>Toggle class</button>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://unpkg.com/classnames#2.2.6/index.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>
I was able to use the code I listed earlier. I had my onClick events positioned incorrectly. Here is an example of the code I used:
bodyFixed() {
document.body.classList.add('body-fixed');
}
bodyRelative() {
document.body.classList.remove('body-fixed');
}
<Toggle>
{({on, getTogglerProps, setOn, setOff, toggle, showAlert}) =>
<div>
<button className="search-icon-top" {...getTogglerProps()}>{on ? <img className="times" onClick={this.bodyRelative} src={require('img/times.svg')} alt=" " /> : <i className="fa fa-search" onClick={this.bodyFixed}></i>}</button>
<div>
{on ? <TodaySearchBox /> : ''}
</div>
</div>}
</Toggle>
This is just a start for now. Thank you for the input.
EDIT: I am open to suggestions. Like I said I am new to React.

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!

React Testing: Event handlers in React Shallow Rendering unit tests

Background
I am trying to learn how to use the React Shallow Rendering TestUtil and had the tests passing until I added an onClick event handler to both; It seems that there must be some difference with the Accordion.toggle function I am trying to use in Accordion.test.js vs this.toggle in Accordian.js...but I can't figure it out.
Question
How can I get the two highlighted tests in Accordian.test.js to pass?
Steps to reproduce
Clone https://github.com/trevordmiller/shallow-rendering-testing-playground
npm install
npm run dev - see that component is working when you click "Lorem Ipsum"
npm run test:watch - see that tests are failing
There are a number of issues preventing your tests from passing.
Looking at the test "should be inactive by default":
Accordion.toggle in your test is a property of the Accordion class, and this.toggle in your code is a property of a instance of the Accordion class - so in this case you are comparing two different things. To access the 'instance' method in your test you could replace Accordion.toggle with Accordion.prototype.toggle. Which would work if it were not for this.toggle = this.toggle.bind(this); in your constructor. Which leads us to the second point.
When you call .bind() on a function it creates a new function at runtime - so you can't compare it to the original Accordion.prototype.toggle. The only way to work around this is to pull the "bound" function out of the result from render:
let toggle = result.props.children[0].props.onClick;
assert.deepEqual(result.props.children, [
<a onClick={toggle}>This is a summary</a>,
<p style={{display: 'none'}}>This is some details</p>
]);
As for your second failing test "should become active when clicked":
You try calling result.props.onClick() which does not exist. You meant to call result.props.children[0].props.onClick();
There is a bug in React that requires a global "document" variable to be declared when calling setState with shallow rendering - how to work around this in every circumstance is beyond the scope of this question, but a quick work around to get your tests passing is to add global.document = {}; right before you call the onClick method. In other words where your original test had:
result.props.onClick();
Should now say:
global.document = {};
result.props.children[0].props.onClick();
See the section "Fixing Broken setState()" on this page and this react issue.
Marcin Grzywaczewski wrote a great article with a workaround for testing a click handler that works with shallow rendering.
Given a nested element with an onClick prop and a handler with context bound to the component:
render() {
return (
<div>
<a className="link" href="#" onClick={this.handleClick}>
{this.state.linkText}
</a>
<div>extra child to make props.children an array</div>
</div>
);
}
handleClick(e) {
e.preventDefault();
this.setState({ linkText: 'clicked' });
}
You can manually invoke the function value of the onClick prop, stubbing in the event object:
it('updates link text on click', () => {
let tree, link, linkText;
const renderer = TestUtils.createRenderer();
renderer.render(<MyComponent />);
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
// initial state set in constructor
expect(linkText).to.equal('Click Me');
// manually invoke onClick handler via props
link.props.onClick({ preventDefault: () => {} });
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
expect(linkText).to.equal('Clicked');
});
For testing user events like onClick you would have to use TestUtils.Simulate.click. Sadly:
Right now it is not possible to use ReactTestUtils.Simulate with Shallow rendering and i think the issue to follow should be: https://github.com/facebook/react/issues/1445
I have successfully tested my click in my stateless component. Here is how:
My component:
import './ButtonIcon.scss';
import React from 'react';
import classnames from 'classnames';
const ButtonIcon = props => {
const {icon, onClick, color, text, showText} = props,
buttonIconContainerClass = classnames('button-icon-container', {
active: showText
});
return (
<div
className={buttonIconContainerClass}
onClick={onClick}
style={{borderColor: color}}>
<div className={`icon-container ${icon}`}></div>
<div
className="text-container"
style={{display: showText ? '' : 'none'}}>{text}</div>
</div>
);
}
ButtonIcon.propTypes = {
icon: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired,
color: React.PropTypes.string,
text: React.PropTypes.string,
showText: React.PropTypes.bool
}
export default ButtonIcon;
My test:
it('should call onClick prop when clicked', () => {
const iconMock = 'test',
clickSpy = jasmine.createSpy(),
wrapper = ReactTestUtils.renderIntoDocument(<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>);
const component = findDOMNode(wrapper).children[0];
ReactTestUtils.Simulate.click(component);
expect(clickSpy).toHaveBeenCalled();
expect(component).toBeDefined();
});
The important thing is to wrap the component:
<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>
Hope it help!

Get selected option text using react js?

I have my select list component rendering my select list:
<form className="pure-form">
<select ref="selectMark" className="mark-selector"
onChange={this.onChange}>{this.getOptions()}
</select>
</form>
I have a method on the component to create the options:
getOptions: function () {
return this.props.renderProps.data.map(function (item) {
return <option key={item.value} value={item.value}>{item.label}</option>;
}.bind(this));
},
My onChange method works fine with the value:
onChange: function(event) {
var newValue = event.nativeEvent.target.value;
this.props.renderProps.onSaveCare(newValue);
this.setState({value: newValue});
this.toggleEdit();
},
Is there a way I can get the option text? This gives me undefined
event.nativeEvent.target.text; //undefined
Something like this should do
var index = event.nativeEvent.target.selectedIndex;
event.nativeEvent.target[index].text
Here is a demo http://jsbin.com/vumune/4/
You can get the option text by replacing this:
event.nativeEvent.target.text;
with this:
event.target.options[event.target.selectedIndex].text
If it's single select, here is more simple way:
e.target.selectedOptions[0].text
This worked for me
const {options, value} = e.target;
console.log(options[value].innerHTML);
Edit: I just realized I was using the "value" field to store the ID of some objects, from 0 to n. I guess a better approach could be the following:
const {options, selectedIndex} = e.target;
console.log(options[selectedIndex].innerHTML);
The text of an option is simply the label property of the corresponding item.
In your case, to retrieve the text of the selected option, you can do:
var selectedItem = this.props.renderProps.data.find(function (item) {
return item.value === event.target.value;
});
selectedItem.label;
Array.prototype.find is part of the ES6 proposal. Underscore or lodash already package it as the _.find method.
Here what i do to retrieve the text from select option in react js.
this.refs.selectMark[this.refs.selectMark.value].text
It's easy using refs.
import the hook
import React, { useContext, useState, useEffect, useRef } from "react";
instantiate it
const yourNewRef= useRef();
Reference it in your element
ref={yourNewRef}
Then you can access it in any event or function by simply calling:
let textSelectOption = yourNewRef.current.options[yourNewRef.current.selectedIndex].text
You can access the value simply by calling
let optionValue = yourNewRef.current.value
refs are great, you can do almost everything you would using jQuery.
Take a look at yourNewRef on console and access all the content via the .current property
2020 and this Worked For Me
My Select Element :
<FormGroup>
{<Select
closeMenuOnSelect={true}
components={animatedComponents}
options={Options}
value = "Ahmedabad"
onChange={this.handleChange}
name = "citySelect"
/>}
</FormGroup>
Call handler :
handleChange = (e) => {
console.log(e.value)
}
change your menu item accordingly
Current / Temporary address
Permanent address
Office / Business Address
and on change event get
onChange = {(e,index) =>
( setAddressChangeType(e.target.value), console.log(index.props.id) )
}
make your dropdown accordingly and get value on change event like
onChange = {(e,index) =>
( setAddressChangeType(e.target.value), console.log(index.props.id) )
}

Categories