React - Unexpected behavior possibly caused by passing functions as props - javascript

I'm trying to build a list of thumbnail image components which when clicked will update the state of the parent. Eventually, I do get the desired behavior, but it takes several clicks and the effect is usually one-click behind the input. I'm speculating that this has something to do with how the functions are being passed as props, but am completely at a loss for what's going on.
EDIT: I think the issue is that the page renders before the setState changes the image state, which is why each click executes with the image that was passed via the previous click. I either need to fin a way to wait until the state updates to render, or do something in ComponentDidUpdate to rerender the page (which seems hackish but still a possibility).
This is the code I have so far:
var ImageSelector = React.createClass({
getInitialState: function(){
return{
imgState: "<%= image_path('image1.jpg') %>"
}
},
_changePicState: function(thumbnail){
var newImage = thumbnail.props.imageLink
this.setState({imgState: newImage})
},
_getThumbnails: function(){
console.log('_getThumbnails')
const thumbnailList = [
{id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
{id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
{id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
]
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink} propFunc={this._changePicState}/>
)
});
},
render: function() {
const thumbnails = this._getThumbnails()
return (
<div>
{thumbnails}
<MyCanvasComponent ref="canvasComp" imageLink={this.state.imgState}/>
</div>
)
}
});
var ImageThumbnail = React.createClass({
_runPropFunc: function(){
this.props.propFunc(this)
},
render: function(){
return (
<img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
)
}
})
EDIT: Including the myCanvasComponent code below.
var MyCanvasComponent = React.createClass({
getInitialState: function(){
return {
currentImage: this.props.imageLink
}
},
componentDidUpdate: function(){
this._draw()
},
_draw: function(){
var draw = function(){
ctx.drawImage(objectImg, 100, 100);
}
var can = this.refs.canvas;
var ctx = can.getContext('2d');
var objectImg = new Image();
var imgPath = this.state.currentImage;
objectImg.src = imgPath
console.log('drawing ' + imgPath)
objectImg.onload = function(){
draw();
}
},
componentWillReceiveProps: function(){
this.setState({currentImage: this.props.imageLink});
},
componentDidMount: function(){
console.log('canvas rendered')
this._draw()
},
render: function() {
return (
<div>
<canvas ref='canvas' width={867} height={600}/>
</div>
);
}
})

The problem lies in your MyCanvasComponent. You're setting the new state in the componentWillReceiveProps lifecycle method by using this.props but this.props references the old props, the new props are passed as a parameter to the componentWillReceiveProps function
By the way, you don't need to hold the current image in the MyCanvasComponent since this state is already managed by the ImageSelector component, passing down the current image from ImageSelector to MyCanvasComponent is sufficient in this case :
var ImageSelector = React.createClass({
getInitialState: function(){
return{
imgState: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg"
}
},
_changePicState: function(imageLink){
this.setState({imgState: imageLink})
},
_getThumbnails: function(){
const thumbnailList = [
{id: 1, imageLink: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg"},
{id: 2, imageLink: "https://s3.amazonaws.com/media-p.slid.es/uploads/jhabdas/images/969312/react-logo-1000-transparent.png"},
{id: 3, imageLink: "http://felknar.com/images/icon-react-7b609cd3.svg"},
]
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/>
)
});
},
render: function() {
const thumbnails = this._getThumbnails()
return (
<div>
{thumbnails}
<MyCanvasComponent imageLink={this.state.imgState}/>
</div>
)
}
});
var ImageThumbnail = React.createClass({
_runPropFunc: function(){
this.props.onClick(this.props.imageLink)
},
render: function(){
return (
<img key={this.props.id} width={50} height={50} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
)
}
})
var MyCanvasComponent = React.createClass({
_draw: function(){
var draw = function(){
ctx.drawImage(objectImg, 100, 100);
}
var can = this.refs.canvas;
var ctx = can.getContext('2d');
var objectImg = new Image();
var imgPath = this.props.imageLink;
objectImg.src = imgPath
objectImg.onload = function(){
draw();
}
},
componentDidUpdate: function(){
this._draw()
},
componentDidMount: function(){
this._draw()
},
render: function() {
return (
<canvas ref='canvas' width={867} height={600}/>
)
}
})
ReactDOM.render(<ImageSelector/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

First off, I would drop the habit of prefixing your function names with underscores. It serves no purpose: it doesn't actually make them private functions. It's code mess, basically. That's probably a bit subjective, but I thought I'd offer that up anyway. :)
Secondly, in ImageThumbnail, I wouldn't pass this back to the parent, when all you really need is the imageLink. So just pass that.
Try the code below. I also renamed the prop to onClick so it's clearer what's going on. When making your own components that have event handlers, try to stick to conventional names (onClick, onChange, onDeleteThumbnail). It'll make your life easier!
Side note: If you have time, try to get on board with the ES2015 class way of doing things.
var ImageSelector = React.createClass({
getInitialState: function () {
return {
imgState: "<%= image_path('image1.jpg') %>"
}
},
changePicState: function (imageLink) {
this.setState({ imgState: imageLink });
},
getThumbnails: function () {
console.log('getThumbnails');
const thumbnailList = [
{ id: 1, imageLink: "<%= image_path('image1.jpg') %>" },
{ id: 2, imageLink: "<%= image_path('image3.jpg') %>" },
{ id: 3, imageLink: "<%= image_path('image7.jpg') %>" },
];
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink}
onClick={this.changePicState} />
)
});
},
render: function () {
return (
<div>
<div>{this.getThumbnails()}</div>
<div>{this.state.imgState}</div>
</div>
)
}
});
var ImageThumbnail = React.createClass({
runPropFunc: function () {
this.props.onClick(this.props.imageLink);
},
render: function () {
return (
<img key={this.props.id} src={this.props.imageLink} className="thumbnail"
onClick={this.runPropFunc} />
)
}
});

