React.js - Rails: Set State - ruby-on-rails

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,

Related

react-hook-form: How validate object works

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.

Rails 5.2 - Removing the data-remote attribute doesn't submit the form as an HTML request

I am rendering a search form in Rails 5.2.2 and have logic that determines if the request should be an AJAX request or an HTML request. Here's what my form looks like:
<form id="form" action="/search" data-prev-query="test" data-remote="true" method="get">
<input type="text" name="q" id="search_query" value="test">
</form>
If the search query is different than the data-prev-query attribute on the form, then I would like the form submission to be an HTML request so that it'd reload the page. Otherwise, I'd like the form submission to be an AJAX request. Here's my logic for removing the data-remote property from the form programmatically:
$("#form").find("#search_query").change(function(e)
{
var theForm = $("#form");
var currentSearchQuery = theForm.find("#search_query").val();
var prevSearchQuery = theForm.data("prev-query");
var isRemote = (currentSearchQuery === prevSearchQuery);
if (isRemote === true)
theForm.attr('data-remote', isRemote);
else
theForm.removeAttr('data-remote');
});
I've confirmed that this code removes or adds the data-remote attribute as expected. However, even though the data-remote attribute is removed, the form is still submitted as an AJAX request.
How can I get the form to submit as an HTML request? I am not using Turbolinks, by the way.
As #chumakoff shared in a comment, I found my answer on this SO post. I also had to call removeData('remote') to clear jQuery's internal cache. Below is my updated code that works. I put a comment next to the added lines.
$("#form").find("#search_query").change(function(e)
{
var theForm = $("#form");
var currentSearchQuery = theForm.find("#search_query").val();
var prevSearchQuery = theForm.data("prev-query");
var isRemote = (currentSearchQuery === prevSearchQuery);
if (isRemote === true)
{
theForm.removeData('remote'); // ADDED LINE
theForm.attr('data-remote', isRemote);
}
else
{
theForm.removeAttr('data-remote');
theForm.removeData('remote'); // ADDED LINE
}
});

Angular 5 - How to fill a FormArray

I will fill the customerNumberContainers which looks like this:
this.form = new FormGroup({
customerNumberContainers: new FormArray([
new FormGroup({
contactTenant: new FormControl('', [Validators.required, Validators.minLength(2)]),
customerNumber: new FormControl('', [Validators.required, Validators.minLength(2)])
}),
]),
Therefore I do this after I get the values over
this.contactService.findContactById(this.id).subscribe(response => { ...
Set values into form:
let customerNumberContainersFormArray: FormArray = this.form.controls.customerNumberContainers as FormArray;
customerNumberContainersFormArray.controls["0"].controls.contactTenant.value = 'TestValue';
but it is not shown with:
in Controller:
get customerNumberContainers(): FormArray {
return this.form.get("customerNumberContainers") as FormArray;
}
in Template:
<div formArrayName="customerNumberContainers">
<div *ngFor="let customerNumberContainer of customerNumberContainers.controls; index as i" [formGroupName]="i">
<mat-input-container class="full-width-input">
<input matInput formControlName="contactTenant">
</mat-input-container>
</div>
Does anyone known what I am doing wrong. It seems for me that values with *ngFor arn't refreshed.
why dont You just patch whole form with model ? like this:
set up your model, for example:
export class Tenants {
id: number;
customerNumberContainers: TenantContact[];
}
export class TenantContact {
contactTenant: string;
customerNumber: string;
}
fetch it from service like u always do but it should match above models and patch whole form (or setValue)
this.contactService.findContactById(this.id).subscribe((tenats: Tenants) => {
this.form.patchValue(tenats);
});

codeigniter jquery sortable save to database

I am trying to save the order of a list of images to the database when using jquery sortable.
I feel i am very close, but cant get my head around the final details.
I am working with CI 2.1.3 and jquery-ui 1.10.3.
I have a dynamicaly generated list with an image:
<ul id="order">
<li id="item-1"><img src="abc.jpg" /></li>
<li id="item-2"><img src="def.jpg" /></li>
<li id="item-3"><img src="ghi.jpg" /></li>
</ul>
And the following Jquery:
<script>
$(document).ready(function() {
$( "#order" ).sortable({
opacity: 0.6,
cursor: 'move',
update: function(event, ui){
var order = $(this).sortable("serialize");
console.log(order);
$.ajax({
url: "http://localhost/gridrobin/home/save_order",
type: 'POST',
data: order,
success: function (data) {
$("#test").html(data);
}
});
}
});
});
</script>
This works fine and i can reorder my list. Now i want to save the new order to the database. I send the ajax post to the controller and it comes through. I checked with a var_dump.
//var_dump($_POST);
$items = $this->input->post('item');
$total_items = count($this->input->post('item'));
echo '<h3>Debugging</h3>';
echo "<p>Total items sent: $total_items</p>";
$this->rd_model->update_order($total_items, $items);
Then I send this data to my model:
for($item = 0; $item < $total_items; $item++ )
{
$data = array(
'id' => $items[$item],
'order' => $order = $item
);
$this->db->where('id', $data['id']);
$this->db->update('portfolio_items', $data);
echo '<br />'.$this->db->last_query();
}
And echo out the last db-query for debugging.
Now when i switch item 1 and item 2, i get a 500 internal error. When i switch them back, i receive the echo of the last query executed, which seems fine.
UPDATE `portfolio_items` SET `order` = 1 WHERE `id` = '1'
UPDATE `portfolio_items` SET `order` = 2 WHERE `id` = '2'
UPDATE `portfolio_items` SET `order` = 3 WHERE `id` = '3'
I dont quite understand why the database updates when th list is switched back to its orignial state, but not otherwise.
UPDATE
For people with the same problem, sakibmoon answer helped me a lot, but the main problem was a duplicate entry error, because apparently i had set the order table as a unique index...
The problem is within your data array in the model. Change that with this -
$data = array(
'id' => $items[$item],
'order' => $item+1
);
Also change the update line -
$this->db->update('portfolio_items', $data['order']);
UPDATE:
Couple of changes. Change the update to this-
$this->db->update('portfolio_items', array('order' => $data['order']));
The code is working now if I set $config['csrf_protection'] = FALSE in config.php. But you should set it to TRUE. I don't know how to make that work. All I know is that you have to send csrf_token with your ajax call. You should create a separate question for that. This question title means something entirely different.

Passing variable to Ajax.ActionLink OnSuccess, OnComplete, OnFailure, etc

I'd like to put a div on my master page that I can update from anywhere in my site with updates. i.e. "recorded updated", "there was an error", etc.
I'm going to style the div differently depending on the type of update. "fail", "success", "info". Basic stuff so far.
So I have several ActionLinks throughout the site and they display their content fine in the updateTarget and I can even have them run fine when I pass OnComplete, OnBegin, etc. functions to them. However, I'd like to be able to send a parameter to that OnBegin function.
Example:
.OnBegin="someFunction('fail');"
Any ideas on how to accomplish what I'm doing here?
<% string message = "failed"; %>
<%=Ajax.ActionLink("TestController","TestAction",
new AjaxOptions{OnBegin="myFunction('" + message + "')"}) %>
You can create an anonymous function, and next call your function with your paramas
<%: Ajax.ActionLink("Text", "ActionName", new { id = item.id }, new AjaxOptions { HttpMethod = "Post", OnComplete = "function(){myFunction(" + item.id.ToString() + ");}" })%>
The script:
<script type="text/javascript">
function onDelete(id) {
alert("hello "+id);
}
</script>

Resources