ReactJS: How do I access my models in event handlers without binding? - javascript

Say I have a ReactJS component that represents a "Document" containing "Paragraph"s, each containing "Sentences" which I want rendered into contenteditable spans.
var paragraphData = [{
id: 1,
sentenceData: [
'Paragraph 1, Sentence 1',
'Paragraph 1, Sentence 2'
]
},{
id: 2,
sentenceData: [
'Paragraph 2, Sentence 1',
'Paragraph 2, Sentence 2'
]
}];
var Sentence = React.createClass({
render: function () {
return (<span
contenteditable="true"
onKeyDown={this.props.onKeyDown}>
{this.props.value}
</span>);
}
});
var Paragraph = React.createClass({
render: function () {
var me = this;
var sentences = this.props.sentenceData.map(function (sentenceData) {
return <Sentence value={sentenceData} onKeyDown={me.props.onKeyDown} />;
});
return <div>{sentences}</div>;
});
var Document = React.createClass({
render: function () {
var me = this;
var paragraphs = this.props.paragraphData.map(function (paragraphData) {
return <Paragraph sentences={paragraphData.sentenceData} onKeyDown={me.onKeyDown}>;
});
return <div>{paragraphs}</div>;
},
onKeyDown: function (e) {
// If "Enter" is pressed, I want to split the sentence at
// getSelection().focusOffset, update the current sentence's
// value to currentValue.substr(0, focusOffset) and insert a
// new sentence with value currentValue.substr(focusOffset),
// but how do I know which paragraph/sentences I need to
// inspect/change? Is "e.target" the only thing I have to go by?
}
});
In ReactJS, the idea is for data to flow up and events to flow down. (Which one is referred to as "up" or "down" seems to change all the time, but hopefully you know what I mean.)
My Question:
In my onKeyDown handler, how do I know which models need to have changes applied?
I thought about using .bind() to bind the handler to each model as it was passed up, but it seems a bit... wrong:
Would that be considered tight coupling between model/view?
It would mean binding hundreds or thousands of times (potentially, on a large document), each time creating a new function - which would go against the best-practice "don't create functions in a loop" principle.
I get the feeling I'm heading in the wrong direction - any help much appreciated!

It is perfectly fine to bind the onKeyDown function.
If you implement shouldComponentUpdate in Sentence, when your app is rendered again, the binded functions won't be created again because these components will not render if they have not change.
I don't think the memory overhead of 1000 or 10000 functions has too much impact, and you should not try to optimize this unless you have perf problems. What you don't want is create all these functions everytime on each render, and this is why shouldComponentUpdate is here.
It won't couple more your components that they are already (they are, because they interact together already on a well defined business context). Basically you could create a generic, uncoupled component that will receive any piece of data and on key down on the rendering of that data, will inject that data to a callback. It is generic and does not add coupling, you can control the entire behavior outside of the component.
Notice that binding functions coming from props or in loops to dom event listeners is not something forbidden by React and actually you can find exemples where it is done.
In a loop (that may have many items!): http://facebook.github.io/react/tips/communicate-between-components.html
With binded function props: (can't find it, but I do use it myself...)
Notice that in React, it is forbidden to rebind a function of a component because React bind all functions to its component.
So if a function is in Document and is injected to Sentence, you can't bind it to this in Sentence because it does not make sens and is forbidden by React.
This is the code that does it: https://github.com/facebook/react/blob/95de877dceeaac08755cfe1142a853c467d91d58/src/core/ReactCompositeComponent.js#L1291
if (newThis !== component && newThis !== null) {
monitorCodeUse('react_bind_warning', { component: componentName });
console.warn(
'bind(): React component methods may only be bound to the ' +
'component instance. See ' + componentName
);
}
Note that a newThis !== null has been added. This was actually added to fix this problem when trying to bind props functions.
Now you can write
<Sentence value={sentenceData} onKeyDown={me.props.onKeyDown.bind(null,sentenceData} />
And on document you have a listener:
onKeyDown: function (sentenceData) { }
This is perfectly fine :)
Notice that there is another way this problem can be solved which may be a little bit more efficient:
var Sentence = React.createClass({
onKeyDown: function(e) {
this.props.onKeyDown(this.props.value)
},
render: function () {
return (<span
contenteditable="true"
onKeyDown={this.props.onKeyDown}>
{this.props.value}
</span>);
}
});
Instead of binding a function, you simply create a class that has this unique function that you would normally bind. In this case I think however it creates more coupling as you don't really control the behavior of the callback from outside the Sentence component.

You're concerning yourself with the wrong problem.
Wherever the relevant onKeyDown handler that you're referring to should be working (i.e. for sentences, or paragraphs), you need to handle the data change and then call:
this.setState({stateName: newdata})
This will cause react to re-render the component with the new data.
For a good example, look at facebook's tutorial for a comment widget.
You can see how the form handles the data change by adding new comments, and then calls setState() to re-render with the additional comments. React handles everything else.
In your example, if you change something within a paragraph, the paragraph should call setState with the new sentence data after you've done all the work you need, and then react will handle the rest.

Related

The correct way to store complex form data in React app

I have a complex form in React containing a dozen sub-components. It also includes my own form components that operate by not single values. Some parts of the form use trees of objects as data.
I have the main component (let's call it EditForm) which stores the whole object tree of data. I assume each component of this form should use a specific part of this main object. I should have the ability to store whole form data into JSON and read it back from JSON (and update all sub-components automatically when JSON changed).
The idea was to refresh only those parts of the form that required when I change the main JSON. But the code which handles this behavior is dramatically big and complex. I have to implement comparison functionality into each sub-component and also transfer parts of the main JSON into props of each sub-component.
const [testobj, setTestobj] = useState({'obj1': {......}, 'obj2': {.......}, .......});
function revalidateSub(sub_name, value)
{
let t2 = cloneDeep(testobj);
t2[sub_name] = value;
setTestobj(t2);
}
return (<MySubComponent subobj={testobj['sub1']} ident={"sub1"} onUpdateData={v => revalidateSub('sub1', v)}/>)
(subobj is the piece of data which is controlled by a specific sub-component, onUpdateData is a handler which is called inside the subcomponent each time when it makes any changes to a controlled piece of data)
This scheme, however, does not work as expected. Because revalidateSub() function stores its own copy of testobj and onUpdateData of each component rewriting the changes of other sub-components...
To be honest, I don't quite understand how to store data correctly for this type of React apps. What do you propose? Maybe I should move the data storage out of React components at all? Or maybe useMemo?
Thank you for the shared experience and examples!
Update1: I have coded the example in the Sandbox, please check
https://codesandbox.io/s/thirsty-browser-xf4u0
To see the problem, please click some times to the "Click Me!" button in object 1, you can see the value of obj1 is changing as well as the value of the main object is changing (which is CORRECT). Then click one time to the "Click Me!" of the obj2. You will see (BOOM!) the value of obj1 is reset to its default state. Which is NOT correct.
I think I have solved this by myself. It turned out, that the setState function may also receive the FUNCTION as a parameter. This function should return a new "state" value.
I have moved revalidateSub() function OUT from the EditForm component, so it does not duplicate the state variable "testobj" anymore. And it is now working as expected.
Codebox Page
First of all, I think the idea is correct. In React components, when the value of the props passed from the state or the parent component changes, re-rendering should be performed. Recognized as a value.
Because of this issue, when we need to update a multilayered object or array, we have to use ... to create a new object or array with an existing value.
It seems to be wrong during the copying process.
I don't have the full code, so a detailed answer is not possible, but I think the bottom part is the problem.
let t2 = cloneDeep(testobj);
There are many other solutions, but trying Immutable.js first will help you find the cause of the problem.
Consider also useReducer.
update
export default function EditForm(props) {
const [testobj, setTestobj] = useState({
sub1: { id: 5, v: 55 },
sub2: { id: 7, v: 777 },
sub3: { id: 9, v: 109 }
});
function revalidateSub(sub_name, value) {
let t2 = cloneDeep(testobj);
t2[sub_name] = value;
return t2;
}
console.log("NEW EditForm instance was created");
function handleTestClick() {
let t2 = cloneDeep(testobj);
t2.sub2.v++;
setTestobj(t2);
}
return (
<div>
<div style={{ textAlign: "left" }}>
<Button onClick={handleTestClick} variant="contained" color="secondary">
Click Me to change from main object
</Button>
<p>
Global value:{" "}
<span style={{ color: "blue" }}>{JSON.stringify(testobj)}</span>
</p>
<MyTestObj
subobj={testobj["sub1"]}
ident={"sub1"}
onUpdateData={(v) => setTestobj((p) => revalidateSub("sub1", v))}
/>
<MyTestObj
subobj={testobj["sub2"]}
ident={"sub2"}
onUpdateData={(v) => setTestobj((p) => revalidateSub("sub2", v))}
/>
<MyTestObj
subobj={testobj["sub3"]}
ident={"sub3"}
onUpdateData={(v) => setTestobj((p) => revalidateSub("sub3", v))}
/>
</div>
</div>
);
}
The reason it bursts when you do the above setState updates the local state asynchronously.
The setState method should be thought of as a single request, not an immediate, synchronous execution. In other words, even if the state is changed through setState, the changed state is not applied immediately after the method is executed.
Thus, your code
setTestobj((p) => revalidateSub(p, "sub3", v))
It is safer to use the first parameter of setstate to get the full state like this!
Thanks to this, I was able to gain new knowledge. Thank you.

What would happen if React doesn't auto bind instance methods for you in a component?

Learning React and seeing the below code I read that React auto binds for you so the thiss in 1 and 2 work out well. I was wondering what would the this in each one refer to if React didn't auto bind it for you? Wouldn't the 2 this refer tot he input element and thus not be able to get to the 1 this?
var ContactForm = React.createClass({
propTypes: {
// ...
},
onNameInput: function(e) {
//1
this.props.onChange(Object.assign({}, this.props.value, {name: e.target.value}))
},
render: function() {
return React.createElement('form', {className: 'ContactForm'},
React.createElement('input', {
// ...
//2
onInput: this.onNameInput,
})
// ...
)
}
});
You can try this out for yourself by seeing what this component will do when you extend React.Component instead of using createClass({}).
That said, this is really just a JS question. this points to different things depending on how its used. If auto-binding did not take place in the above example, then it would most likely refer to window, if you were running React in the browser. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this for more details.
Wouldn't the 2 this refer tot he input element and thus not be able to get to the 1 this?
this #2 is in the scope of its surrounding function, specifically the render function. It points to whatever the this scope of that function is, which in this case is the component instance. During the assignment onInput: this.onNameInput, you are dereferencing onNameInput from the component instance, so when you make that assignment, it may no longer be bound to that instance. If onNameInput was never bound, when it is called its this will be window.

How can I access child components values from a parent component when they are added dynamically?

Current Working Example
I am creating a search form that has a varying number of input elements based on the users selection from a select box.
I have broken this up into three components, a wrapper called SearchContainer, a select box called SearchSelect, and the inputs within components called SearchWithTwo and SearchWithOne just for the sake of the example.
App
└─SearchContainer Form
│ SearchSelect
│ ... any one of the multiple search inputs (SearchWithOne, SearchWithTwo)
When a user changes the value of the select box the related component which contains the inputs is loaded. The component could have anywhere from one to ten inputs. All the examples I've seen mention using ref which would be great if my inputs weren't changing.
I currently have it working by using the following in the onSubmit handler for SearchContainer
handleSubmit: function(e) {
e.preventDefault();
var form = this.getDOMNode();
[].forEach.call(form.elements, function(e){
// get the values
});
// submit the values to get results.
}
However this doesn't feel like the proper way to be doing this. Is there a better recommended way to iterate through the children components and read their state? Or can I somehow pass the children into the parent state and get the values that way?
I think I have a solution in the form of a fork of your fiddle, and I'll cover the main ideas below.
First, I'm no React expert, but I like the idea of it, and I know it's gaining popularity so I want to learn more. What I don't know is the right way to use composition or inheritance to reduce the code duplication shown below.
Basically, my idea is to add a method to each search class that exposes its state to calling classes. This is implemented here as a very simple function inside the createClass call:
getData: function() {
return this.state;
},
It's so simple, there has to be a way to create a base class or mixin class with this method and then inherit/compose over it with other classes. I'm just not sure how. For now, you can just copy-paste these lines wherever it makes sense to expose a component's state.
Keep in mind, this is anathema to the Flux architecture everyone loves. In Flux, state always comes from a source object which lives outside the React components.
Anyway, abandoning larger architecture concerns for now, you can just grab that state variable by calling getData in the handleSubmit method. No DOM traversal required:
handleSubmit: function(e) {
e.preventDefault();
var form = this.getDOMNode(),
fd = new FormData(form);
var submitData = this.state.SearchBox.getData();
// submit the values to get results.
},

React js - Disable render of a component in a mixin

I'm trying to develop a React mixin to check the user access level before rendering the component.
If the user doesn't have the permission to see the component, I would like to disable the rendering of the component.
I've been looking for something build in react to handle this but found nothing, so I did that:
var AuthentLevelMixin = {
componentWillMount: function() {
if(!Auth.check()) {
// Disable component render method
this.render = function () {
return false;
}
}
}
}
It works as expected but I feel like it's the "dirty way".
So my question is: what is the "React way" for doing the same as this snippet ?
For a mixin this is about the best you can do. It's just a simple early return in render.
var AuthentLevelMixin {
isAuthenticated: function(){
return Auth.check();
}
};
var C = React.createClass({
mixins: [AuthentLevelMixin],
render: function(){
if (!this.isAuthenticated()) return <div />;
return (
<div>...</div>
);
}
});
If you decide to go with your initial strategy (I don't recommend it), it just needs to be modified slightly:
// more explicit names are important for dirty code
var PreventRenderUnlessAuthMixin = {
componentWillMount: function() {
this._originalRender = this.render;
this._setRenderMethod();
},
componentWillUpdate: function(){
this._setRenderMethod();
}.
_emptyRender: function () {
return <span />;
},
_setRenderMethod: function(){
this.render = Auth.check() ? this._originalRender : this._emptyRender;
}
}
If you want to handle the authorization inside your mixin without adding logic to your component you are doing it the right way. BUT: Every component implementing this mixin should then be aware of what happens within this mixin. If the result you expect is, that nothing is rendered, then you are perfectly right with what you are doing. So if your way is resulting in simplicity it is the React-Way. And in my Opinion this is the case.
In the componentWillMount lifecycle event you will capture the moment right before rendering - which is a great time to prevent rendering. So I really dont see anything speaking against your code.
EDIT:
aproach of defining: "react way"
Once you have the same input resulting in the same output every time your code becomes predictable. With your code being predictable you achieve simplicity. These are terms used by Pete Hunt to describe the intentions of React. So therefor if you stay predictable and in result achieving simplicity you are doing it the react way.
In case of the above mixin both these rules apply and is therefor the "react way" in the definition I have provided above.
My advice here would be to not use a mixin. The best way to clean up your component is to remove this logic from the component, and simply not render the component based on the result of checking Auth.
The problem with this is that you have a component that is no longer consistent, because it depends on something other than its props. This doesn't really do much other than push the problem upwards, but it does allow you to have one more pure component.
I can see why the mixin is attractive though, so here's a simpler way of doing what you need that doesn't involve dynamically swapping the render method:
var PreventRenderUnlessAuthMixin = {
componentWillMount: function () {
var oldRender = this.render;
this.render = function () {
return Auth.check() ? this.render() : <div />
}.bind(this);
}
}

How to go from jQuery to React.js? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I have been reading up on React for a few days nows. I can understand most of what I'm looking at, but I'm not entirely confident in my ability to write it. I have been working on a small web app that does all of its html generation through jQuery and appending elements to each other. I'd like to try and rebuild this with React because I believe that it would be faster. This JSFiddle is a small example of the sort of thing I am working on. How would you write it with React?
JS:
function remove() {
this.remove();
}
function timed_append_new_element() {
setTimeout(function () {
var added_content = $("<span />", {
class: "added_content",
text: "Click to close",
click: remove
});
container.append(added_content);
}, 3000);
}
function append_new_element() {
var added_content = $("<span />", {
class: "timed_added_content",
text: "Click to close",
click: remove
});
container.append(added_content);
}
var container = $("<div />", {
class: "container"
});
var header = $("<span />", {
class: "header",
text: "jQuery to React.js Header"
});
var add_button = $("<button />", {
class: "add_button",
text: "Add Element",
click: append_new_element
});
var timed_add_button = $("<button />", {
class: "add_button",
text: "Add Element in 3 seconds",
click: timed_append_new_element
});
container.append(header);
container.append(add_button);
container.append(timed_add_button);
$("body").append(container);
There are a few basic tenets to keep in mind that may help you build a good React application:
Your UI should be a function of the data
In many "jQuery soup" style applications, the business logic for the application, the app's data, and the UI interaction code are all intermingled. This makes these sorts of applications difficult to debug and, especially, difficult to grow. React, like many modern client-side application frameworks, enforce the idea that the UI is just a representation of your data. If you want your UI to change, you should change a piece of data and allow whatever binding system the framework uses to update the UI for you.
In React, each component is (ideally) a function of two pieces of data–the properties passed to the component instance, and the state that the component manages internally. Given the same properties (or "props") and state, the component should render in the same way.
This can be a bit of an abstract idea without concrete examples, so keep it in mind as we move on for now.
Don't touch the DOM
In React, even more so than other data-bound frameworks, you should try not to manipulate the DOM directly if at all possible. A lot of React's performance and complexity characteristics are only possible because React uses a virtual DOM with diffing algorithms internally to operate on the real DOM. Any time you build a component that reaches out and does its own DOM manipulation, you should ask yourself if you could build the same feature more idiomatically with React's virtual DOM features.
Of course, sometimes you'll need to access the DOM, or you'll want to incorporate some jQuery plugin without rebuilding it in React. For times like these, React gives you good component lifecycle hooks that you can use to ensure that React's performance doesn't suffer too much (or, in some cases, to keep your component from plain breaking).
Not manipulating the DOM goes hand-in-hand with "UI as a function of the data," above.
Invert the data flow
In a large React application, it can be difficult to keep track of which sub-component is managing a certain piece of application data. For this reason, the React team recommends keeping data manipulation logic in a central location. The most straightforward way to do this is to pass callbacks into child components; there's also an architecture developed at Facebook called Flux which has its own website.
Create composable components
A lot of times, it can be tempting to write a large component that manages several pieces of state or several pieces of UI logic. Where possible (and within reason), you should consider breaking larger components into smaller ones that operate on a single piece of data or UI logic. This makes it much easier to extend and move around pieces of your application.
Beware mutable data
Since component state should only be updated via calls to this.setState from within the component, it's helpful to be wary of mutable data. This is doubly true when multiple functions (or components!) might update the mutable object in the same tick; React might try to batch state changes, and you could lose updates! As mentioned in the comments by Eliseu Monar, consider cloning mutable objects before mutating them. React has immutability helpers that can assist.
Another option is to forgo keeping mutable data structures directly in state at all; the Flux pattern, mentioned above, is an interesting take on this idea.
There's a great article on the React site called Thinking in React which goes over how you might take an idea or a mockup and turn it into a React application, and I strongly encourage going over it. As a concrete example, let's take a look at the code you provided. You essentially have one piece of data to manage: a list of content that exists inside the container element. All the changes to your UI can be represented by additions, removals, and changes to that data.
By applying the tenets above, your final application might look something like this:
/** #jsx React.DOM */
var Application = React.createClass({
getInitialState: function() {
return {
content: []
};
},
render: function() {
return (
<div className="container">
<span className="header">jQuery to React.js Header</span>
<button className="add_button"
onClick={this.addContent}>Add Element</button>
<button className="add_button"
onClick={this.timedAddContent}>Add Element in 3 Seconds</button>
{this.state.content.map(function(content) {
return <ContentItem content={content} removeItem={this.removeItem} />;
}.bind(this))}
</div>
);
},
addContent: function() {
var newItem = {className: "added_content", text: "Click to close"},
content = this.state.content,
newContent = React.addons.update(content, {$push: [newItem]});
this.setState({content: newContent});
},
timedAddContent: function() {
setTimeout(function() {
var newItem = {className: "timed_added_content", text: "Click to close"},
content = this.state.content,
newContent = React.addons.update(content, {$push: [newItem]});
this.setState({content: newContent});
}.bind(this), 3000);
},
removeItem: function(item) {
var content = this.state.content,
index = content.indexOf(item);
if (index > -1) {
var newContent = React.addons.update(content, {$splice: [[index, 1]]});
this.setState({content: newContent});
}
}
});
var ContentItem = React.createClass({
propTypes: {
content: React.PropTypes.object.isRequired,
removeItem: React.PropTypes.func.isRequired
},
render: function() {
return <span className={this.props.content.className}
onClick={this.onRemove}>{this.props.content.text}</span>;
},
onRemove: function() {
this.props.removeItem(this.props.content);
}
});
React.renderComponent(<Application />, document.body);
You can see the code in action in this JSFiddle: http://jsfiddle.net/BinaryMuse/D59yP/
The application is made of two components: a top-level component called Application, which manages (in its state) an array called content, and a component called ContentItem, which represents the UI and behavior of a single item from that array. Application's render method returns a ContentItem element for each item in the content array.
One thing to notice is that all of the logic for managing the values inside the content array are handled in the Application component; the ContentItem components are passed a reference to Application's removeItem method, which the ContentItem delegates to when clicked. This keeps all the logic for manipulating state inside the top-level component.

Categories

Resources