I still didn't get rid of the underscores, but it's on the to-do list. I feel like this component might be getting a bit bloated, but it ensures that the new canvas element gets drawn after the state change. Previously, the state change would be queued and the new component would be rendered (along with the draw function which used to be in the MyCanvasComponent) and the state would change after rendering, so everything lagged behind by one click.
Thank you once again for your help!
var ImageSelector = React.createClass({
getInitialState: function(){
return{
imgState: "<%= image_path('image1.jpg') %>"
}
},
_draw: function(){
var draw = function(){
ctx.drawImage(objectImg, 100, 100);
}
var can = this.refs.canvas;
var ctx = can.getContext('2d');
var objectImg = new Image();
var imgPath = this.state.imgState;
objectImg.src = imgPath
objectImg.onload = function(){
draw();
}
},
componentDidUpdate: function(){
this._draw()
},
componentDidMount: function(){
this._draw()
},
_changePicState: function(imageLink){
this.setState({imgState: imageLink})
},
_getThumbnails: function(){
const thumbnailList = [
{id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
{id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
{id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
]
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/>
)
});
},
render: function() {
const thumbnails = this._getThumbnails()
return (
<div>
{thumbnails}
<canvas ref='canvas' width={867} height={600}/>
</div>
)
}
});
var ImageThumbnail = React.createClass({
_runPropFunc: function(){
this.props.onClick(this.props.imageLink)
},
render: function(){
return (
<img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
)
}
})

Related

Passing form values to onclick handler in react.js

