React+Reflux: Passing Variables to Data Stores - reactjs

I'm building a React+Reflux application that, among other things, allows for the creation/deletion/editing of categories. So far I'm able to display all categories and handle creation/deletion of categories within a React component through associated stores and actions. This all works great, updates the database and re-renders the component as expected. My sticking point is when trying to drill down into a specific existing category in order to edit it.
I think I somehow need to pass a category Id to a store which will then pass it along to a php/sql query via an ajax call in order to get/set data specific to that particular category. If I bypass the store altogether and put the ajax call within the component itself I'm able to get it working via a url parameter with React-router (no auto re-render of course) but I haven't been able to figure out how to accomplish this through a store.
In other words, this more or less works:
"ManageCategories" React component that uses CategoryStore to list all categories each wrapped in an anchor tag that passes the category Id along to "ManageCategory" route/component
The "ManageCategory" component uses the category Id param directly in an ajax call within its getInitialState method to display data specific to the category
However, I think below is the more correct Reflux way to do this but I'm not sure how to get it to work:
"ManageCategories" component same as above
"ManageCategory" component that somehow passes its category Id param to the CategoryStore (or maybe a different "IndividualCategoryStore"?) which returns only data specific to that category and handles updates/edits to that category
I was able to get a sort of clunky version of this working by adding a new method ("getCategoryData") to the CategoryStore that is called in the getInitialState method of the "ManageCategory" component and is passed the categoryId param. This results in a flash of all categories (from the CategoryStore's getDefaultData) followed by the correct single category listing (from the component's getInitialState).
I feel fairly comfortable with the concepts behind React+Reflux but at this point I think it's likely I'm misunderstanding something fundamental. Worked on this particular issue for more than a week but none of the examples/tutorials/docs I've found address the specific question of passing a variable to a data store.
Actions:
var Actions = Reflux.createActions([
"createCategory",
"deleteCategory",
"editCategory"
]);
CategoryStore:
var CategoryStore = Reflux.createStore({
listenables: [Actions],
onCreateCategory: function(catName) {
// ajax call to create new category that calls updateCategories on success
},
onDeleteCategory: function(catId) {
// ajax call to delete category that calls updateCategories on success
},
updateCategories: function(){
$.ajax({
url: url + '?action=getAllCategories',
async: false,
dataType: 'json',
success: function(categoryData) {
this.categories = categoryData;
}.bind(this),
error: function(xhr, status, err) {
console.error(url, status, err.toString());
}.bind(this)
});
this.trigger(this.categories);
},
getDefaultData: function() {
$.ajax({
url: url + '?action=getAllCategories',
async: false,
dataType: 'json',
success: function(categoryData) {
this.categories = categoryData;
}.bind(this),
error: function(xhr, status, err) {
console.error(url, status, err.toString());
}.bind(this)
});
return this.categories;
}
});
Category Component:
var Category = React.createClass({
handleDeleteCategory: function() {
Actions.deleteCategory(this.props.id);
},
render: function() {
return (
<li className="category">
<IconButton icon="action-highlight-remove" onClick={this.handleDeleteCategory} />
<h5><a href={"/#/manage-category/" + this.props.id}>{this.props.name} ({this.props.id})</a></h5>
</li>
);
}
});
ManageCategories Component:
var ManageCategories = React.createClass({
mixins: [
Reflux.connect(CategoryStore, "categories")
],
getInitialState: function() {
return {
categories: []
};
},
handleCreateCategory: function() {
// category creation code
},
render: function() {
var categoryNodes = this.state.categories.map(function(category) {
return (
<Category name={category.name} id={category.id} />
)
});
return (
<div className="dev-tools-container">
<h1>Developer Tools</h1>
<div className="categories">
<h3>Categories</h3>
<ul>
{categoryNodes}
</ul>
<h4>Create New Category:</h4>
<form>
<label htmlFor="new-category-name">Category Name</label> <input type="text" id="new-category-name" /><br />
<PaperButton label="Create" primary={true} onClick={this.handleCreateCategory} />
</form>
</div>
</div>
);
}
});
Thanks in advance for any insights or assistance.

