react-hook-form: How validate object works - react-hook-form

I see in the docs
<input
{...register("test1", {
validate: {
positive: v => parseInt(v) > 0,
lessThanTen: v => parseInt(v) < 10,
checkUrl: async () => await fetch(),
}
})}
/>
So here how can i show different messages for each validation.
like number is -5, then i show
"Number is not positive"
"Number less than 10"
HOw to access each error positive and lessThanTen

Per default RHF will only show one error per field, so if there are multiple you will you don't have to loop over them. You can just use the errors object provided by RHF and access the name of your field and then the message property.
If you need to have all errors to be shown simultaneously you can set the config criteriaMode, check the docs here for more info.
function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="test1">Test Field</label>
<input
{...register("test1", {
validate: {
positive: (v) => parseInt(v) > 0 || "Number is not positive",
lessThanTen: (v) => parseInt(v) < 10 || "Number less than 10",
checkUrl: async () => {
const result = await Promise.resolve(true);
return result || "result was false so show this message";
}
}
})}
/>
{errors.test1 && <p>{errors.test1.message}</p>}
<input type="submit" />
</form>
);
}

To apply multiple validations, you can build a custom hook as a resolver. A custom hook can easily integrate with yup/Joi/Superstruct as a validation method, and be used inside validation resolver.
You can find more doc and examples here:
https://react-hook-form.com/advanced-usage/
in the section:
Custom Hook with Resolver
I recommend you yup.

Related

React.js - Rails: Set State

I have two components: App and Registration Form
The form has two inputs: Name and Last name
Looking at the App state in dev. tools I see length: undefined and name: "name entered". I'm not getting any errors but I'm missing the last name.
This is only happening in Rails. I tried the same code in a non rails environment and it works fine. I'm using this gem for React: gem 'react-rails', '~> 1.5.0' and running Rails 4.2.4
var App = React.createClass({
getInitialState : function(){
return {
registrations: {}
}
},
addRegistration : function(registration){
// create unique id
var timestamp = (new Date()).getTime();
// update state
this.state.registrations['registration-' + timestamp] = registration;
//set the state
this.setState({ registrations : this.state.registrations });
},
render : function(){
return (
<RegistrationForm addRegistration={this.addRegistration}/>
)
}
});
var RegistrationForm = React.createClass({
createRegistration : function(event){
// prevent default
event.preventDefault();
// take data from form and create object
var registration = {
name : this.refs.name.value,
lastname : this.refs.lastname.value
}
// Add registration to App Object
this.props.addRegistration(registration);
this.refs.registrationForm.reset();
//console.log(registration);
},
render : function(){
return (
<div className="col-sm-12">
<form action="" className="form" ref="registrationForm" onSubmit={this.createRegistration}>
<div className="form-group">
<label >Name</label>
<input className="form-control" ref="name"/>
</div>
<div className="form-group">
<label >Last Name</label>
<input className="form-control" ref="lastname"/>
</div>
<div>
<button className="btn btn-primary">Submit</button>
</div>
</form>
</div>
)
}
});
App = React.createFactory(App)
What I'm trying to do is to give each registration a unique id number based on the time stamp.
When I console log the following:
addRegistration : function(registration){
// create unique id
var timestamp = (new Date()).getTime();
// update state
this.state.registrations['registration-' + timestamp] = registration;
//set the state
this.setState({ registrations : this.state.registrations });
},
I can see a registration object the way I want it. I can add as many unique registrations to the App state but each registration has length: undefined, name: "name" , but it's missing the last name.
If I change the set state to this:
this.setState({ registrations : registration });
This gives me a single registration with name and last name but it doesn't add multiple registrations. It only creates one registration which gets update every time I submit the add registration form.
this.state.registrations['registration-' + timestamp] = registration;
You seem to be mutating the state directly, based on the React Docs https://facebook.github.io/react/docs/component-api.html
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
setState() does not immediately mutate this.state but
creates a pending state transition. Accessing this.state after calling
this method can potentially return the existing value.
There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
setState() will always trigger a
re-render unless conditional rendering logic is implemented in
shouldComponentUpdate(). If mutable objects are being used and the
logic cannot be implemented in shouldComponentUpdate(), calling
setState() only when the new state differs from the previous state
will avoid unnecessary re-renders.
Try cloning the current state then use that as the argument.
// if array
var clonedRegistration = this.state.registrations.slice();
clonedRegistration['registration-' + timestamp] = registration;
this.setState({registrations: clonedRegistration})
or
this.setState({registrations: {['registration-'+ timestamp]: registration} });
I think the answer Road put was close.
First set your initial state to an array.
getInitialState: function(){
return { registrations: []}
}
your addRegistration function
addRegistration : function(registration){
I think this is what you're missing:
//getting current state
var oldRegistrations = this.state.registrations;
Otherwise I believe you're updating the same thing over and over, instead of adding a new registration object. Then push your registration. You should set the timestamp
// update state
oldRegistrations.push(registration);
var registrations = oldRegistrations;
//set the state
this.setState({ registrations : registrations });
},
I would advise creating the id somewhere in here since you're not using an actual ajax call to a rails db:
var registration = {
name : this.refs.name.value,
lastname : this.refs.lastname.value
id: (new Date()).getTime();
}
I'm not sure I understand your question regarding your form values or if you were having trouble with them. But if you were I think doing something like this may help:
<input type='text' className='form-control'
placeholder='Name' name='name'
value={this.state.name} onChange={this.handleChange} >
</input>
<input type='text' className='form-control'
placeholder='Last Name' name='last name'
value={this.state.last_name} onChange={this.handleChange} >
</input>
Then implement a handleChange function within the same component to constantly handle the form's values onChange. That should look like this:
handleChange: function(e) {
var name = e.target.name;
var obj = {};
obj[name] = e.target.value;
this.setState(obj);
}
Hope this helps,

