Adding new data to a state in react.js - reactjs

I working on a project where I am using react.js with laravel where I am using react for adding new comment in a post for that I am trying to to react's state but I want to add new data retrieved from server in my state without deleting or erasing old data present from that state. How can I achieve that method, is there any dependency for that, or any example or tutorial for that? Even If I am going with wrong approach then also please tell me what approach will be perfect.
The sample code for my problem -
I've an ajax function to get data initially.
getData: function() {
$.ajax({
url: '/get-data',
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data
});
}.bind(this)
});
}
and now after triggering new event I want to update my state without loosing my old data.
Thank you.

You should use the react immutability helpers
This allows you to update nested parts of the state without having to reset the whole thing. If you are just manipulating the root level properties of the state (e.g. this.state.blah or this.state.foo) then this.setState({ foo: 'a value'}); will leave the other root elements as they were.
If you do then want to completely reset the state you can use replaceState({});
setState
replaceState

If data in your code is a valid JSON object, you can use something like:
object-assign
lodash.merge
to merge your data before setting it to state.data. An example using lodash.merge:
import merge from 'lodash.merge'
// some of your code
// inside your getData function in your components
$.ajax({
success: function (data) {
this.setState({
data: merge({}, this.state.data, data)
});
}.bind(this)
});
Whilst it may be not a good practice to be doing this sort of wholesome replacement, it should address your issue. What might be a better approach is not really a scope of this answer :D

Related

How can I cause a state to be refreshed with ui-router?

I am on the state:
home.subjects.subject.exams.exam.tests.test
I delete a test and retrieved a new list of tests without the test I deleted in response.data.tests.
So now I want to go to this state:
this.$state.go('home.subjects.subject.exams.exam.tests', {
examId: this.exs.exam.examId,
subjectId: this.sus.subject.id,
tests: response.data.tests
});
But the router is thinking I already got all the resolves for the state so it does not try to repopulate the new list of tests.
Can someone tell me if it's possible to go to a state like this but have the resolves work.
Hope this makes sense.
There is an additional param that you can pass to go:
this.$state.go('home.subjects.subject.exams.exam.tests', {
examId: this.exs.exam.examId,
subjectId: this.sus.subject.id,
tests: response.data.tests
}, {
reload: true
});
See http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state for more details

React parent renders with initial state before successful ajax call sets state

I'm using React with webpack and babel for compiling jsx. I have a parent component that looks like this:
const Menu = module.exports = React.createClass({
loadUser() {
let xhr = new XMLHttpRequest();
xhr.open('GET', this.state.url, true);
xhr.onload = function() {
let data = JSON.parse(xhr.responseText);
this.setState({
user: data
});
}.bind(this);
xhr.send();
},
componentDidMount() {
this.loadUser();
},
getInitialState() {
return {
url: this.props.url,
user: []
};
},
render: function() {
return (
<div className="menu">
<Nav user={this.state.user} />
...
</div>
);
}
});
As you can see, I attempt to use this.setState(...) to set this.state.user to the data received from the XMLHttpRequest. However, when I try to access it in the child, Nav, by simply calling, console.log(this.props.user), only an empty array, i.e. [], is printed.
What am I doing wrong here? I've read the React docs and I'm stumped. In the following tutorial, data is fetched from the server and transferred to the child component in a manner similar to what I've done (above). Any help would be greatly appreciated. If I need to supply any additional code or context, let me know. Thanks.
getInitialState is used at the first renderso it's normal it's complete before your ajax call since the ajax call is performed in ComponentDidMount which is triggered just after the first rendering.
Before the ajax call is empty your state.user will be empty, then when the data are received it should update your view with the new data.
In my opinion you're not doing anything wrong it depends on what you want to do.
For example you could put a message in getinitialstate like mgs:"Please wait data are fetching" and remove this msg when your data arrive.
Otherwise if you absolutely need your data to be ready before rendering your component you can use that : https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html Read carefully it may not fit your use.
Talking for myself I would put a loading msg in getinitialstate and proceed the way you do.

Sencha Touch loading from store after login - race issue?

