REACT - Retrieve state of the parent and pass to child - javascript

I am learning React and have got to a point where I am a bit stuck. Here is the Parent component of my add with the attribute im using to populate its state with a JSON object:
var App = React.createClass({
getInitialState: function() {
return {
clothing:{},
order:{}
}
},
loadClothingItems: function(){
this.setState({clothing:clothing});
},
});
In my render function for App I am passing down the apps props for loadClothingItems like so:
<OtherOptions loadClothingItems={this.loadClothingItems} />
Here is the child component (OtherOptions). There is an a-tag which onClick populates the App state. I then need to get the state that has been added to App back down to OtherOptions in order to generate all my object key properties into li items within the ul:
var OtherOptions = React.createClass({
render:function(){
return (
<div className="other-options-inner">
<a onClick={this.props.loadClothingItems}>Load Clothing</a>
<ul>
{console.log(this.props.state)}
</ul>
</div>
)
}
});
When I initially load the app I get a console log of "undefined" which is expected as I have not yet populated the App components state but when I click the a-tag it gives me "undefined again".
I was expecting to the new state of App being passed down to OtherOptions here so I could take the state and render out some li items. Maybe i'm not going about it the right way to communicate with the parent or maybe i'm completely off-track but either way I am stuck and the documentation hasn't helped.
Thanks

If you need to pass all of the state to the child component then you can do so via props <OtherOptions items={this.state} loadClothingItems={this.loadClothingItems} />
and access it in the child component via this.props.items
Also, I'm not sure what you're trying to accomplish in loadClothingItems, but clothing doesn't exist anywhere so trying to set the state of clothing to clothing will be undefined.

Related

VueJS XHR inside reusable component

Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.

Passing a value to the parent component

How could a child component pass its value to the parent component? Here is my child component:
Javascript:
new Vue({
el: '#table-list',
data: {
tableList: ['Empty!'],
tableSelected: ""
},
methods: {
getTableList() {
axios
.get('/tables')
.then(tableList => {
this.tableList = tableList.data;
})
.catch(e => console.warn('Failed to fetch table list'));
},
selectTable(table) {
this.tableSelected = table;
}
},
mounted() {
this.getTableList();
}
});
HTML:
<div id="table-list">
<p v-for="table in tableList">
<i class="fa fa-table" aria-hidden="true"></i>
<span class="text-primary" v-on:click="selectTable(table)"> {{ table }} </span>
</p>
</div>
When on click, selectTable is called, I want to show the value in its parent component? i.e I need to pass tableSelected property to the parent component. How could I do this?
You should use vue components, specifically events mechanism for what you want to archive.
Props are for pass data from parent to a child components, and events to send messages from child component to parent.
We have learned that the parent can pass data down to the child using props, but how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Please see this fiddle https://jsfiddle.net/AldoRomo88/sLo1zx5b/
I have changed your selectTable method to emit a custom event
selectTable: function(table) {
this.$emit('item-changed',table);
}
And in your parent component you just need to listen for that event
<div>
{{selectedItem}}
</div>
<table-list #item-changed="newValue => selectedItem = newValue " ></table-list>
Let me know if you need more clarification.
Here is the page that explains how children emit events to listening parents.
Here is the page on managing state.
Remember what you're aiming for, with VUE, is MVVM. You want all your state in a store, where each item of state is stored once, regardless of how many times it's referenced, and how many ways it can be updated.
Your tableSelected is an item of state. You can pass state changes up the chain if you need to, so long as they finish in a store, not in a component or a vue. But you can keep it simple: make tableSelected a property in your store, and declare it directly in the data element of components that need it. If you want to be rigorous, put a changeTableSelected() method on the store.
You need to start worrying about props and events if one component will have many instances, or if a component knows nothing about the page on which it will appear. Until that time, I would prefer using data and the store.

ReactJS - updating components when separate class property is changed

In my project I have an html doc with some react code, for instance:
var Test = React.createClass({
getInitialState: function() {
return {storage: this.props.storage};
},
render: function() {
return (
<h2>
{this.state.storage}
</h2>
);
}
});
In another class, AppMan, I have a property called storageLeft. If I render the Test component like:
<Test storage={AppMan.storageLeft}/>
What is the correct way to go about updating the Test component whenever storageLeft is changed inside the AppMan class? I'm not sure if passing it as a property of the component is the right way to go about it. Initially, all I can think of is doing a setInterval and constantly doing this.setState({storage: AppMan.storageLeft}); or something along those lines. Any better ideas?
Setting state via props is an anti-pattern. You should save storageLeft in the AppMan component's state and then pass that state to the Test component as a prop. A component automatically re-renders itself and its children (if needed) when you change its state.
The docs state that:
To implement interactions, we introduce mutable state to the
component. this.state is private to the component and can be changed
by calling this.setState(). When the state updates, the component
re-renders itself.
This way your components stay in sync when you pass state as a prop.
AppMan.js
<Test storage={this.state.storageLeft} />
Test.js
var Test = React.createClass({
render: function() {
return (
<h2>
{this.props.storage}
</h2>
);
}
});