Can't get extra form fields when the form is submitted

My automatic generated base form class is as follows:
abstract class BaseGestorForm extends BaseFormDoctrine {
public function setup() {
$this->setWidgets(array(
'persona_fk' => new sfWidgetFormInputHidden(),
'unitat_fk' => new sfWidgetFormInputHidden(),
'baixa' => new sfWidgetFormDateTime(),
));
$this->setValidators(array(
'persona_fk' => new sfValidatorChoice(array('choices' => array($this->getObject()->get('persona_fk')), 'empty_value' => $this->getObject()->get('persona_fk'), 'required' => false)),
'unitat_fk' => new sfValidatorChoice(array('choices' => array($this->getObject()->get('unitat_fk')), 'empty_value' => $this->getObject()->get('unitat_fk'), 'required' => false)),
'baixa' => new sfValidatorDateTime(array('required' => false)),
));
$this->widgetSchema->setNameFormat('gestor[%s]');
$this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
$this->setupInheritance();
parent::setup();
}
public function getModelName() {
return 'Gestor';
}
}
In the form I added two extra fields (totes_unitats and unitats_a_gestionar). The first field is a drop down list where users select one or more choices and using jquery when a user press a button the options selected are added to unitats_a_gestionar drop down list. At the same time, these options are removed from totes_unitats list.
class GestorForm extends BaseGestorForm {
public function configure() {
unset($this['baixa']);
$this->widgetSchema['persona_fk'] = new sfWidgetFormChoice(array(
'choices' => UsuariLdap::getAllUsuaris()
));
$this->widgetSchema['totes_unitats'] = new sfWidgetFormChoice(array(
'choices' => UnitatTable::findAllUnitatsPerOrdreArray(),
'multiple' => true
));
$this->widgetSchema['unitats_a_gestionar'] = new sfWidgetFormChoice(array(
'choices' => array(),
'multiple' => true
));
$this->widgetSchema->setLabels(array(
'persona_fk' => 'Gestor',
'unitat_fk' => 'Unitat',
'totes_unitats' => 'Totes les unitats',
'unitats_a_gestionar' => 'Unitats a gestionar'
));
$this->validatorSchema->setOption('allow_extra_fields', true);
$this->validatorSchema['persona_fk'] = new sfValidatorString(array('required' => true), array('required' => 'Requerit'));
}
}
Where I find the problem is in the actions file. First of all, I call the executeNouGestor method that renders the form. Then when the user press to proceed and create the Gestor object, it calls executeValidaGestor that validates the form. This last method calls processFormGestor where there is no way to retrieve the unitats_a_gestionar extra field.
public function executeNouGestor(sfWebRequest $request) {
$this->gestorForm = new GestorForm();
}
public function executeValidaGestor(sfWebRequest $request) {
$this->forward404Unless($request->isMethod(sfRequest::POST));
$this->gestorForm = new GestorForm();
$this->processFormGestor($request, $this->gestorForm);
$this->setTemplate('nouGestor');
}
protected function processFormGestor(sfWebRequest $request, sfForm $gestorForm) {
$gestorForm->bind($request->getParameter($gestorForm->getName()), $request->getFiles($gestorForm->getName()));
if ($gestorForm->isValid()) {
var_dump($_POST);
var_dump($request->getParameterHolder()->getAll());
...
}
}
These two var_dump shows me the following information:
var_dump($_POST):
array(2) {
["gestor"]=>
array(2) {
["persona_fk"]=>
string(3) "330"
["_csrf_token"]=>
string(32) "91e18aa0570bfc7558d21ebb4b98f512"
}
["Desar"]=>
string(5) "Desar"
}
var_dump($request->getParameterHolder()->getAll()):
array(4) {
["gestor"]=>
array(2) {
["persona_fk"]=>
string(3) "330"
["_csrf_token"]=>
string(32) "91e18aa0570bfc7558d21ebb4b98f512"
}
["Desar"]=>
string(5) "Desar"
["module"]=>
string(13) "administracio"
["action"]=>
string(12) "validaGestor"
}
So, as you can see, in ["gestor"] there is no track neither totes_unitats nor unitats_a_gestionar extra form fields. I have no idea why. The way I show the form fields in the template is as usual:
<?php echo $gestorForm['persona_fk']->renderLabel(); ?>
<div class="input"><?php echo $gestorForm['persona_fk']->render(); ?></div>
<div class="error-input"><?php echo $gestorForm['persona_fk']->renderError(); ?></div>
<?php echo $gestorForm['totes_unitats']->renderLabel(); ?>
<div class="input multiple"><?php echo $gestorForm['totes_unitats']->render(); ?></div>
<div class="error-input"><?php echo $gestorForm['totes_unitats']->renderError(); ?></div>
<?php echo $gestorForm['unitats_a_gestionar']->renderLabel(); ?>
<div class="input multiple"><?php echo $gestorForm['unitats_a_gestionar']->render(); ?></div>
<div class="error-input"><?php echo $gestorForm['unitats_a_gestionar']->renderError(); ?></div>
I also add the jquery code that manage the options added or removed between the two drop down lists with multiple selection:
function afegirTreureUnitats() {
var boto_afegir = $("#btn-multiple-afegir");
var boto_treure = $("#btn-multiple-treure");
boto_afegir.click(function() {
var selectedItems = $("#gestor_totes_unitats option:selected");
var output = [];
$.each(selectedItems, function(key, e)
{
output.push('<option value="' + e.value + '">' + e.text + '</option>');
});
$("#gestor_unitats_a_gestionar").append(output.join(""));
ordenaOptionsSelect("gestor_unitats_a_gestionar");
selectedItems.remove();
});
boto_treure.click(function() {
var selectedItems = $("#gestor_unitats_a_gestionar option:selected");
var output = [];
$.each(selectedItems, function(key, e)
{
output.push('<option value="' + e.value + '">' + e.text + '</option>');
});
$("#gestor_totes_unitats").append(output.join(""));
ordenaOptionsSelect("gestor_totes_unitats");
selectedItems.remove();
});
}
function ordenaOptionsSelect(idSelect)
{
var options = $('#' + idSelect + ' option');
options.sort(function(a, b)
{
if (a.text > b.text)
return 1;
else if (a.text < b.text)
return -1;
else
return 0;
});
$('#' + idSelect).empty().append(options);
}
$(document).ready(function() {
afegirTreureUnitats();
});
The form rendered has this appearance:
https://drive.google.com/file/d/0B0Mz720p9Q_DN1RnYWIyR0pXOTQ/edit?usp=sharing
I have also found a curious fact. If I select one of the options in the drop down list, either totes_unitats or unitats_a_gestionar they are sent through POST method, but only one (if I select more than one I only can get one of the options selected).
When you use <select> elements on the form, or <input> of type radio or checkbox the browser will send their values on form submission only when the fields have any options selected.
The list of options in the <select> tag is not sent back to the server. Only the values of the options which are actually selected.
You can resolve your problem in two ways:
Either create a JS which will modify your form just before sending it and will select all the items on both your lists. This way your form will send the values and you will be able to work with them on the server side.
Other option is to add two hidden fields which will keep the lists of the options and modify these lists together with the <select> fields.

