Reputation: 93
There's a parent, <MessageBox />
element, which contains a list of messages
stored in its state. For each message
in messages
, a <Message />
element is created inside <MessageBox />
which has fields for message.subject
and message.body
. A user can edit the message.subject
and message.body
and once done, the message
object is sent back to <MessageBox />
through a props.updateHandler()
to maintain the message state in the parent.
In my current approach, I'm storing the message data in MessageBox
's state and in the render()
function, I'm creating the <Message />
elements and passing a callback to each of them to send back data changes. In the callback, the updated data from each of the <Message />
elements is updated back into MessageBox
's state. The reason for this is to keep all the recent updated data in one place only. The above approach creates havoc if shouldComponentUpdate()
method in <Message />
is not overloaded (infinite recursion).
Is there a better approach for this? I've to write a lot of code just to override the builtin methods to keep the entire thing stable. As I'm not planning to go for Flux/Redux, is there a React-only approach for this?
EDIT: Since there's a lot of confusion, I'm adding minimal code.
class Message extends React.Component {
constructor(props) {
this.state = {
subject: this.props.subject,
body: this.props.body,
type: this.props.type,
messageIndex: this.props.messageIndex
};
}
componentDidUpdate() {
this.props.updateHandler(messageIndex, {
subject: this.state.subject,
body: this.state.body,
type: this.state.type
});
}
render() {
return (
<div>
<input
type="text"
defaultValue={this.state.subject}
onBlur={e => this.setState({subject: e.target.value})} />
<input
type="text"
defaultValue={this.state.subject}
onBlur={e => this.setState({body: e.target.value})} />
<select
type="text"
value={this.state.subject}
onChange={e => this.setState({type: e.target.value})}>
<option>Type 1</option>
<option>Type 2</option>
</select>
</div>
)
}
}
class MessageBox extends React.Component {
constructor(props) {
this.state = {
messages: aListOfMessageObjects
}
}
updateHandler(message) {
// Message update happens here and returns a list updatedMessages
this.setState({
messages: updatedMessages
});
}
render() {
let _this = this;
var messagesDOM = this.state.messages.map((m) => {
return (
<Message
message={m}
updateHandler={_this.updateHandler.bind(_this)} />
);
})
return (
<div>
{messagesDOM}
</div>
);
}
}
Upvotes: 0
Views: 645
Reputation: 8071
MessageBox
from componentDidUpdate()
of Message
. Do a callback directly from an action in Message.Message
component at all. Props will keep the values you are interested if you update parent's state properly.What you need is something like:
<input type="text"
defaultValue={this.props.subject}
onBlur={e => this.updateSubject(e.target.value)} />
updateSubject: function(newSubjectValue) {
this.props.updateHandler(messageIndex, {
subject: newSubjectValue,
body: this.props.body,
type: this.props.type
});
}
That way the component will get re-rendered, but won't do another call to the parent's setState.
Upvotes: 1
Reputation: 11693
If that can help, read thinking-in-react. It explains how data should go only one way to avoid be lost in UI updates.
React ToDo MVC will provide you an example of React good practice on a real case
To know how to pass props from your parent to children read controlled-components. you'll have to use value
and onBlur
on each input. Any onBlur
event will call this.props.updateHandler
with e
as parameter instead of e => this.setState({type: e.target.value})
.
Upvotes: 1