React JS - Function within Component cannot see State - reactjs

In code below the onclick function testNewBug is unable to access the state of its parent component 'BugList'. Can anyone see where I have gone wrong with this, I am correctly setting the state and can view it in DevTools, surely with the function within the component 'this.state' should be working?
class BugList extends React.Component {
constructor() {
super();
this.state = {
bugs: bugData
}
}
render() {
console.log("Rendering bug list, num items:", this.state.bugs.length);
return (
<div>
<h1>Bug Tracker</h1>
<BugTable bugs={this.state.bugs} />
<button onClick={this.testNewBug}>Add Bug</button>
</div>
)
}
testNewBug() {
var nextId = this.state.bugs.length + 1;
this.addBug({id: nextId, priority: 'P2', status:'New', owner:'Pieta', title:'Warning on console'})
}
addBug(bug) {
console.log("Adding bug:", bug);
// We're advised not to modify the state, it's immutable. So, make a copy.
var bugsModified = this.state.bugs.slice();
bugsModified.push(bug);
this.setState({bugs: bugsModified});
}
}

Oh dear I was being and idiot, I forgot to bind my event handler to 'this'
<button onClick={this.testNewBug.bind(this)}>Add Bug</button>

if you know the method will always bind to the current class instance you can always define your method like this with =>:
testNewBug = () => {
var nextId = this.state.bugs.length + 1;
this.addBug({id: nextId, priority: 'P2', status:'New', owner:'Pieta', title:'Warning on console'})
}
you won't have to worry about bind(this) all over the place and this assures the function has one instance per class.

Related

call function in React functional component

