I am using a class component. When I run this script, it plots the graphic, but in the next line when it calls the function computePM_from_ClickedPoint,
I get the error below. The error refers to "this", but without it, I cannot call the function. Suggestions?
Error: Cannot read properties of undefined (reading 'computePM_from_ClickedPoint')
interface IProps {
mapView: __esri.MapView | __esri.SceneView;
}
constructor(props) {
super(props);
this.enableMapClick = this.enableMapClick.bind(this);
}
enableMapClick = () => {
var view = this.props.mapView;
view.on("click", function (evt) {
var point = new Point({
longitude: evt.mapPoint.longitude,
latitude: evt.mapPoint.latitude,
spatialReference: view.spatialReference,
});
var pointMarker = new PictureMarkerSymbol({
url: "https://static.arcgis.com/images/Symbols/Animated/EnlargeRotatingRedMarkerSymbol.png",
height: "20px",
width: "20px",
});
var graphic = new Graphic({
geometry: point,
symbol: pointMarker,
});
console.log(graphic)
view.graphics.add(graphic);
this.computePM_from_ClickedPoint(graphic.geometry);
});
computePM_from_ClickedPoint(screenPoint) {
var mp = webMercatorUtils.webMercatorToGeographic(screenPoint, false);
----
-----
}
render() {
return (
<div>
<Button onClick={this.enableMapClick}>
Click the Map
</Button>
</div>
)}
tried unsuccessfully changing the call to:
this.computePM_from_ClickedPoint(graphic.geometry).bind(this);
Instead of bindingthis.computePM_from_ClickedPoint(graphic.geometry).bind(this) try to bind to this.enableMapClick on onClick event (this way you can call it with this/Class same specific object), something like.
render() { return (
<div>
<button onClick="{this.enableMapClick.bind(this)}">Click the Map</button>
</div>
)}
For more details:- javascript-bind-method
The function is getting the value of a button click as props. Data is mapped through to compare that button value to a key in the Data JSON called 'classes'. I am getting all the data correctly. All my console.logs are returning correct values. But for some reason, I cannot render anything.
I've tried to add two return statements. It is not even rendering the p tag with the word 'TEST'. Am I missing something? I have included a Code Sandbox: https://codesandbox.io/s/react-example-8xxih
When I click on the Math button, for example, I want to show the two teachers who teach Math as two bubbles below the buttons.
All the data is loading. Just having an issue with rendering it.
function ShowBubbles(props){
console.log('VALUE', props.target.value)
return (
<div id='bubbles-container'>
<p>TEST</p>
{Data.map((item,index) =>{
if(props.target.value == (Data[index].classes)){
return (
<Bubble key={index} nodeName={Data[index].name}>{Data[index].name}
</Bubble>
)
}
})}
</div>
)
}
Sandbox Link: https://codesandbox.io/embed/react-example-m1880
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
const circleStyle = {
width: 100,
height: 100,
borderRadius: 50,
fontSize: 30,
color: "blue"
};
const Data = [
{
classes: ["Math"],
name: "Mr.Rockow",
id: "135"
},
{
classes: ["English"],
name: "Mrs.Nicastro",
id: "358"
},
{
classes: ["Chemistry"],
name: "Mr.Bloomberg",
id: "405"
},
{
classes: ["Math"],
name: "Mr.Jennings",
id: "293"
}
];
const Bubble = item => {
let {name} = item.children.singleItem;
return (
<div style={circleStyle} onClick={()=>{console.log(name)}}>
<p>{item.children.singleItem.name}</p>
</div>
);
};
function ShowBubbles(props) {
var final = [];
Data.map((item, index) => {
if (props.target.value == Data[index].classes) {
final.push(Data[index])
}
})
return final;
}
function DisplayBubbles(singleItem) {
return <Bubble>{singleItem}</Bubble>
}
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
json: [],
classesArray: [],
displayBubble: true
};
this.showNode = this.showNode.bind(this);
}
componentDidMount() {
const newArray = [];
Data.map((item, index) => {
let classPlaceholder = Data[index].classes.toString();
if (newArray.indexOf(classPlaceholder) == -1) {
newArray.push(classPlaceholder);
}
// console.log('newArray', newArray)
});
this.setState({
json: Data,
classesArray: newArray
});
}
showNode(props) {
this.setState({
displayBubble: true
});
if (this.state.displayBubble === true) {
var output = ShowBubbles(props);
this.setState({output})
}
}
render() {
return (
<div>
{/* {this.state.displayBubble ? <ShowBubbles/> : ''} */}
<div id="sidebar-container">
<h1 className="sidebar-title">Classes At School</h1>
<h3>Classes To Search</h3>
{this.state.classesArray.map((item, index) => {
return (
<button
onClick={this.showNode}
className="btn-sidebar"
key={index}
value={this.state.classesArray[index]}
>
{this.state.classesArray[index]}
</button>
);
})}
</div>
{this.state.output && this.state.output.map(item=><DisplayBubbles singleItem={item}/>)}
</div>
);
}
}
ReactDOM.render(<Sidebar />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The issue here is ShowBubbles is not being rendered into the DOM, instead (according the sandbox), ShowBubbles (a React component) is being directly called in onClick button handlers. While you can technically do this, calling a component from a function will result in JSX, essentially, and you would need to manually insert this into the DOM.
Taking this approach is not very React-y, and there is usually a simpler way to approach this. One such approach would be to call the ShowBubbles directly from another React component, e.g. after your buttons using something like:
<ShowBubbles property1={prop1Value} <etc...> />
There are some other issues with the code (at least from the sandbox) that you will need to work out, but this will at least help get you moving in the right direction.
I have a list and I wanna set onClick event when a list item is clicked. But my function isn't triggered even though I have bind the function.
I have looked at TODO example (TODO GITHUB). In that example, onClick list item will dispatch an action and in the end it will change the Global State (Redux Store).
What I wanna have is, when I click list item, it won't change Global State but it just change local state. I have initialize local state and onClickItemHandler function, but it seems my function isn't executed (no console.log result in chrome console).
Here is my code
// Searchable Component
class Searchable extends Component{
constructor(props,context){
super(props);
this.state ={
listAccount: [],
style:{
searchResultList:{
listStyle: 'none',
width: '100%',
height: '0em'
},
searchResultItem:{
listStyle: 'none',
width: '100%',
height: '0em'
},
}
};
this.onChange = this.onChange.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onItemClickHandler = this.onItemClickHandler.bind(this);
}
onChange(event){
this.props.onInputChange(this.props.data.type, this.props.data.id,event.target.value);
}
onFocus(event){
console.log("input on focus");
const showResultList= {
height: '10em',
overflow:'auto'
};
const showResultItem= {
height: '2.4em',
visibility: 'visible'
};
let style= this.state.style;
style.searchResultList = showResultList;
style.searchResultItem = showResultItem;
this.setState({style});
}
onBlur(event){
console.log("input on Blur");
const showResultList= {
height: '0',
overflow:'hidden'
};
const showResultItem= {
height: '0',
visibility: 'collapse'
};
let style= this.state.style;
style.searchResultList = showResultList;
style.searchResultItem = showResultItem;
this.setState({style});
}
onItemClickHandler(event){
console.log("test");
console.log(event.target);
}
render(){
return(
<div className="searchable">
<input type="search" className="input-searchable" value={this.props.data.input} onChange={this.onChange} onFocus={this.onFocus} onBlur={this.onBlur}/>
<div className="search-result">
<SearchResultList
ResultList={this.props.accounts}
listStyle={this.state.style.searchResultList}
listItemStyle={this.state.style.searchResultItem}
onClick={this.onItemClickHandler}
/>
</div>
</div>
);
}
}
// SearchResultList Component
const SearchResultList = ({ResultList, listStyle, listItemStyle, onClick}) => (
<ul style={listStyle} onClick={onClick}>
{
ResultList.map((item,idx) =>
<SearchResultItem
key={++idx}
style={listItemStyle}
text={item.name}
onClick={onClick}
/>
)
}
</ul>
);
// SearchResultItem Component
const SearchResultItem = ({ onClick, text, style}) => (
<li
style={style}
onClick={onClick}
>
{text}
</li>
);
you need to bind your onClick function:
try
<SearchResultList
ResultList={this.props.accounts}
listStyle={this.state.style.searchResultList}
listItemStyle={this.state.style.searchResultItem}
onClick={::this.onItemClickHandler}
/>
or
<SearchResultList
ResultList={this.props.accounts}
listStyle={this.state.style.searchResultList}
listItemStyle={this.state.style.searchResultItem}
onClick={this.onItemClickHandler.bind(this)}
/>
They do the same thing, binding this to your function. I believe it is getting executed upon render because it isn't being bound correctly.
Also, in case you are passing these methods to multiple children and are worried about performance, you can bind in the constructor like so:
constructor(props) {
super(props)
this.onItemClickHandler = this.onItemClickHandler.bind(this)
}
then use
<SearchResultList
ResultList={this.props.accounts}
listStyle={this.state.style.searchResultList}
listItemStyle={this.state.style.searchResultItem}
onClick={this.onItemClickHandler}
/>
If you are worried about performance you can use es7 class property. You wont need to manually bind your function in the constructor this way. So DRY yay!!
onItemClickHandler = (event) => {
console.log("test");
console.log(event.target);
}
Is it possible in React to pass all event to child element.
as an example I've got a custom Button class, that (simplified) looks something like this:
class Button extends Component {
constructor (props) {
super(props);
this.onClick = this.onClick.bind(this);
}
/* .... */
onClick (ev) {
const { disabled, onClick } = this.props;
if (!disabled) {
onClick(ev);
}
}
render () {
const {
children,
disabled,
type
} = this.props;
return (
<button
disabled={disabled}
onClick={this.onClick}
ref="button"
type={type}
>{children}</button>
}
}
I don't know what events i may want to use in the future (onMouseDown, onMouseUp, onBlur, onKeyDown, onTouchStart, and so on...)
Is it possible to pass all possible events to the button element without writing out a prop for every possible event?
adding {...this.props} to the button element is not what I want because it passes all props and some props (like className which is omitted in this example) should not be passed directly.
I thought about cloning the props object and deleting the props which should not be passed directly but this feels like a hack. Does anybody know a cleaner way?
I've written a function to iterate over the props and filter out all properties starting with 'on' this is the closest I've come so far. In case it helps anyone else:
/* helpers.js */
export function filterEvents (props, ignore = []) {
let events = {};
for (let property in props) {
if (props.hasOwnProperty(property)) {
if (property.startsWith('on') && ignore.indexOf(property) === -1) {
events[property] = props[property];
}
}
}
return events;
}
/* Tests for the filterEvents */
import { expect } from 'chai';
import { filterEvents } from './helpers';
describe('filterEvents', () => {
const props = {
className: 'someClass',
disabled: true,
onBlur: 'onBlur',
onClick: 'onClick',
onMouseDown: 'onMouseDown',
onMouseUp: 'onMouseUp'
};
it('only returns keys starting with on', () => {
const expected = {
onBlur: 'onBlur',
onClick: 'onClick',
onMouseDown: 'onMouseDown',
onMouseUp: 'onMouseUp'
};
expect(filterEvents(props)).to.deep.equal(expected);
});
it('only returns keys starting with on minus the ones in the ignore array', () => {
const expected = {
onBlur: 'onBlur',
onMouseUp: 'onMouseUp'
};
const ignore = ['onClick', 'onMouseDown'];
expect(filterEvents(props, ignore)).to.deep.equal(expected);
});
});
/* Using the function inside a component */
import { filterEvents } from './helpers'; //at the top of the components file
//Inside the render method:
const events = filterEvents(this.props, ['onClick']); //don't include onClick it's handled like the questions example
return (
<button
disabled={this.props.disabled}
onClick={this.onClick}
{...events}
>
{this.props.children}
</button>
);
I've taken Barry127's answer and added all the event handlers from React and put them in an object.
const acceptedEventHandlersForComponentValidationFunction = {
clipBoard: [
"onCopy",
"onCut",
"onPaste",
"onCopyCapture",
"onCutCapture",
"onPasteCapture"
],
composition: [
"onCompositionEnd",
"onCompositionStart",
"onCompositionUpdate",
"onCompositionEndCapture",
"onCompositionStartCapture",
"onCompositionUpdateCapture"
],
keyboard: [
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onKeyDownCapture",
"onKeyPressCapture",
"onKeyUpCapture"
],
focus: ["onFocus", "onBlur", "onFocusCapture", "onBlurCapture"],
form: [
"onChange",
"onInput",
"onInvalid",
"onReset",
"onSubmit",
"onChangeCapture",
"onInputCapture",
"onInvalidCapture",
"onResetCapture",
"onSubmitCapture"
],
generic: ["onError", "onLoad", "onErrorCapture", "onLoadCapture"],
mouse: [
"onClick",
"onContextMenu",
"onDoubleClick",
"onDrag",
"onDragEnd",
"onDragEnter",
"onDragExit",
"onDragLeave",
"onDragOver",
"onDragStart",
"onDrop",
"onMouseDown",
"onMouseEnter",
"onMouseLeave",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onClickCapture",
"onContextMenuCapture",
"onDoubleClickCapture",
"onDragCapture",
"onDragEndCapture",
"onDragEnterCapture",
"onDragExitCapture",
"onDragLeaveCapture",
"onDragOverCapture",
"onDragStartCapture",
"onDropCapture",
"onMouseDownCapture",
"onMouseMoveCapture",
"onMouseOutCapture",
"onMouseOverCapture",
"onMouseUpCapture"
],
pointer: [
"onPointerDown",
"onPointerMove",
"onPointerUp",
"onPointerCancel",
"onGotPointerCapture",
"onLostPointerCapture",
"onPointerEnter",
"onPointerLeave",
"onPointerOver",
"onPointerOut",
"onPointerDownCapture",
"onPointerMoveCapture",
"onPointerUpCapture",
"onPointerCancelCapture",
"onGotPointerCaptureCapture",
"onLostPointerCaptureCapture",
"onPointerOverCapture",
"onPointerOutCapture"
],
selection: ["onSelect", "onSelectCapture"],
touch: [
"onTouchCancel",
"onTouchEnd",
"onTouchMove",
"onTouchStart",
"onTouchCancelCapture",
"onTouchEndCapture",
"onTouchMoveCapture",
"onTouchStartCapture"
],
ui: ["onScroll", "onScrollCapture"],
wheel: ["onWheel", "onWheelCapture"],
media: [
"onAbort",
"onCanPlay",
"onCanPlayThrough",
"onDurationChange",
"onEmptied",
"onEncrypted",
"onEnded",
"onError",
"onLoadedData",
"onLoadedMetadata",
"onLoadStart",
"onPause",
"onPlay",
"onPlaying",
"onProgress",
"onRateChange",
"onSeeked",
"onSeeking",
"onStalled",
"onSuspend",
"onTimeUpdate",
"onVolumeChange",
"onWaiting",
"onAbortCapture",
"onCanPlayCapture",
"onCanPlayThroughCapture",
"onDurationChangeCapture",
"onEmptiedCapture",
"onEncryptedCapture",
"onEndedCapture",
"onErrorCapture",
"onLoadedDataCapture",
"onLoadedMetadataCapture",
"onLoadStartCapture",
"onPauseCapture",
"onPlayCapture",
"onPlayingCapture",
"onProgressCapture",
"onRateChangeCapture",
"onSeekedCapture",
"onSeekingCapture",
"onStalledCapture",
"onSuspendCapture",
"onTimeUpdateCapture",
"onVolumeChangeCapture",
"onWaitingCapture"
],
image: ["onLoad", "onError", "onLoadCapture", "onErrorCapture"],
animation: [
"onAnimationStart",
"onAnimationEnd",
"onAnimationIteration",
"onAnimationStartCapture",
"onAnimationEndCapture",
"onAnimationIterationCapture"
],
transition: ["onTransitionEnd", "onTransitionEndCapture"],
other: ["onToggle", "onToggleCapture"]
}
/*
- Component props event handler vilidation
Return all valid events to be used on a component
{
acceptedEventHandlerTypes: [
"${event handler type}"
],
eventHandlers: {
${event}: "${callback function}" // ${event} can contain "Capture" at the end to register the event handler for the capture phase
}
}
*/
const validateComponentPropsEventHandlers = (
acceptedEventHandlerTypes,
eventHandlers = {}
) => {
if (Object.keys(eventHandlers).length == 0) {
return {}
}
// Fill eventsForSpecifiedType with only the required events
let eventsForSpecifiedType = {}
let eventsCount = 0
for (const eventHandlerType in acceptedEventHandlerTypes) {
if (
acceptedEventHandlerTypes[eventHandlerType] in
acceptedEventHandlersForComponentValidationFunction
) {
const newEvents =
acceptedEventHandlersForComponentValidationFunction[
acceptedEventHandlerTypes[eventHandlerType]
]
eventsForSpecifiedType[
acceptedEventHandlerTypes[eventHandlerType]
] = newEvents
eventsCount += newEvents.length
}
}
// Fill events
let events = {}
let eventsCountCheck = 0
const checkIfEventsCountHasBeenReached = () =>
eventsCountCheck == eventsCount
for (const eventHandler in eventHandlers) {
if (checkIfEventsCountHasBeenReached()) {
return events
}
// Append event handler to events object if it is recognised
Object.values(eventsForSpecifiedType).forEach(
(EVENT_HANDLERS) => {
if (
EVENT_HANDLERS.includes(eventHandler) &&
!(eventHandler in events)
) {
events[eventHandler] = eventHandlers[eventHandler]
eventsCountCheck += 1
}
if (checkIfEventsCountHasBeenReached()) {
return events
}
}
)
}
return events
}
// Usage
const test = () => {console.log("test")}
const events = validateComponentPropsEventHandlers(["mouse"], { onClick: test })
console.log(events)
// <button {...events}>Button</button>