I am pretty new to React. I am trying to create a simple form and pass values into an 'onclick' handler. You can see the code below:
const reactContainer = document.getElementById('react');
let SForm = React.createClass({
getApps: function(){
getAppsExternal(document.getElementsByClassName("token")[0].value,document.getElementsByClassName("publisher_id")[0].value)
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: ","",
React.createElement("input",{type: "password",className:"token",maxLength:"30"})),
React.createElement("div",{},"Publisher ID: ",
React.createElement("input",{type: "text",className:"publisher_id",maxLength:"7"})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps},"Get Apps"))
)
}
})
let elementTester =React.createElement(SForm)
ReactDOM.render(React.createElement(SForm),reactContainer)
My question is, how do I pass the parameters into getAppsExternal the 'react' way without using document.getElementsByClassName ?
See: https://reactjs.org/docs/forwarding-refs.html
Assuming you use the lattest React, you can use React.createRef()
const reactContainer = document.getElementById('react');
let SForm = React.createClass({
componentWillMount: function() {
this.tokenRef = React.createRef()
this.publisherRef = React.createRef()
},
getApps: function(){
getAppsExternal(this.tokenRef.current.value, this.publisherRef.current.value)
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: ","",
React.createElement("input",{type: "password",className:"token",maxLength:"30", ref: this.tokenRef})),
React.createElement("div",{},"Publisher ID: ",
React.createElement("input",{type: "text",className:"publisher_id",maxLength:"7", ref: this.publisherRef})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps},"Get Apps"))
)
}
})
let elementTester =React.createElement(SForm)
ReactDOM.render(React.createElement(SForm),reactContainer)
If it's not available for you, there is the callback approach
const reactContainer = document.getElementById('react');
let SForm = React.createClass({
setTokenRef: function(ref) {
this.tokenRef = ref
},
setPublisherRef: function(ref) {
this.publisherRef = ref
},
getApps: function(){
getAppsExternal(this.tokenRef.value, this.publisherRef.value)
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: ","",
React.createElement("input",{type: "password",className:"token",maxLength:"30", ref: this.setTokenRef.bind(this)})),
React.createElement("div",{},"Publisher ID: ",
React.createElement("input",{type: "text",className:"publisher_id",maxLength:"7", ref: this.setPublisherRef.bind(this)})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps.bind(this)},"Get Apps"))
)
}
})
let elementTester =React.createElement(SForm)
ReactDOM.render(React.createElement(SForm),reactContainer)
As you're not using arrow functions, don't forget to bind your callbacks like above
The simplest way, you can create a getInitialState then make a onChange function that will set the values to the state then you will be able to use them like this {this.state.password}
getInitialState: function() {
return {password: '', publisher: ''};
},
onChange: function(e){
this.setState({ [e.target.name]: e.target.value });
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: {this.state.password}","",
React.createElement("input",{type: "password",className:"token",maxLength:"30",name: 'password',value: this.state.password,onChange: this.onChange.bind(this)})),
React.createElement("div",{},"Publisher ID: {this.state.publisher} ",
React.createElement("input",{name: 'publisher',type: "text",className:"publisher_id",maxLength:"7",value: this.state.publisher, onChange: this.onChange.bind(this)})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps},"Get Apps"))
)
}

Cannot read the json object in react

I am trying to make a json webapp which reads from apis and prints the result into a list.
The problem is that i cannot read the complete json object on render.
console.log(this.state.coinInfo);
coinInfo is a json object looking like:
quantstamp: {id: "quantstamp", name: "Quantstamp", symbol: "QSP", rank: "107", price_usd: "0.446127", …},
vechain: {id: "vechain", name: "VeChain", symbol: "VEN", rank: "30", price_usd: "5.80186", …}
writing:
this.state.coinInfo["vechain"] works, but
this.state.coinInfo["vechain"].id says undefined.
I would love to get the coinInfo informations into the element on render.
Here is the full code:
var Dashboard = React.createClass({
getInitialState: function() {
return {
coinListJson: "json/coinlist.json",
coinList: [],
coinInfo: {}
}
},
createDashboard: function(event) {},
componentDidMount: function() {
// Is there a React-y way to avoid rebinding `this`? fat arrow?
var th = this;
var coinListBuffer;
var coinInfoBuffer = {};
this.serverRequest = axios.get(this.state.coinListJson).then(function(result) {
coinListBuffer = result.data.coins;
th.setState({
coinList: result.data.coins
})
}).then(function() {
//alert("geht "+coinListBuffer[1].id);
coinListBuffer.map(function(val, index) {
var url = "https://api.coinmarketcap.com/v1/ticker/" + val.id + "/?convert=EUR";
th.serverRequest = axios.get(url).then(function(result) {
coinInfoBuffer[val.id] = result.data;
console.log(result.data[0]);
var coinInfoBufferString = coinInfoBuffer;
th.setState({
coinInfo: coinInfoBufferString
})
})
})
})
},
render: function() {
var th = this;
console.log(this.state.coinInfo);
return (
<ul>
{
this.state.coinList.map(function(val, index) {
return (
<li key={index}>
{val.id}
</li>);
})
}
</ul>
)
}
});

How to load another component inside a onChange function in ReactJS