I am trying to call a function from a div like the following
<div id='div_abstract'>
{content.abstract && content.abstract.length ? (
<article id="abstract" onMouseUp={spanSelect}>{content.abstract </article>) : ''}
</div>
My functional component is structured like this
export default function ExternalInfos(props) {
...
function spanSelect() { ... }
return(
...
);
}
And the function I'm trying to call is
let table = [];
function spanSelect() {
var span = document.createElement("span");
span.setAttribute("id","span");
if (window.getSelection()) {
var text = window.getSelection();
if (text.rangeCount) {
var range = text.getRangeAt(0).cloneRange();
range.surroundContents(span);
text.removeAllRanges();
text.addRange(range);
};
};
let object = window.getSelection().toString();
table.push(object);
const annotation = document.getElementById("annotationArea");
annotation.updateObjectAnnotation(table);
}
But nothing happens when I select text from my div and it doesn't return an error.
How do I solve this?
You need to capitalize the event handler prop: onMouseUp.
From the React docs (https://reactjs.org/docs/handling-events.html):
"React events are named using camelCase, rather than lowercase."

Binding Angular2 components inside of a Jquery plugin template

I'm working on using a kendo inside of an angular 2 project.
Getting the widget set up correctly is no problem:
ngOnInit() {
let options = inputsToOptionObject(KendoUIScheduler, this);
options.dataBound = this.bound;
this.scheduler = $(this.element.nativeElement)
.kendoScheduler(options)
.data('kendoScheduler');
}
When that runs, the plugin modifies the DOM (and, to my knowleged, without modifiying the shadow DOM maintained by angular2). My issue is that if I want to use a component anywhere inside of the plugin, like in a template, Angular is unaware of it's existence and won't bind it.
Example:
public views:kendo.ui.SchedulerView[] = [{
type: 'month',
title: 'test',
dayTemplate: (x:any) => {
let date = x.date.getDate();
let count = this.data[date];
return `<monthly-scheduler-day [date]="test" [count]=${count}"></monthly-scheduler-day>`
}
}];
The monthly-scheduler-day class:
#Component({
selector: 'monthly-scheduler-day',
template: `
<div>{{date}}</div>
<div class="badge" (click)=dayClick($event)>Available</div>
`
})
export class MonthlySchedulerDayComponent implements OnInit{
#Input() date: number;
#Input() count: number;
constructor() {
console.log('constructed');
}
ngOnInit(){
console.log('created');
}
dayClick(event){
console.log('clicked a day');
}
}
Is there a "right" way to bind these components inside of the markup created by the widget? I've managed to do it by listening for the bind event from the widget and then looping over the elements it created and using the DynamicComponentLoader, but it feels wrong.
I found some of the details I needed in this thread: https://github.com/angular/angular/issues/6223
I whipped this service up to handle binding my components:
import { Injectable, ComponentMetadata, ViewContainerRef, ComponentResolver, ComponentRef, Injector } from '#angular/core';
declare var $:JQueryStatic;
#Injectable()
export class JQueryBinder {
constructor(
private resolver: ComponentResolver,
private injector: Injector
){}
public bindAll(
componentType: any,
contextParser:(html:string)=>{},
componentInitializer:(c: ComponentRef<any>, context: {})=>void):
void
{
let selector = Reflect.getMetadata('annotations', componentType).find((a:any) => {
return a instanceof ComponentMetadata
}).selector;
this.resolver.resolveComponent(componentType).then((factory)=> {
$(selector).each((i,e) => {
let context = contextParser($(e).html());
let c = factory.create(this.injector, null, e);
componentInitializer(c, context);
c.changeDetectorRef.detectChanges();
c.onDestroy(()=>{
c.changeDetectorRef.detach();
})
});
});
}
}
Params:
componentType: The component class you want to bind. It uses reflection to pull the selector it needs
contextParser: callback that takes the existing child html and constructs a context object (anything you need to initialize the component state)
componentInitializer - callback that initializes the created component with the context you parsed
Example usage:
let parser = (html: string) => {
return {
date: parseInt(html)
};
};
let initer = (c: ComponentRef<GridCellComponent>, context: { date: number })=>{
let d = context.date;
c.instance.count = this.data[d];
c.instance.date = d;
}
this.binder.bindAll(GridCellComponent, parser, initer );
Well your solution works fine until the component needs to change its state and rerender some stuff.
Because I haven't found yet any ability to get ViewContainerRef for an element generated outside of Angular (jquery, vanilla js or even server-side)
the first idea was to call detectChanges() by setting up an interval. And after several iterations finally I came to a solution which works for me.
So far in 2017 you have to replace ComponentResolver with ComponentResolverFactory and do almost the same things:
let componentFactory = this.factoryResolver.resolveComponentFactory(componentType),
componentRef = componentFactory.create(this.injector, null, selectorOrNode);
componentRef.changeDetectorRef.detectChanges();
After that you can emulate attaching component instance to the change detection cycle by subscribing to EventEmitters of its NgZone:
let enumerateProperties = obj => Object.keys(obj).map(key => obj[key]),
properties = enumerateProperties(injector.get(NgZone))
.filter(p => p instanceof EventEmitter);
let subscriptions = Observable.merge(...properties)
.subscribe(_ => changeDetectorRef.detectChanges());
Of course don't forget to unsubscribe on destroy:
componentRef.onDestroy(_ => {
subscriptions.forEach(x => x.unsubscribe());
componentRef.changeDetectorRef.detach();
});
UPD after stackoverflowing once more
Forget all the words above. It works but just follow this answer

Safe to set on this.state just to keep track?

I was wondering if it is safe to set .state when I need to keep track.
My component, allows user to click it, every time they do it increments tieId. On mouse exit of the component, if the tieId is different from when they mouse enetered the component, I want to do a save.
I had to keep track on this.state
Is this code approptiate or will it break the React diff checking as I didn't use this.setState{tieId: ...}?
var HelloMessage = React.createClass({
getInitialState: function() {
return {tieId: -1, onEnt: -1};
},
click: function(e) {
this.state.tieId++;
if (this.state.tieId > 2) {
this.state.tieId= -1;
}
},
mouseEnter: function(e) {
this.state.onEnt = this.state.tieId
},
mouseLeave: function(e) {
if (this.state.tieId != this.state.onEnt) {
alert('ok saving');
} else {
alert('will not save because tie id is same as when entered \n tieId: ' + this.state.tieId + ' \n onEnt: ' + this.state.onEnt);
}
},
render: function() {
return <div onClick={this.click} onMouseLeave={this.mouseLeave} onMouseEnter={this.mouseEnter} >Hello </div>;
}
});
ReactDOM.render(<HelloMessage />, mountNode);
Can copy paste this code at the React site Live JSX editor - http://facebook.github.io/react/
You should always use setState() to make changes to your component state in React.
In case you don't believe me, here's Dan Abramov saying the same thing :)
(There's an exception if you're using ES6 classes: your constructor should set state directly.)

