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

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.

Related

React Cannot read property '__reactInternalInstance$2f71vks24hx' of null [duplicate]

Here is the problematic component in question.
const UserList = React.createClass({
render: function(){
let theList;
if(this.props.data){
theList=this.props.data.map(function(user, pos){
return (
<div className="row user">
<div className="col-xs-1">{pos}</div>
<div className="col-xs-5">{user.username}</div>
<div className="col-xs-3">{user.recent}</div>
<div className="col-xs-3">{user.alltime}</div>
</div>
);
}, this);
} else {
theList = <div>I don't know anymore</div>;
}
console.log(theList);
return (
theList
);
}
});
Whenever I attempt to return {theList}, I receive a Cannot read property '__reactInternalInstance$mincana79xce0t6kk1s5g66r' of null error. However, if I replace {theList} with static html, console.log prints out the correct array of objects that i want. As per the answers, I have tried to return both {theList} and theList but that didn't help.
In both cases, console.log first prints out [] which I assume is because componentDidMount contains my ajax call to get json from the server and has not fired yet before the first render(). I have tried to check against
this.props.data being null but it does not help.
Here is the parent component if it helps:
const Leaderboard = React.createClass({
getInitialState: function(){
return ({data: [], mode: 0});
},
componentDidMount: function(){
$.ajax({
url: 'https://someurlthatreturnsjson',
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error('https://someurlthatreturnsjson', status, err.toString());
}.bind(this)
});
},
render: function(){
return (
<div className="leaderboard">
<div className="row titleBar">
<img src="http://someimage.jpg"></img>Leaderboard
</div>
<HeaderBar />
<UserList data={this.state.data}/>
</div>
);
}
});
Ah OK, there were some interesting problems in here, but you were so close. The big one, with react you must always return a single top-level element (e.g. a div). So, your variable theList was actually an array of divs. You can't return that directly. But you can return it if it's wrapped in a single parent div.
const mockData = [
{
username: 'bob',
recent: 'seven',
alltime: 123,
},
{
username: 'sally mae',
recent: 'seven',
alltime: 133999,
},
];
var $ = {
ajax(opt) {
setTimeout(() => {
opt.success(mockData);
}, 200);
}
}
const UserList = React.createClass({
render: function(){
let theList;
if (this.props.data && this.props.data.length) {
theList = this.props.data.map(function(user, pos){
return (
<div key={user.username} className="row user">
<div className="col">{pos}</div>
<div className="col">{user.username}</div>
<div className="col">{user.recent}</div>
<div className="col">{user.alltime}</div>
</div>
);
});
} else {
theList = <div>There is NO data</div>;
}
return <div>{theList}</div>;
}
});
const Leaderboard = React.createClass({
getInitialState: function(){
return ({data: [], mode: 0});
},
componentDidMount: function(){
$.ajax({
url: 'https://someurlthatreturnsjson',
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error('https://someurlthatreturnsjson', status, err.toString());
}.bind(this)
});
},
render: function(){
return (
<div className="leaderboard">
<UserList data={this.state.data}/>
</div>
);
}
});
ReactDOM.render(
<Leaderboard/>,
document.getElementById('container')
);
.col {
width: 200px;
float: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
To explain the fiddle a little bit. Don't worry about the weird looking var $ stuff, I'm just stubbing out jQuery's ajax method so I can return some fake data after 200ms.
Also, for me jsfiddle gives me a 'bad config' message when I run it, but I close the message and the result is there. Don't know what that's about.
return (
{theList}
)
should just be
return theList
because you are not inside JSX at that point. What you're doing there will be interpreted as
return {
theList: theList
}
That's ES6 shorthand properties syntax.
Error can also arise from accessing nested state that doesn't exist:
I lack the reputation to comment, so adding an answer for future assistance -- I ran into this same issue for a different reason. Apparently, the error is triggered from an earlier error throwing off react's internal state, but the error is getting caught somehow. github issue #8091
In my case, I was trying access a property of state that didn't exist after moving the property to redux store:
// original state
state: {
files: [],
user: {},
}
// ... within render function:
<h1> Welcome {this.state.user.username} </h1>
I subsequently moved user to redux store and deleted line from state
// modified state
state: {
files: [],
}
// ... within render function (forgot to modify):
<h1> Welcome {this.state.user.username} </h1>
And this threw the cryptic error. Everything was cleared up by modifying render to call on this.props.user.username.
There is a small problem with the if statement:
if(this.props.data !== []){
should be:
if(this.props.data){
this.props.data is null, if the ajax call returns null. alternatively the code could be more elaborate.
const data = this.props.data;
if(data && data.constructor === Array && data.length > 0) {
Not sure if this is how you want to do it, but it works for me.
edit:
const UserList = React.createClass({
render: function() {
if(this.props.data){
return this.props.data.map(function(user, pos){
return (
<li> className="row user">
<span>{pos}</span>
<span>{user.username}</span>
<span>{user.recent}</span>
<span>{user.alltime}</span>
</li>
);
});
} else {
return <li>I don't know anymore</li>;
}
}
});
I encountered this error when I rendered a stateless component and decided to remove the react-root-element (wrapping div) after rendering it with basic dom manipulation.
Conclusion: be aware of this, better don't do it.

react components not talking to each other

I've built a basic restuarant recommendation app that filters by location using the YELP api. The api was responding to my requests with the response object and everything was appending to my divs perfectly, but I realized that for my project, I needed to make a new layer for the data listing. Here are the relevant portions of my two components as they are now:
display-recs:
var DisplayRecs = React.createClass({
render: function() {
var recsLoop = [];
if (this.props.recommendations) {
for (var i=0; i < this.props.recommendations.length; i++) {
recsLoop.push(<Recommendations item={this.props.recommendations[i]} />)
}
}
console.log(this.props.recommendations);
return (
<div className="DisplayRecs">
{recsLoop}
</div>
);
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
recommendations:
var Recommendations = React.createClass({
render: function() {
<div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
I cannot figure out why the nameList, phoneList, and ratingList will not print onto the dom. When I view the elements tab in my devtools, all i see is an empty displayrecs div. I've tried to just change things by guessing, but it's not been fruitful. Can any of you see an obvious problem with the current code?
Thanks
Your Recommendations react component's render function doesn't have any return statement. Try doing this:
var Recommendations = React.createClass({
render: function() {
return ( <div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>);
}
});
Also add a key to the Recommendations components as #Vikramaditya recommends:
recsLoop.push(<Recommendations key={i} item={this.props.recommendations[i]} />)

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.

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.

React+Reflux: Passing Variables to Data Stores

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.

Resources