Use Model object value in Client template Telerik Grid mvc

I need to display two different values in the grid based on the Model Object property.I am able to do it using Template but not client template.
How to do it?
I have used the below code but i am getting error that IsContractORPO is undefined.
#{
Model.IsContractORPO = "P";
}
<div>
#{Html.Telerik().Grid(Model.TestList)
.Name("testGrid")
.Columns(columns =>
{
columns.Bound(col => col.FTE);
columns
.Template(#<text>#if (Model.IsContractORPO == "C")
{<div>#item.FTE</div>}
else if (Model.IsContractORPO == "P")
{<div>-</div>}</text>).Title("FTETestColumn")
.ClientTemplate("<# if (model.IsContractORPO == 'C') { #> <div><#=FTE#></div><# } else if(model.IsContractORPO == 'P'){ #><div>-</div><# } #>");
columns.Bound(col => col.BalanceAmount);
columns.Bound(col => col.BalanceUnits);
})
.ClientEvents(events => events.OnDataBinding("onDataBinding"))
.Render();
}
</div>
After sometime of analysis, i came to know that we can use a model property in the client template by making it as a script variable.
The following code solved the Issue.
<script type="text/javascript">
var isContractORPO = '#Model.IsContractORPO';
</script>
And the client template would be like
ClientTemplate("<# if (isContractORPO == 'C') { #> <div><#=FTE#></div><# } else if(isContractORPO == 'P'){ #><div>-</div><# } #>");