I have this following code
var SelectOption = React.createClass({
getInitialState: function() {
return {
data: []
};
},
handleemployeeChange: function() {
alert('sssss');
},
loadOptionfromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data
});
console.log(data);
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
alert('sssss');
this.loadOptionfromServer();
//setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return ( < SelectOptionList data = {
this.state.data
}
/>
);
}
});
var SelectOptionList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(list) {
return ( < Addcontenttoselect id = {
list.emp_ide_id
}
option = {
list.emp_name
} >
< /Addcontenttoselect>
);
});
return ( < select id = "select1"
className = "form-control"
data - placeholder = "Basic Select2 Box"
onChange = {
this.handleemployeeChange
} > {
commentNodes
} < /select>
);
}
});
var Addcontenttoselect = React.createClass({
render: function() {
return ( < option value = "{this.props.id}" > {
this.props.option
} < /option>);
}
});
ReactDOM.render( < div className = "col-md-3" > < h3 > Select Employee to Review < /h3><SelectOption url="/appraisal / employeelist " pollInterval={70000} /></div>, document.getElementById('select-box'));
So this component creates a Select Tag in the browser , I want to take the Value of the selected option and Call another component which will create a Table from a data got from API
Any leads please let me know
Thanks
With react you have multiple ways to pass around data to your components, it depends heavily on the use and the complexity of your application.
If you have a lot of components which need to know about the state/data of another component you should look at application architectures like flux or redux. Facebooks Flux
For some applications a full data flow architecture can be overkill so it depends on how you design your components. A common pattern is to have one component who handles the state/interactivity of your application.
Your main component will hold all the business logic of your app and pass down functions to its child to e.g. change state.
You can read more about this here Facebook thinking react
I did a little fiddle which adresses your challenge:
Fiddle
var Select = React.createClass({
render: function() {
var selectOptions = this.props.options.map(function(optionData) {
return (
<option key={optionData.id} value={optionData.id}>
{optionData.name}
</option>
);
});
return (
<select
id="select1"
className="form-control"
placeholder="Basic Select2 Box"
onChange={this.props.onChange}
>
{ selectOptions }
</select>
);
}
});
var SelectApp = React.createClass({
// The main component holds the data
getInitialState: function() {
return {
data: [],
currentData: null
}
},
componentDidMount: function () {
this.loadOptions();
},
loadOptions: function () {
var _this = this;
return setTimeout(function() {
_this.setState({data: [
{
id: 1,
name: 'Foo Bar'
},
{
id: 2,
name: 'Bar Foo'
}
]});
}, 2000);
},
onChange: function (e) {
var employeeId = e.target.value,
_this = this,
mockedData = [
{
id: 1,
data: 'Good employee'
},
{
id: 2,
data: 'Not so good employee'
}
];
// Mocking an additional data fetch
setTimeout(function () {
var result = mockedData.find(function (employeeData) {
return (employeeData.id == employeeId);
});
_this.setState({
currentData: result
});
}, 2000);
},
renderResult: function () {
if (this.state.currentData) {
return (
<div>
<h4>Employee:</h4>
<p>{this.state.currentData.data}</p>
</div>
);
}
return;
},
render: function() {
return (
<div>
<div>
<h3> Select Employee to Review </h3>
<Select url={this.props.url} options={this.state.data} onChange={this.onChange}/>
</div>
{this.renderResult()}
</div>
);
}
});
ReactDOM.render(<SelectApp url="/appraisal / employeelist " pollInterval={70000} />, document.getElementById('container'));
Edit:
renderResult: function () {
if (this.state.currentData) {
return (
<loadUserAppraisal url="something" empid={this.state.currentData.id} />
);
}

Pass function in props Griddle custom component - ReactJS

I have the following code:
var RiderComponent = React.createClass({
updater: function(){
this.props.metadata.update();
},
render: function () {
return (
<a href="#" onClick={this.updater}>
click!
</a>
);
}
});
var RiderList = React.createClass({
columnMeta: [
{
"columnName": "action",
"displayName": "",
"cssClassName": "buy",
"order": 6,
"customComponent": RiderComponent,
"update": this.update
}
],
update: function () {
this.props.update();
},
render: function () {
return <Griddle results={this.props.data}
useGriddleStyles={false}
showFilter={true}
columnMetadata={this.columnMeta}
columns={['action']}
resultsPerPage={18}
initialSort={'value'}
initialSortAscending={false}
noDataMessage={'Geen wielrenners gevonden.'}
/>
}
});
I want to run the update() function from RiderList in my RiderComponent when I click on an item. However I keep getting: 'Uncaught TypeError: this.props.metadata.update is not a function'.
What is the correct way to execute the update function in my RiderComponent?
RiderList is rendered in TeamManager:
var TeamManager = React.createClass({
getInitialState: function () {
return {
results: [],
activeRiders: [],
extraRiders: [],
availablePositions: []
}
},
componentWillMount: function () {
this.getExternalData();
},
getExternalData: function () {
var race_id = 1; // TODO: this is hardcoded
request = Api.getRidersPageUrl(race_id);
$.get(request, function (rsp) {
var data = rsp;
var activeRiders = []; // Riders with position 0..8
var extraRiders = []; // Riders with position 9..14
var availablePositions = Array.apply(null, {length: 9}).map(Number.call, Number); // Array 0..8;
if (data) {
// set data to arrays here
}
this.setState({
results: data,
activeRiders: activeRiders,
extraRiders: extraRiders,
availablePositions: availablePositions
});
}.bind(this));
},
render: function () {
return (
<RiderList update={this.getExternalData} data={this.state.results}/>
)
}
});
module.exports = TeamManager;
Here this.getExternalData just reloads the data in this.state.results.
EDIT: I got something working while using onRowClick={this.update} in RiderList render . However this fires when I click the row and not a specific button IN the row.
Updated answer based upon question new info,
var RiderComponent = React.createClass({
render: function () {
return (
<a onClick={this.props.metadata.update}>
click!
</a>
);
}
});
var RiderList = React.createClass({
getColumnMeta: function(){
return[
{
"columnName": "action",
"displayName": "",
"cssClassName": "buy",
"order": 6,
"customComponent": RiderComponent,
"update": this.update
}
]
},
update: function () {
this.props.update();
},
render: function () {
return <Griddle results={this.props.data}
useGriddleStyles={false}
showFilter={true}
columnMetadata={this.getColumnMeta()}
columns={['action']}
resultsPerPage={18}
initialSort={'value'}
initialSortAscending={false}
noDataMessage={'Geen wielrenners gevonden.'}
/>
}
});

React.js how to pass callbacks to child components?

I would like to pass a callback to a doubly nested component, and while I am able to pass the properties effectively, I can't figure out how to bind the callback to the correct component so that it's triggered. My structure looks like this:
-OutermostComponent
-FirstNestedComponent
-SecondNestedComponent
-DynamicallyGeneratedListItems
The List Items when clicked should trigger a callback which is the OutermostComponents method "onUserInput", but instead I get "Uncaught Error: Undefined is not a function". I suspect the problem is in how I am rendering the SecondNestedComponent inside the first, and passing it the callback. The code looks something like this:
var OutermostComponent = React.createClass({
onUserInput: //my function,
render: function() {
return (
<div>
//other components
<FirstNestedComponent
onUserInput={this.onUserInput}
/>
</div>
);
}
});
var FirstNestedComponent = React.createClass({
render: function() {
return (
<div>
//other components
<SecondNestedComponent
onUserInput={this.onUserInput}
/>
</div>
);
}
});
var SecondNestedComponent = React.createClass({
render: function() {
var items = [];
this.props.someprop.forEach(function(myprop) {
items.push(<DynamicallyGeneratedListItems myprop={myprop} onUserInput={this.props.onUserInput}/>);}, this);
return (
<ul>
{items}
</ul>
);
}
});
How do I correctly bind callbacks to the appropriate nested components?
You are passing this.onUserInput as a property to FirstNestedComponent. Therefore, you should access it in FirstNestedComponent as this.props.onUserInput.
var FirstNestedComponent = React.createClass({
render: function() {
return (
<div>
<SecondNestedComponent
onUserInput={this.props.onUserInput}
/>
</div>
);
}
});
For your reference, please check the implementation I've created at jsfiddle.net/kb3gN/12007
function ListenersService(){
var listeners = {};
this.addListener = function(callback){
var id;
if(typeof callback === 'function'){
id = Math.random().toString(36).slice(2);
listeners[id] = callback;
}
return id;
}
this.removeListener = function( id){
if(listeners[id]){
delete listeners[id];
return true;
}
return false;
}
this.notifyListeners = function(data){
for (var id in listeners) {
if(listeners.hasOwnProperty(id)){
listeners[id](data);
}
}
}
}
function DataService(ListenersService){
var Data = { value: 1 };
var self = this;
var listenersService = new ListenersService();
this.addListener = listenersService.addListener;
this.removeListener = listenersService.removeListener;
this.getData = function(){
return Data;
}
setInterval(function(){
Data.value++;
listenersService.notifyListeners(Data);
}, 1000);
}
var dataSevice = new DataService(ListenersService);
var World = React.createClass({
render: function() {
return <strong>{this.props.data.value}</strong>;
}
});
var Hello = React.createClass({
getInitialState: function() {
return {
data: this.props.dataService.getData()
};
},
componentDidMount: function() {
this.props.dataService.addListener(this.updateHandler)
},
updateHandler: function(data) {
this.setState({
data: data
});
},
render: function() {
return (
<div>
Value: <World data={this.state.data} />
</div>
);
}
});
React.renderComponent(<Hello dataService={dataSevice} />, document.body);

Categories