Uncaught TypeError: Cannot read property 'ImageRoute' of undefined

I have a simple set of components that all are composed of the following main component. The document component exposes its event handler from its props property. The event fires all the way up as expected. However once its caught in the main component, i try to set the state. Upon setting the state inside the event handler it throws an error the first time i try and retrieve the state. Every subsequent attempt works as expected.
In the example image below it shows the first time i set and try to print out the value of ImageRoute from the document object it fails then works every single time after.
selectedDocument is the eventhandler
Anybody have an explanation?
var Workstation = React.createClass({
getInitialState: function () {
return {};
},
selectedDocument :function(obj, clickedNumber){
var component = this;
console.log(obj.ImageRoute)
console.log(clickedNumber + " was clicked")
component.setState({ selectedDocument: obj });
console.log("selectedDocument set")
if (this.isMounted()) {
console.log(this.state.selectedDocument.ImageRoute)
} else {
console.log("not mounted")
}
},
render: function () {
return (
<div>
<DocumentQueue initialData={jsonData.initialData} selectedDocument={this.selectedDocument} />
<ImageViewer src={this.state.selectedDocument==null ? this.state.selectedDocument : this.state.selectedDocument.ImageRoute} />
</div>
);
}
});
you havent set the state yet. what I mean is the setState function is async and doesn't wait until the state is set before moving to the next line of code. you can use a callback function to set this up correctly
var component = this;
console.log(obj.ImageRoute)
console.log(clickedNumber + " was clicked")
component.setState({ selectedDocument: obj }, function(){
console.log("selectedDocument set")
if (component.isMounted()) {
console.log(component.state.selectedDocument.ImageRoute)
} else {
console.log("not mounted")
}
});

i need React databinding (Arr push after data refresh)

I am faced with the problem
web page is to react with the signal.
Signal does not regularly.
my Scenarios (Arr data push after refresh)
It does not give any one event
Because I can not use. setState funciton
i think javascript function call for react databind refresh
Because the data binding, you use the following dataRefresh() functions.
I know code is incorrect.
I've written code like the following:
var dataArr = [{
key : '1',
text : "hello1",
title: "title1"
},
{
key : '2',
text : "hello2",
title: "title2"
},
{
key : '3',
text : "hello3",
title: "title3"
}
];
var Repeat = React.createClass({
render : function(){
var data = this.props.items;
return(
<PanelGroup accordion >
{ data.map(function(item){
return(
<Panel header={item.title} eventKey={item.key} >
{item.text}
</Panel>
);
})}
</PanelGroup>
);
}
});
function startReact(){
React.render(
<div>
<Repeat items={ dataArr }/>
</div>,
document.getElementById('content')
);
}
startReact();
function dataRefresh(){
dataArr.push({
key : '4',
text : "hello4",
title: "title4"
});
startReact();
}
setTimeout("dataChange()",3000);
It is the question.
I need to have an idea that can solve the problem.
Advice is required.
That's a bad idea. When you have new data use setState so it will update/rerender your view automatically that's the point of react.
Bind your update to a function that update the state not directly to the render.
Here is an example very easy it explain how to update your state when the user is clicking on some button:
https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html
So for your instead of handling a user action you'll set an handler that is called when you have new data.
I hope it's clear
To go in React way, you must use state instead of that above method.
Use getInitialState to point to the global dataArr and then use setState to update the state.
Even I would suggest putting dataArr in the base component holding the child components. This will avoid polluting the global namespace as well.
Inside your setTimeout, avoid using string. instead wrap it inside a function like below:
setTimeout(function() {
dataChange();
}, 3000);
So the code will become:
var Repeater = React.createClass({
getInitialState: function() {
return {
data: dataArr
}
},
componentDidMount: function() {
setTimeout(function() {
// update the dataArr
// Instead of calling dataChange gloabl method, I would put it inside the base component and call this.updateData();
// this.setState({data: dataArr});
}.bind(this),3000);
},
updateData: function() {
// increment the array and call
this.setState({data: dataArr});
},
render : function() {
return (
<div>
<Repeat items={ this.state.data}/>
</div>
);
}
});
The below code will become:
function startReact(){
React.render(<Repeater />,
document.getElementById('content')
);
}

Resources