So this code was working fine.
var PollDetail = React.createClass({
getInitialState() {
return {editing: false}
},
edit() {
this.setState({editing: true})
},
save() {
this.props.onChange(this.refs.newText.value, this.props.id)
this.setState({editing: false})
},
remove() {
this.props.onRemove(this.props.id)
},
renderForm() {
return (<div className="pollquestion">
<input ref="newText" type="text" className="form-control"></input>
<button onClick={this.save}>Save</button>
</div>)
},
renderDisplay() {
return (<div className="pollquestion">
<p>{this.props.children}</p>
<span>
<button onClick={this.edit}>Edit</button>
<button onClick={this.remove}>X</button>
</span>
</div>)
},
render() {
return (this.state.editing) ? this.renderForm() : this.renderDisplay()
}
})
At least with respect to switching to editing mode.
Then I switched the function to this:
class PollDetail extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
editText: ''
}
this.edit = () => this.edit;
this.save = () => this.save;
this.submit = () => this.submit;
this.remove = () => this.remove;
}
// edit() {
// this.setState({editing: true, editText:''})
// }
// update(event) {
// //var responseOption = this.state.editText;
// this.setState({ editText: event.target.value})
// },
save(event) {
// this.props.update(this.refs.newText.value, this.props.response)
this.setState({editing: false, editText:event.target.value})
}
submit(event) {
alert("Current state: " + this.state.editText)
}
remove() {
this.props.onRemove(this.props.response)
}
renderForm() {
return (<div className="responseBox">
<input ref="newText" type="text"
className="form-control" onChange={this.save}
value={this.state.editText}>
</input>
<button className="btn btn-succes" onClick={this.submit}>Update</button>
</div>)
}
renderDisplay() {
return (<div className="responseBox">
<p>{this.props.children}</p>
<span>
<button className="btn btn-warning"
onClick={() => this.setState(state => {editing:true})}>Edit</button>
<button className="btn btn-danger" onClick={this.remove}>X</button>
</span>
</div>)
}
render() {
return (this.state.editing) ? this.renderForm() : this.renderDisplay()
}
}
and now I cannot switch to edit mode *change state to editing: true.
{Will to live is slowly eroding.}
Hi @JohnnyBizzel
The problem is that your arrow function is slightly wrong. If you want to return an object directly, you need to wrap the braces in parens; without them it treats the code inside the braces as a normal block with no implied return.
// Before
onClick={() => this.setState(state => {editing:true})}
// After
onClick={() => this.setState(state => ({editing:true}))}
As a side note, unless you want to refer to state in the function, you can just pass an object to setState
onClick={() => this.setState({editing:true})}
1 Like
Ok that fixes that, but now the onChange event doesn’t work.
save(event) {
event.preventDefault();
this.setState({editing: false, editText:event.target.value})
}
State is not getting updated.
I tried changing the input box to this:
<input ref="newText" type="text"
className="form-control" onChange={this.save}
value={this.props.children} />
which makes the value appear but I can’t change it. Even though react tools shows a function exists, it doesn’t seem to execute.
I am trying to follow this example as found on React’s documentation https://codepen.io/gaearon/pen/VmmPgp?editors=0010
Think I’ve figured it out, the way you’ve bound those functions won’t work. When React calls onChange (save in this case), it will just return the this.save function and not actually run it.
this.edit = () => this.edit;
this.save = () => this.save;
this.submit = () => this.submit;
this.remove = () => this.remove;
So, I recommend you either change those declarations to use .bind: this.save = this.save.bind(this)
, like that codepen you linked, or, you can use another technique that facebook recommends (and I use), which looks like this:
save = (event) => {
this.setState({editing: false, editText:event.target.value})
}
Much easier and you don’t have to have all those bound functions in the constructor.
As an aside, because your setting editing to false in the save function, the second you type anything in the input it will go back to the edit button.
1 Like
Ok, I tried that but I still can’t type anything into the input text box.
I have tried not changing the editing state, leaving just the editText update…
save = (event) => {
this.setState({editText:event.target.value})
}
Should of mentioned, if you’re going to use that way, you’ll need to remove the bindings from the constructor
It appears that value={this.props.children}
will override any update to the input box via the onChange event.
Yea that’s correct, when using a controlled component like that, you need to make sure the value is tied to the operation of onChange, so, like you had it before: value={this.state.editText}
.
Reacts documentation goes into it a bit more: https://facebook.github.io/react/docs/forms.html#controlled-components.
If you wanted the value of children to be the default input text, but then it be editable after the fact, you could assign it in the constructor.
constructor(props) {
super(props);
this.state = {
editing: false,
editText: props.children
}
}
1 Like
@joesmith100 Thanks for helping out. You are helping me a lot today!
This is what the updated code looks like. One more question. If I do the update to the database in this component will this force a refresh of the Parent, or should I be doing that in the Parent layer?
No worries dude, happy to help .
React works in a one directional flow, so if you have a setup like:
Parent > Child > Child
If the middle Child node calls setState, it will only re-render itself and any children (and it’s children’s children etc…), it won’t re-render it’s Parent.
So it depends on what you want to happen. If the parent relies on data coming back from the PUT request, then your going to need to make the API call in the parent so that it can process the response, set it’s own state then pass the props it’s children need.
If only that component needs something from the response, you could make the request there.
Thought so. That’s where I am getting lost. Passing the update function down.
Better to keep this data editing on it’s own page?
Then it won’t need to refresh the presentation data.
Or how about a redirect? Will this force a refresh?
What you want to do, is pass a function down from the parent to the child node that is responsible for accepting new input. I’m using browser fetch API here as an example, obviously you can use whatever you want.
class Parent extends React.Component {
updatePollApi = (newText) = > {
fetch('/api/polls/' + id, { method: 'PUT' })
.then(res => res.json())
.then(updatedPoll => this.updatePoll(updatedPoll))
}
updatePolls = (updatedPoll) => {
// Do stuff with updated poll and exsiting polls
this.setState( ...updatePolls )
}
render() {
return (
<Child updatePollApi={this.updatePollApi} />
);
}
}
class Child extends React.Component {
constructor() {
super();
this.state = {
inputValue: ''
};
}
render() {
return (
<input
value={this.state.inputValue}
onChange={ev => this.setState({ inputValue: ev.target.value })
/>
<button onClick={() => this.props.updatePollApi(this.state.inputValue)>Submit</button>
);
}
}
As you can see, the Child calls the parents updateApiPoll function with the new values from it input, the Parent then makes the Ajax request and passes the response to a different function that’s responsible for updating the local data and passing it down through props as required.
I hope this makes sense, understanding the relationship between components in React took me quite a while to figure out.
Redirecting would technically force a refresh because the browser would be loading a new page, so React would have to rerender again and fetch the new data, however, this wouldn’t be the best way to go about it.
Looks easy but in my code the props are undefined in the middle component.
Oh well. Back to the drawing board.
class PollResponse extends Component {
constructor(props) {
super(props);
this.state = {
someResponses: [],
typed: ''
}
}
eachPollResponse(resp, upd) {
const remove = () => {}
return (<PollDetail key={resp.respID}
id={resp.respID} onChange={upd} onRemove={remove}
>
{resp.response}</PollDetail>)
}
render() {
return (<div className="responses" onChange={this.props.update}>
You typed: <code>{this.state.typed}</code>
{this.props.someResponses.map(this.eachPollResponse, this.props.update)}
</div>
)
}
}
update() is a prop of PollResponse
but I can’t pass that into PollDetail
You could pass it into poll detail like so:
class PollResponse extends Component {
constructor(props) {
super(props);
this.state = {
someResponses: [],
typed: ''
}
}
// Bound function
eachPollResponse = (resp) => {
const remove = () => {}
return (
<PollDetail
onChange={this.props.update}
key={resp.respID}
id={resp.respID}
onRemove={remove}
>
{resp.response}
</PollDetail>
)
}
render() {
return (
<div className="responses" onChange={this.props.update}>
You typed: <code>{this.state.typed}</code>
{this.props.someResponses.map(this.eachPollResponse)}
</div>
)
}
}
Remember, custom functions need to be bound when using ES6 classes, otherwise they won’t get the proper this
reference. Which might also explain why you’re seeing undefined
.
1 Like
Wow. I think was close.
So now the API call can go in the parent update function I guess?
Thanks again for your help.
1 Like
Indeed it should, than you can manage the return data in the Parent and pass it down to the children with props.