Symfony 1.3: Any opinion about this code? Could be shorter or better?

I need your opinion about this code below.
I have a list of messages: each message has a link that change the state
of the message (read - non read).
In the partial "_message" i have this:
<div class="switching_link" id="switching_link_<?php echo $message ?>">
echo include_partial('link_switch_state', array('message' =>
$message))
</div>
In the partial "_link_switch_state" i have this:
if((int)$message->getState() == 1) {
$string_state_message="non read";
} else {
$string_state_message="read";
}
echo link_to_remote('Mark as '.$string_state_message, array(
'url' => 'message/switchState?id='.$message->getId(),
'update' => 'switching_link_'.$message,
"complete" => "switchClassMessage('$message');",
));
And in message/actions/actions.class.php i have this:
public function executeSwitchState(sfWebRequest $request) {
// searching the message we want to change its state.
$this->messages =
Doctrine::getTable('Message')->findById($request->getParameter('id'));
// changing the state of the message.
if($this->messages[0]->getState() == 1) {
$this->messages[0]->setState(0);
}
else {
$this->messages[0]->setState(1);
}
$this->messages[0]->save();
// rendering the partial that shows the link ("Mark as read/non
read").
return $this->renderPartial('mensaje/link_switch_state', array(
'message' => $this->messages[0]));
}
Mmmh not sure if you can write it shorter in Symonfy, but you can get rid of the if-else statements (assuming that the state can either be 0 or 1):
In _message:
$string_state_message = ($message->getState()) ? "non read" : "read";
0 and "0" evaluate to false, any other non-empty string to true.
In message/actions/actions.class.php:
$this->messages[0]->setState(!$this->messages[0]->getState());
This is again due the fact that 0 == false and 1 == true.

Passing potential nulls to Macros in Spark view engine

I have the following macro:
<macro name="InputField" id="string" value="string">
...
<input type="text" id="${id}" name="${id}" value="${value} />
...
</macro>
And the call to the macro:
${InputField( "model.address.address1", 75, "Address", model.Address.Address1 )}
The only problem is that model.Address will be null in some situations
(creating the item instead of editing it), because of this the macro
doesn't run or fails and just outputs the macro call to the view.
How can I pass either "" or the value of model.Address.Address1
depending if Address is null or not? the null operator ($!{}) doesnt
seem to work in this instance.
Solution 1. Write method
public static string HandleNull(Func<object> func)
{
try { return func().ToString(); }
catch (NullReferenceException) { return ""; }
}
and use it instead of Spark macro.
${InputField( "model.address.address1", 75, "Address", HandleNull(() => model.Address.Address1) )}
Solution 2. Use http://www.jmill.net/taxonomy/term/312
Solution 3.
<macro name="InputField" id="string" value="Func<string>">
...
<input type="text" id="${id}" name="${id}" value="$!{value()} />
...
</macro>
${InputField( "model.address.address1", 75, "Address", () => model.Address.Address1 )}
All the solutions depend on deferred execution.

Resources