1

Using React 15.6.1 I'm making a QA forum and here's the code for rendering a single answer with multiple replies:

import React, { Component } from 'react';
import bindHelper from '../util/bindHelper';

const AnswerStub = (props) => (
  <div className={props.wrapperClass}>
    <div dangerouslySetInnerHTML={{ __html: props.answer.body }} />
    <div className="pull-right">
      <span>{props.answer.author.name}</span><br />
      <span>{props.answer.postDate}</span>
    </div>
  </div>
)

class Answer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      key: props.answer._id,
      replyTo: false,
      replyText: ''
    }
    bindHelper(this);
  }

  _handleReply(event) {
    event.preventDefault();
    console.log(event.target.id);
    console.log(this.state.key);
    this.setState({ replyTo: !this.state.replyTo });
  }

  _handleClear(event) {
    this.setState({ replyText: '' });
  }

  _handleReplyChange(event) {
    this.setState({ replyText: event.target.value });
  }

  _handlePostReply(event) {
    //TODO: dispatch event handler for reply
  }

  render() {
    return (
      <div className="row answer">
        <div className="col-md-1">
          <span className={this.props.answer.accepted ? "glyphicon glyphicon-ok" : ""} />
        </div>
        <AnswerStub wrapperClass="col-md-11" answer={this.props.answer} />
        <div className="comments col-md-12">
          {this.props.answer.replies && this.props.answer.replies.map((reply, i) => <AnswerStub key={i} wrapperClass="comment-single" answer={reply} />)}
        </div>

        <div className="clearfix" />

        {/* REPLY OPTION */}
        <div className="col-md-offset-1 col-md-11">
          <a id={`reply_${this.state.key}`} href="" onClick={this.handleReply.bind(this)}>
            {this.state.replyTo ? "Cancel" : "Post a Reply"}
          </a>
          {this.state.replyTo &&
            <div>
              <textarea className="col-md-11 form-control" rows="3"
                value={this.state.replyText} onChange={this.handleReplyChange} />
              <br />
              <button className="col-md-2 btn btn-default" onClick={this.handleClear}>Clear</button>
              <button className="col-md-2 btn btn-primary pull-right" onClick={this.handlePostReply}>Post</button>
            </div>
          }
        </div>

        <div className="clearfix" />
        <br />

      </div>
    );
  }
}
export default Answer;

I am calling this in another component like

{this.props.answers && this.props.answers.map((ans,i) => <Answer key={ans._id} answer={ans} />)}

But when i click on a reply, the reply textbox for that opens, for every other case the last textbox keeps getting toggled no matter which link I click.

Sample output on the console:

59ba431d518a97998d310bd9
reply_59bba3b82219703fb84d07e7
59bba3b82219703fb84d07e7
reply_59ba431d518a97998d310bd9
59bba3b82219703fb84d07e7
reply_59bba3b82219703fb84d07e7
59bba3b82219703fb84d07e7

The target element is correct, but it's accessing the wrong state.

Shouldn't the states of each of the Answer component be separate? I cant figure out what is going on. And yes, I'm a beginner in React!

EDIT

bindHelper is a helper function i wrote to bind the this reference.

9
  • Can you rephhrase this part? But when i click on a reply, the reply textbox for that opens, for every other case the last textbox keeps getting toggled no matter which link I click. Commented Sep 18, 2017 at 19:17
  • Assume a question has 2 answers. When I click the reply link for first, the first reply box opens, as expected. When I click the same same link, I am expecting it to close, but the second reply box opens. All subsequent clicks (on either of the links) simply toggles the second reply box. Commented Sep 18, 2017 at 19:21
  • 1
    Try it without the bindHelper (bind the methods manually) and let us know if anything changed Commented Sep 18, 2017 at 19:23
  • You also have onClick={this.handleReply.bind(this)} at some point. Shouldn't it be onClick={this.handleReply} since you are handling the binding in the constructor? Commented Sep 18, 2017 at 19:25
  • Well, that does solve the problem indeed! But can you help out what is going wrong with the helper? Commented Sep 18, 2017 at 19:26

1 Answer 1

1

Ok, so the problem is that in bindHelper you are messing with the prototype of each Answer instance.

At this point I have to remind you that all Answer instances share the same prototype. If it helps you you can find the prototype of an object like this (and is 100% cross-browser compatible too):

object.constructor.prototype

So, when with each Answer instance you add a new key with the replacement part to the prototype object, you are essentially replacing the one that was added by the previous call of bindHelper

So for example, you have the first Answer instance. You call bindHelper(this). Now this.constructor.prototype (or this.__proto__, it's the same) has a new property handleReply, which is bbound to this instance of Answer.

On the second Answer instance, when you call bindHelper(this) you are again operating on the very same this.constructor.prototype (this.__proto__) as before. It's the same because all instances of Answer share it. So, you go there again, and you replace the already existing handleReply which was bound to the previous Answer instance, to a new handleReply which is bound to the Answer instance.

And this happens for everything, as a result, the handleReply method on the prototype is bound only to the last Answer instance, thus your error.

UPDATE

Here's how your bindHelper could work (sorry for formatting errors, I am on my phone)

export default function bindHelper(obj, signature='_handle', replacement='handle'){

  // iterate over all the own properties in the object prototype

  for(let key of Object.getOwnPropertyNames(obj.constructor.prototype)){

    //check for the ones that begin with '_handle' by default or the provided starting signature

    if(key.startsWith(signature)){

      obj[key.replace(signature, replacement)] = obj.constructor.prototype[key].bind(obj);

    }

  }

}

As a personal thought I do find this function quite limiting, as you'll find cases where you may need methods whose names start with something other than one pattern

Sign up to request clarification or add additional context in comments.

4 Comments

Okay!! Sad to realize my little helper hack was a bad idea. Will revert back to the old school way of binding in the constructor. Thanks!
Actually you can make it work. I'll reply again with code when I return home. Or you can think about it in the meantime ;)
Updated the answer
Yeah, agreed this has limitations, but the idea was to save some monotonous typing. We can always manually bind function which do not conform to a pattern.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.