I intend to load data from server only after the user is Authenticated.
For simplicity, let us assume that the user is already authenticated.
I put the load data function call (that loads data from a store) in the Main.js initialize function as you can see below.
However, the getStore('storeId').load() function is async, which makes me worried in case the data store finished loading only after the Main view finished loading which might make the view load without the data (fix me if I am wrong, maybe sencha can deal with this, the view has reference to the storeId).
What is the best practice to target such issues?
Trivial solution: calling the store load synchronously, but does it make any difference? and just in case, how to do it? I tried to add synchronous variable set to true but doesn't work.
app.js
launch: function() {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
Ext.Ajax.request({
url: MyApp.app.baseUrl + 'session/mobileCheckAuth',
method: "POST",
useDefaultXhrHeader: false,
withCredentials: true,
success: function(response, opts) {
if (response && response.status === 200) {
Ext.Viewport.add(Ext.create('MyApp.view.Main'));
} else {
Ext.Viewport.add(Ext.create('MyApp.view.LoginPanel'));
}
},
failure: function(response, opts) {
alert('Unexpected failure detected');
Ext.Viewport.add(Ext.create('MyApp.view.LoginPanel'));
}
});
},
Main.js
Ext.define('MyApp.view.Main', {
...
initialize: function() {
console.log('main.initialize');
this.callParent(arguments);
// Load app data
MyApp.utils.Functions.loadData();
}
There are two ways to solve this :
If your view consists of a grid, there will not be a race condition. If the grid renders before the store has finished loading, the grid will update itself with the new data that is added. For this to work, you have to declare the store as 'store: "mystore";' .
If you have a complex view that is not directly bound to the store, you have to load the store first, and in the on 'load' event of the store you initialize the rendering of the view.
store.load({callback: function(){
Ext.Viewport.add(Ext.create('MyApp.view.Main'}
});

Backbone.js - Liking a post, code refactoring

Right now I'm using this code to Like a post. I'm Using jQuery methods to change the Like to Unlike and to change the Like count
View
Streaming.Views.StreamsIndex = Backbone.View.extend({
events: {
'click .like-icon': 'post_liked',
},
initialize: function(){
this.model = new Streaming.Models.StreamsIndex();
this.model.bind('post_likeSuccess', this.post_likeSuccess);
},
post_liked: function(event) {
event.preventDefault();
event.stopPropagation();
current_target = $(event.currentTarget);
liked_id = current_target.attr("id");
href = current_target.attr('href');
this.model.like(href, liked_id); // calls model to send API call for Like
},
post_likeSuccess: function(data, liked_id) {
$("#" + liked_id).attr({
"href": data.unlike,
"title": "Unlike",
"rel": "Unlike",
"class": "likehead-ico_active" // changing like icon
});
//changing like count
$("#"+ liked_id+"_count").text(parseInt($("#"+ liked_id+"_count").text()) + 1);
}
});
Model:
Streaming.Models.StreamsIndex = Backbone.Model.extend({
like: function(href, liked_id) {
var self = this;
$.ajax({
url: href,
type: "POST",
dataType: "json",
async: false,
success: function (data) {
self.trigger('post_likeSuccess', data, liked_id);
},
error: function (data) {
self.trigger('post_likeFail', data, liked_id);
alert("This action was not performed");
}
});
}
});
Is there a better way I can do this?
After liking a post can I change the Like text to unLike, Change the like count in a better way without using jquery?
There are several issues with this code. I'll try address them one by one.
Looks like your Streaming.Views.StreamsIndex has several posts in it. It should be broken down into a component views, that are rendered through a collection, so that each model in the collection is bound to a view. You could, maybe call it Streaming.Views.StreamPost
Your initialize method would have:
this.collection = this.model.posts(); // Or something to this effect
Your render method would have:
// addPost is a function
// that takes 'post' as a parameter
// build the corresponding view object
// and appends it to the posts container
this.collection.each(this.addPost, this)
// example of how addPost looks
var view = new Streaming.Views.StreamPost({model: post});
this.$('#posts-container').append(view.render().el);
The event listener 'click .like-icon': 'post_liked' should on the new component view Streaming.Views.StreamsIndex, instantiated in the addPost above. With this, you don't have to use the ugly current_target = $(event.currentTarget) hack. You always do this.model.get('id') to get the id of the post. The thumb rule he is to not use jQuery or any other form of raw DOM manipulation when using Backbone. That is what views & templates are for! Adjust your template by putting a little logic (as little as possible) to show something if post is liked, and show something else if post is not liked yet. The job of deciding whether a post is liked or not, is to be done by the Post model. I usually write wrapper methods in views that call relevant methods on the model.
Instead of using custom events like post_likeSuccess, update the state of your model and re-render your view. If you updated your templates like I mentioned above, then re-render would take care of all the DOM manipulation you are doing.

CakePHP and AJAX to update Database without page refresh

I'm working with CakePHP 1.3.7 and I'm trying to do the following:
On a given page, the user can click a link (or image, or button, doesn't matter) that passes a parameter which is saved into a database. BUT, all this, without refreshing the page.
I've been doing some research and I believe I need to use AJAX as well to acomplish this. However, I can't find the a good example/explanation on how to do it.
I think that the idea is to create the link using AJAX, which calls the controller/action that would receive the variable as a parameter and performs the operation to save it in its corresponding field/table of the DB.
Does anyone have a small example of what I want to do? Or maybe point me to some tutorial that explains it... Thanks so much in advance!
EDIT
Well, thank you guys for your replies. THey're not working directly, but I think I'm getting closer to what I want. Here's what i'm doing now:
I have this code in my view:
<div id="prev"><a>click me</a></div>
<div id="message_board"> </div>
I call this JS file:
$(document).ready(function () {
$("#prev").click(function(event) {
$.ajax({data:{name:"John",id:"100"}, dataType:"html", success:function (data, textStatus) {$("#message_board").html(data);}, type:"post", url:"\/galleries\/add"});
return false;
});
});
And my add action in my galleries controller looks like:
function add() {
$this->autoRender = false;
if($this->RequestHandler->isAjax()) {
echo "<h2>Hello</h2>";
print_r($this->data);
$this->layout = 'ajax';
if(!empty($this->data)) {
$fields = array('phone' => 8, 'modified' => false);
$this->User->id = 6;
$this->User->save($fields, false, array('phone'));
}
}
}
When clicking on the '#prev' element, I get a response from the add action, I know because the text 'Hello' is printed inside #message_board. And it does this without refreshing the page, which is why I need. My problem is that I can't make the $.ajax() function to send any data, when it gets to the controller the $this->data is empty, so it never goes inside the if that saves the info to the database (right now it's saving just an easy thing, but I will want it to save the data that comes from the view).
Can anyone see what am I doing wrong? How can I send the data to the controller?
CakePHP does not matter, most of the code you would need for this would be at clientside. Implementing AJAX by yourself is a pain in the $, so you really want to use a library; currently the most popular is probably jQuery. There's a bunch of examples on their AJAX page: http://api.jquery.com/jQuery.ajax/
So, assuming you have something like this in the document:
<form id="s">
<input id="q"/>
<input type="submit" href="Search!"/>
</form>
<div id="r"/>
you can put this in the JavaScript:
$('#s').submit(function(evt) {
evt.preventDefault();
$.ajax({
url: 'foo.php',
data: {
query: $('#q').val()
},
success: function(data) {
$('#r').html(data);
}
});
return false;
});
Then your foo.php only needs to return the fragment HTML that would go into the div#r.
EDIT: I forgot to stop the submit :( Thanks to #Leo for the correction.
EDIT: I can see what your confusion is about. You will not get a data. I haven't worked with CakePHP, but I assume $this->data is what you'd get from $_REQUEST['data']? You don't get that on the server. data is a hash of what is getting submitted; you will directly get the $_REQUEST['name'] and $_REQUEST['id'] (which, I assume, translate into CakePHP as $this->name and $this->id).
You need to add
$('#s').submit(function(evt) {
evt.preventDefault();
To prevent a page refresh, as in Amadans answer just refer to your controller/ action in the url variable
$('#s').submit(function(evt) {
$.ajax({
url: '/patients/search/',
data: {
query: $('#q').val()
},
success: function(data) {
$('#r').html(data);
}
In the patients/add controller action make sure you return a valid result ( in json is good )

Resources