How to trigger re-render on model change in ReactJS?

I am creating a react component using
React.render(<ReactComponent data="myData">, document.body);
Once the data model changes, I call render again using
React.render(<ReactComponent data="myData">, document.body);
Is this the right/recommended way to update my HTML?
Will this utilize the advantages of the React virtual DOM (i.e. rendering only the elements that have actually changed).
Also, should I be using state or properties when passing in myData?
You should be rendering only one main App component which does AJAX requests etc and uses the data model inside its render function to update sub components.
When creating React components you should always keep the use of state minimal and move it up to the top level component, Instead you should use props to render child components.
This article helped me a lot when i was first getting started with React: https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
so something like:
var App = React.createClass({
render: function(){
return (
<div>
<input type="button" onClick={this.handleClick}/>
<Dropdown items={this.state.countries}/>
</div>
)
},
getInitialState: function(){
return {countries: {}};
},
componentDidMount: function(){
var self = this;
$.getJSON("countries", function(err, countries){
self.setState({countries: countries});
});
},
handleClick: function(){
// every time the user does something,
// all you need to do is to update the state of the App
// which is passed as props to sub components
}
})
React.render(React.createElement(App, {}), document.body);

setState not triggering a re-render when data has been modified

Just started working with React, and it's been going relatively smooth up until this point.
I have a list, that contains X pre-defined items, which means that the list is always rendered with a given amount of rows.
This data is collected from a REST API, prior to the list rendering. The data contains variables relating to the list, as well as an array that contains each item within the list.
I chose the easy route, so I populate every component with a single JSON object named 'data', that contains everything necessary.
Rendering the list looks something like this:
<MyList data={data} />
Then, in the getInitialState of MyList:
dataInt: this.props.data.dataInt || 0,
dataStr: this.props.data.dataStr || '',
rows: this.props.data.rows || []
So I store my array of items (JSON) in the initial state of the list (parent), and when I choose to render the list, I first create all components (children) in an array, like this:
var toRender = [];
for(i = 0; i < this.state.rows.length; i++) {
toRender.push(<ItemRow data={this.state.rows[i]} />);
}
and then I render like this:
return (
<div className="item-container">
<table className="item-table">
{toRender}
</table>
</div>
);
The render-function of MyItem look something like this:
return (
<tr>
<td>{this.state.dataFromItem}</td>
</tr>
);
Now, let's say I want to modify the child data from within the parent, with some new data I just got from the API. Same structure, just the value of one field/column has changed:
i = indexOfItemToBeUpdated;
var newRows = this.state.rows;
newRows[i] = data; // New JSON object with same keys, different values.
this.setState({ rows: newRows }); // Doesn't trigger re-render
this.forceUpdate(); // Doesn't trigger re-render
What am I doing wrong?
At first I thought it was because I was wrapping the render function of MyItem in a , but since it renders perfectly fine on the initial render, I will assume that is an acceptable wrapper.
After some testing, it seems that the parent view is re-rendered, but not the children (which are based on the data that is updated within the parent).
JSfiddle: https://jsfiddle.net/zfenub6f/1/
I think the problem could be the your are only using getInitialState for the Row. You are setting state with the passed in props. If the children get new props, getInitialState won't get called again. I always refer back to https://facebook.github.io/react/docs/component-specs.html to see all the lifecyle events. Try using componentWillReceiveProps(object nextProps) to set the state again. If you don't actually need state in the child, remove the use of state and just use props and it probably will work.
If you're children are using State and state is not updating, what about props? does that render just fine? If the parent changes the state of the children and the children don't reflect that change those child components most likely need a unique key assigned to them which tells React that these items need to be updated when the state is updated on the parent. I ran into a similar issue where props inside the child updated to reflect the parent but the state of the child would not update. Once I added keys, the solution was resolved. For further reading on this check out this blog that initially hinted the problem to me.
http://blog.arkency.com/2014/10/react-dot-js-and-dynamic-children-why-the-keys-are-important/
There is also the official documentation from React that explains this situation.
http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

Categories

Resources