7

I'm trying to build a simple unit convertor to practice React.js. I want to be able to change the value of one unit eg: Kg, and have the other unit eg: lb to automatically change on the screen. Please see this website to give you an idea: http://www.convertunits.com/from/lb/to/kg

I have the following code, it renders but the units don't update. What I want to know is:

  • Is it even accurate for one component to have two states? 1 for Kg and another for lb
  • Or would they need to be sibling components? If so, how would they go about updating each other's states?
  • If it's possible to have the state for both units in the same component, what am I doing wrong here?

Thank you! (I have a simple express app to render the page)

import React from 'react';

export default class Converter extends React.Component {
    render() {
      return (
        <div className="convert">
            <Range />
        </div>
      );
   }
}


class Range extends React.Component {
  constructor(props) {
    super(props);
    this.state = { kg: null, lb: null };
}

kgClick() {
    this.setState({ lb: state.kg * 2.2046 });
}

lbClick() {
    this.setState({ kg: state.lb / 2.2046 });
}

render() {

  return (
        <div>
            <label> Kg: </label>
            <input type="number" name="Kg" onChange={this.kgClick} value={this.state.kg} />
            <label> Lb: </label>
            <input type="number" name="Lb" onChange={this.lbClick} value={this.state.lb} />
        </div>
    );
  }
}

Backend logic:

var express = require('express');
var app = express();

app.set('port', (9000));
app.set('view engine', 'jsx');
app.set('views', __dirname + '/views');
app.engine('jsx', require('express-react-views').createEngine({ transformViews: false }));

require('babel/register')({
    ignore: false
});

app.use('/', function(req, res) {
  res.render('index', "");
});

app.listen(app.get('port'), function() {});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Anita
  • 153
  • 1
  • 2
  • 11
  • 1
    quickly looking at your code, make sure your event handlers, (`lbCLick`, `kgClick`) are getting the proper `this`. React components using ES6 classes no longer autobind `this` to non React methods [This blog post](http://babeljs.io/blog/2015/06/07/react-on-es6-plus/) covers a few of the potential issues your facing – enjoylife Dec 14 '15 at 17:58
  • Thanks for the great article! – Anita Dec 14 '15 at 21:45

1 Answers1

4

Yes, it is perfectly valid (and often necessary) to have more than one state property in a React component.

Your main problem is that you are never passing your click event instance to the handler function. Therefore, that function has no way of knowing the value of the number input. Also, you need to update the state for both measurements in your function. This is because you are setting the value for your number inputs to equal the state of that value. When you change the number in the input, it will not actually change in the render of that input unless you also update the state. Finally, as mattclemens points out, you should make sure you are binding this correctly. Either bind it on the component instance (like I do below) or use ES6 arrow notation in your handler function.

With all of that in mind, your class will work if it looks something like this:

class Range extends React.Component {


constructor(props) {
    super(props);
    this.state = { kg: 0, lb: 0 };
}

kgClick(e) {
    var newLb = e.target.value * 2.2046;
    this.setState({ kg: e.target.value, lb: newLb });
}

lbClick(e) {
    var newKg = e.target.value / 2.2046;
    this.setState({ lb: e.target.value, kg: newKg });
}

render() {

  return (
        <div>
            <label> Kg: </label>
            <input type="number" name="Kg" onChange={this.kgClick.bind(this)} value={this.state.kg} />
            <label> Lb: </label>
            <input type="number" name="Lb" onChange={this.lbClick.bind(this)} value={this.state.lb} />
        </div>
    );
  }
}
Anita
  • 153
  • 1
  • 2
  • 11
Andy Noelker
  • 10,949
  • 6
  • 35
  • 47
  • Thank you for your response. I read the article and have amended my code but it still doesn't work. I've tried both `constructor(props) { super(props); this.state = { kg: 0, lb: 0 }; this.kgClick = this.kgClick.bind(this); this.lbClick = this.lbClick.bind(this); }` and the ES6 arrow function notation but neither method work. – Anita Dec 14 '15 at 19:14
  • I'm a dunce. I just realized exactly what is going wrong. I will update my answer to show you. – Andy Noelker Dec 14 '15 at 19:26
  • Okay I updated my answer. I hope that all makes sense. Let me know if you need more clarification. I just tried out that exact class and it worked for me. – Andy Noelker Dec 14 '15 at 19:32
  • Thank you so much for taking the time to help. It's really odd, I tried the code you've pasted but it doesn't work. Are you using the express code for the backend or something else? – Anita Dec 14 '15 at 19:51
  • Hmm that is strange. I am not using express - I am compiling everything with webpack and using babel with its polyfill, its transform-runtime plugin, and the presets es2015, stage-0, and react. It is entirely possible there is some weird compilation issue with your backend setup. Are you getting any server errors? Or are there any client-side errors? I don't have much experience with express unfortunately. But I do know that if you have a working react setup and you can use ES6 then my class should work as I literally copy and pasted it from my own working version. – Andy Noelker Dec 14 '15 at 20:06
  • 1
    ok, I changed my environment to render from the client side and it works! Thank you for your help, you're a legend. Learned a lot. – Anita Dec 14 '15 at 21:45