After finally posting my question here I think I may have figured out where I was going astray all along. I was thinking in terms of passing the category Id to the store to filter the data therein when all I really need to do is take the full collection of data from the store and consume it selectively within the component.
So once routed to the ManageCategory component with the catId passed along as a url param, all I need to do is filter the data based on the catId.
For example, once in the ManageCategory component I can use lodash to filter and pluck the name value of the current category, as below. No need to edit the collection of data stored in the CategoryStore at all.
var ManageCategory = React.createClass({
mixins: [
Reflux.connect(CategoryStore, "categoryData")
],
getInitialState: function() {
return {
categoryData: []
};
},
render: function() {
var categoryName = _.chain(this.state.categoryData)
.filter({"id": this.props.params.catid})
.pluck("name");
return (
<div className="category-container">
<h1>{categoryName}</h1>
</div>
);
}
});
Feel free to let me know if there's a better way of doing this but for now this is exactly what I needed. Hopefully something from all of this will be helpful to someone else too.

Related

Make AJAX request when the property was changed

I would like to have a component, which get the property from parent component and make an AJAX request, based on this propery. The parent component can change this property and my child component must get another one AJAX request.
Here is my code, but I am not sure it is optimal and even correct:
<News source={this.state.currentSource} />
Component:
var News = React.createClass({
propTypes: {
source: React.PropTypes.string
},
getInitialState: function() {
return {
entities: []
};
},
componentWillReceiveProps(nextProps) {
var url = 'http://localhost:3000/api/sources/' + nextProps.source + '/news';
this.serverRequest = $.get(url, function(result) {
this.setState({
entities: result
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
// ...
}});
module.exports = News;
Does componentWillReceiveProps is a good place for this code?
componentWillReceiveProps will work just fine, one thing I would do differently is if <News/> is getting its props from a parent component then a good pattern to follow is to have the parent component actually handle the api calls and pass down the results as props. This way your News component really only has to deal with rendering content based on its props vs rendering and managing state.
I can only see limited portion of your App so that might not fit your use case but here is a great article on doing that type of differentiation between smart and dumb components.
http://jaketrent.com/post/smart-dumb-components-react/

react js displaying ajax data on function call by passing parameter

I am currently trying to learn react by building a simple app that works with a JSON array by calling a certain API. I would then like to show the results of the array in a list item and when click one of the list-item and then it returns a parameter and call an api and display data in other part of the page.
I have successfully called the API and am showing the correct data in the list-item but I am struggling to figure out how to show the data after the click in another part of the page.
So I currently have this in my page:
<div class="col-lg-3 col-md-3">
<div class="block-job-list" id="JobCardBlock"></div>
</div>
<div class="col-lg-9 col-md-9">
<div class="block-job-list" id="JobDetailBlock"></div>
</div>
<script type="text/jsx">
var JobCard = React.createClass({
// get game info
loadGameData: function() {
document.getElementById("overlay").style.display = "block";
var a_token = window.localStorage.getItem('access_token');
$.ajax({
type: 'GET',
url: this.props.source,
data: {
page: 1,
},
dataType: "json",
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + a_token);
},
success: function(data) {
$('#overlay').hide();
this.setState({
data: data.order_list.data
});
}.bind(this),
error: function(xhr, status, err) {
$('#overlay').hide();
console.error('#GET Error', status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {
data: []
}
},
componentDidMount: function() {
this.loadGameData();
},
render: function() {
return ( < div className = "CurrentGame" >
< JobList data = {
this.state.data
}
/> < /div>
);
}
});
var JobList = React.createClass({
displayData: function(e) {
var a_token = window.localStorage.getItem('access_token');
$.ajax({
type: 'POST',
url: 'http://zipship-beta.herokuapp.com/job_detail',
data: {
order_id: e,
},
dataType: "json",
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + a_token);
},
success: function(data) {
$('#overlay').hide();
this.setState({
data: data.order_list
});
console.log(this.state.data);
}.bind(this),
error: function(xhr, status, err) {
$('#overlay').hide();
console.error('#GET Error', status, err.toString());
}.bind(this)
});
},
render: function() {
if (!this.props.data) {
return null;
}
return (
<ul className="list-group">
{
this.props.data.map(function(jobDetail, i) {
return <li className="list-group-item" key=jobDetail.id} onClick={()=>{this.displayData(jobDetail.id)}}>
</div>
</li>
},this)
}
</ul>
);
}
});
</script>
If you are looking to make actions of job list change the state of job card here are two options you might consider.
The easiest of which is to create a parent react class such as JobInfo which simply acts as a container for both these elements.
render: function() {
<div>
<JobCard/>
<JobList/>
</div>
}
Then JobInfo would maintain state for both of these two objects. In React when you find ways to reduce state its always a good sign. Then the JobInfo object would have some function such as onListItemClick or updateJobCard. Since you are using your ajax request to update the state, it would be move up to this parent class since the state lives here. Then you simply pass this function down (as a prop) to the JobList object and use this for your onClick event on your list items. Now since the state of the parent is changed from the onClick event the state change will propagate down to both the JobList and JobCard info.
The other option you can look into is having an external event handler. These kind of methods are what you find in Flux structures. The idea use you JQuery or some event library to have your JobList object Trigger an event globally, and have your JobCard object subscribed to this event.
The second option I would recommend against. For most simple cases its completely unnecessary and to implement this type of system.
TLDR; Centralize your state and pass state altering functions as props.

ReactJS, I want to retrigger an ajax call in ComponentDidMount

-I have a Component (let's call it StorageComponent) that get a data(JSON) from a restful api (the request is made in componentDidMount)
-StorageComponent then passes the data to a child component and it will do stuff with other components to display the data and interact with user.
-Now there is another hierarchy of components independent of the above.
-This handle some form inputs by users, there is one component for each form input (radio button, checkbox, text, etc). And because every re-render will get rid of any state of the child, I had to use an object literal (call it ModelObject) to store each form input. So whenever a user enter something in a form it will make a call to Modelobject and store it there, the component will also ask for data from ModelObject.
-After The user entered all input he will eventually hit a submit button component in this hierarchy, where it will make a call to ModelObject to do ajax POST to the RestAPI. My problem is here, I would like for ModelComponent to get the data from the RestAPI again, so user will see the updated data. I thought forceUpdate() would work, I thought it would re-trigger rendering and thus componentDidMount in StorageComponent.
So what is the best way to do this. Moreover is there any bad practice mentioned above? Is this enough information?
edit:
the storageComponent hierarchy
var StorageComponent= React.createClass({
getInitialState: function(){
return {
data: []
};
},
componentDidMount: function(){
this.serverRequest = $.get(this.props.source, function(result){
result = JSON.parse(result);
this.setState({
data: result
});
}.bind(this));
},
render: function() {
return (
<div>
<Nav dataList={this.state.data} /> //whole bunch of other child component below this one
</div>
);
}
});
app.storageComponent= React.render(
<HabitModel source = "/api/listing/user"/>,
document.getElementById('myDiv')
);
the ModelObject that I've mentioned:
var formModel = {
newInfo: {
inputBox: "",
frequency: "",
date: "",
days: []
},
addDescription: function(description){
this.newHabitInfo.description = description;
},
addFrequency: function(selection){
this.newHabitInfo.frequency = selection;
},
addDay: function(startDay){
this.newHabitInfo.startDay = startDay;
},
getFrequency: function(){
return this.newHabitInfo.frequency;
},
//this is the function I want the second hierarchy of components to
//use to force the storageComponent to do the re-trigger the ajax
updateHabitListing: function(){
if(this.validate()){
app.habitListing.forceUpdate();
}else{
console.log("form not finish");
}
}

React-Router's Link-To updates the URL but doesn't refresh page

The header of my site has 10 category images (links). Each uses React-Router's Link to route to each category's respective categoryShow.
The link works from categoryIndex, but it no longer works when being clicked form a cagetoryShow. It properly updates the browser when clicking on it, for example it does pushState to /cateories/18 and /categories/2, but the browser doesn't refresh.
Worth noting is the link works from every other Index-type and Show-type page. It just doesn't work from categoryShow in particular. I wonder if successive clicks to the same name, eg Link to="categoryShow", somehow keeps the router from doing a page refresh?. Edit: I tried changing that to Link to= {"/categories/" + this.props.id } and it does the same thing.
Here's the noteworthy component structure. All the data is successfully being passed all the way through updating the URL. It's just that the page does't refresh in one particular case:
-categoryShow
-header (fetches and passes category data to child)
-headerMenu (receives category data, passes on to child)
-headerMenuCard (receives category data, uses the id in the link seen below)
headerMenuCard:
var HeaderMenuCard = React.createClass({
...
return(
<div >
<Link to="categoryShow" params={{id: this.props.id}} ></Link>
</div>
)
})
Here's CategoryShow, which is where the link routes to:
var CategoryShow = React.createClass({
getInitialState: function(){
return{
didFetchData: false,
items: [],
userID: localStorage.getItem('userID'),
headerImage: "../categories.png"
}
},
componentDidMount: function(){
this.fetchData()
},
fetchData: function(){
var data = {
userID: this.state.userID
}
var params = this.props.params.id
$.ajax({
type: "GET",
url: "/categories/" + params,
data: data,
dataType: 'json',
success: function(data){
this.setState({didFetchData: 'true', items: data.items})
}.bind(this),
error: function(data){
alert("error! couldn't fetch category data")
}
})
},
render: function(){
var itemArray = this.state.items.map(function(item){
return <ItemCard name={item.name} key={item.id} id={item.id} photo_url={item.photo_url} description={item.description} userID={localStorage.getItem('userID')} like_status={item.like_status} />
})
return(
<div>
<Header />
<section className="body-wrapper">
{itemArray}
</section>
</div>
)
}
})
You'll receive new parameters in props and thus you only need to run fetchData or any other logic in componentWillReceiveProps or componentWillUpdate.

Pass data from one component to another component without parent child relation

Here are my two components.
Component 1:
var Main = React.createClass({
getInitialState: function(){
return{
data: [],
values:[],
childRecord:[]
};
},
componentDidMount: function(){
$.ajax({
url: "data.json",
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
render: function() {
var that = this,
data = this.state.data;
if(this.state.data.length > 0) {
data = this.state.data[0].values;
data = this.state.data[0].values[0].children;
}
var data = this.state.data;
if(this.state.data.length > 0) {
data = this.state.data[0].values;
}
var nestedData = data.map(function(Record, id) {
return(
<Tile key={id} data={Record} child={data}/>
);
});
return (
<div className="row main-container">
{nestedData}
</div>
);
}
});
*Component 2:*
var Tile=React.createClass({
render:function(){
return(
<div className="row">
<div className="tileForm">
<h3>{this.props.data}</h3>
</div>
</div>
);
}
});
I want to render some part of json in Tile Component. The other components have parent child relation so I can render the data easily, but I dont know how to render the data in Tile component since there is no relation b/w Tile Component and other components. I am using routing to call Tile component and rendering Main component by using DefaultHandler, this is not the complete code.
Any kind of help will be appreciated.
Thanks
Fhm
You want to use the Flux (or something similar) pattern for this use case: https://github.com/facebook/flux - it was designed to solve this exact problem.
You would encapsulate your data (the JSON payload) in a shared Store. Both your components could then read from this store independent of how they are related to each other.
If the data needs to change you would pair this store with an action creator that your components could call to trigger data refresh/mutate, etc.
This also is helpful for when you have a deep nesting of components because you can avoid having to deeply bubble down your data.

Resources