React.js state confusion - javascript

I am still trying to understand the state concept in react.js. Can anyone please help with the below jsfiddle? I am trying to filter the records based on the category selected.
var App = React.createClass({
render: function() {
return (
<div>
<Instructions />
<h1>Requests</h1>
</div>
);
}
});

From what I've found with react, communicating changes between components that don't have a parent-child relationship kind of requires the state to be managed in a top-level component that is a parent to both the components that are trying to communicate. In your example, App is your top-level component that contains MySelect and DisplayRecords as children. If you want the status of MySelect to affect the rows shown in DisplayRecords, you'll have to manage that in the state of App.
In the example, I moved the select box' selection to the state of App, and passed props to the different components accordingly. I tried my best to explain the notable changes with comments, but if you have questions about any changes, definitely leave a comment!
var DisplayRecords = React.createClass({
render: function(){
var _this = this; // avoid conflicting this keyword
return (
<div>
<table><tbody> // include tbody to avoid errors (weird react thing)
{_this.props.records.map(function(record){ // loop through each record
// if all records is selected, or the record status matches the selection
if(_this.props.filter=="All Requests" || record.status == _this.props.filter){
// return the record as a table row
return (
<tr key={record.id} >
<td>{record.title}</td>
<td>{record.status}</td>
<td>{record.updated_at}</td>
<td>{record.created_at}</td>
<td>Delete</td>
</tr>
)
}
})}
</tbody></table>
</div>
)
}
});
var MySelect = React.createClass({
callParentFunction: function(e) {
// call parent's getFilter function with the selected option's text as argument
this.props.changeHandler(e.target.options[e.target.selectedIndex].text);
},
render: function() {
// note removed specified value of select box
return (
React.createElement("select", { onChange: this.callParentFunction},
React.createElement("option", { value: 1 }, "All Requests"),
React.createElement("option", { value: 2 }, "Approved"),
React.createElement("option", { value: 3 }, "Denied"),
React.createElement("option", { value: 4 }, "Pending")
)
)
}
});
var App = React.createClass({
getInitialState: function(){
// set initial selection
return {
selected: "All Requests"
}
},
getFilter:function(newFilter){
// set new selection on change of select box
this.setState({selected: newFilter})
},
render: function() {
// pass selected state to both MySelect and DisplayRecords
// pass getFilter to MySelect so it can be called onChange
return (
<div>
<MySelect selection={this.state.selected} changeHandler={this.getFilter} />
<h1>Requests</h1>
<DisplayRecords records={this.props.data} filter={this.state.selected} />
</div>
);
}
});
React.render(<App data={requests}/>, document.getElementById('container'));

Related

How to append to dom in React?

Here's a js fiddle showing the question in action.
In the render function of a component, I render a div with a class .blah. In the componentDidMount function of the same component, I was expecting to be able to select the class .blah and append to it like this (since the component had mounted)
$('.blah').append("<h2>Appended to Blah</h2>");
However, the appended content does not show up. I also tried (shown also in the fiddle) to append in the same way but from a parent component into a subcomponent, with the same result, and also from the subcomponent into the space of the parent component with the same result. My logic for attempting the latter was that one could be more sure that the dom element had been rendered.
At the same time, I was able (in the componentDidMount function) to getDOMNode and append to that
var domnode = this.getDOMNode();
$(domnode).append("<h2>Yeah!</h2>")
yet reasons to do with CSS styling I wished to be able to append to a div with a class that I know. Also, since according to the docs getDOMNode is deprecated, and it's not possible to use the replacement to getDOMNode to do the same thing
var reactfindDomNode = React.findDOMNode();
$(reactfindDomNode).append("<h2>doesn't work :(</h2>");
I don't think getDOMNode or findDOMNode is the correct way to do what I'm trying to do.
Question: Is it possible to append to a specific id or class in React? What approach should I use to accomplish what I'm trying to do (getDOMNode even though it's deprecated?)
var Hello = React.createClass({
componentDidMount: function(){
$('.blah').append("<h2>Appended to Blah</h2>");
$('.pokey').append("<h2>Can I append into sub component?</h2>");
var domnode = this.getDOMNode();
$(domnode).append("<h2>appended to domnode but it's actually deprecated so what do I use instead?</h2>")
var reactfindDomNode = React.findDOMNode();
$(reactfindDomNode).append("<h2>can't append to reactfindDomNode</h2>");
},
render: function() {
return (
<div class='blah'>Hi, why is the h2 not being appended here?
<SubComponent/>
</div>
)
}
});
var SubComponent = React.createClass({
componentDidMount: function(){
$('.blah').append("<h2>append to div in parent?</h2>");
},
render: function(){
return(
<div class='pokey'> Hi from Pokey, the h2 from Parent component is not appended here either?
</div>
)
}
})
React.render(<Hello name="World" />, document.getElementById('container'));
In JSX, you have to use className, not class. The console should show a warning about this.
Fixed example: https://jsfiddle.net/69z2wepo/9974/
You are using React.findDOMNode incorrectly. You have to pass a React component to it, e.g.
var node = React.findDOMNode(this);
would return the DOM node of the component itself.
However, as already mentioned, you really should avoid mutating the DOM outside React. The whole point is to describe the UI once based on the state and the props of the component. Then change the state or props to rerender the component.
Avoid using jQuery inside react, as it becomes a bit of an antipattern. I do use it a bit myself, but only for lookups/reads that are too complicated or near impossible with just react components.
Anyways, to solve your problem, can just leverage a state object:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://fb.me/react-0.13.3.js"></script>
</head>
<body>
<div id='container'></div>
<script>
'use strict';
var Hello = React.createClass({
displayName: 'Hello',
componentDidMount: function componentDidMount() {
this.setState({
blah: ['Append to blah'],
pokey: ['pokey from parent']
});
},
getInitialState: function () {
return {
blah: [],
pokey: []
};
},
appendBlah: function appendBlah(blah) {
var blahs = this.state.blah;
blahs.push(blah);
this.setState({ blah: blahs });
},
render: function render() {
var blahs = this.state.blah.map(function (b) {
return '<h2>' + b + '</h2>';
}).join('');
return React.createElement(
'div',
{ 'class': 'blah' },
{ blahs: blahs },
React.createElement(SubComponent, { pokeys: this.state.pokey, parent: this })
);
}
});
var SubComponent = React.createClass({
displayName: 'SubComponent',
componentDidMount: function componentDidMount() {
this.props.parent.appendBlah('append to div in parent?');
},
render: function render() {
var pokeys = this.props.pokeys.map(function (p) {
return '<h2>' + p + '</h2>';
}).join('');
return React.createElement(
'div',
{ 'class': 'pokey' },
{ pokeys: pokeys }
);
}
});
React.render(React.createElement(Hello, { name: 'World' }), document.getElementById('container'));
</script>
</body>
</html>
Sorry for JSX conversion, but was just easier for me to test without setting up grunt :).
Anyways, what i'm doing is leveraging the state property. When you call setState, render() is invoked again. I then leverage props to pass data down to the sub component.
Here's a version of your JSFiddle with the fewest changes I could make: JSFiddle
agmcleod's advice is right -- avoid JQuery. I would add, avoid JQuery thinking, which took me a while to figure out. In React, the render method should render what you want to see based on the state of the component. Don't manipulate the DOM after the fact, manipulate the state. When you change the state, the component will be re-rendered and you'll see the change.
Set the initial state (we haven't appended anything).
getInitialState: function () {
return {
appended: false
};
},
Change the state (we want to append)
componentDidMount: function () {
this.setState({
appended: true
});
// ...
}
Now the render function can show the extra text or not based on the state:
render: function () {
if (this.state.appended) {
appendedH2 = <h2>Appended to Blah</h2>;
} else {
appendedH2 = "";
}
return (
<div class='blah'>Hi, why isn't the h2 being appended here? {appendedH2}
<SubComponent appended={true}/> </div>
)
}

Displaying data from table in modals using reactjs, localStorage and state (traversing)

Ive been trying to get data from a table to a modal in ReactJS, trying as in trying to get it to work with minimal effort. I think I understand how Components work ok. But when displaying data in the modal I want the component to first go through the list and remove 'selected' class on the rest of the rows and then display the selected row. Right now my modal only displays the last row, regardless of where I click.
var BoatRow = React.createClass({displayName: 'BoatRow',
handleClick: function(event){
this.setState({className:'selected'});
},
getInitialState: function(){
return (
{
className:'!selected',
}
)
},
render:function(){
var listed = this.state.className ? 'selected' :
localStorage.setItem('boat', JSON.stringify({
name:this.props.boat.Name,
//some data
}));
return (
React.DOM.tr({className:this.state.className},
React.DOM.td(null, this.props.boat.Name),
//rest of table row data
React.DOM.button({type: "button", 'data-toggle': "modal", 'data-target': "#modalContent", onClick:this.handleClick
}, "Select" )
)
)
)
}
});
Im first going through the JSON object and pushing it to an array.
var AllBoatList = React.createClass({displayName: 'AllBoatList',
render: function(){
var rows = [];
var lastAvailable = null;
this.props.boats.forEach(function(boat, i){
if(boat.Availability !== 0){
rows.push(BoatRow({boat:boat, key:boat.id}));
}
});
return(
React.DOM.table({id:"boat-table"},
//table head
)
),
React.DOM.tbody(null, rows)
)
);
}
});
var data = [{
"Name": "Boat Name",
"id": "1"
}, //rest of Json data
}
]
React.render(AllBoatList({boats:data}),
document.getElementById('all-boats')
);
And this is where the modal data gets displayed.
var Boats = require(['./assets/src/scripts/boats']);
var BoatModal = React.createClass({displayName: 'BoatModal',
getInitialState: function(){
return {
value: JSON.parse(localStorage.getItem('boat'))
}
console.log(this.state.value);
},
render:function(){
return (
React.DOM.div({className: "DisplayContainer"},
React.DOM.p(null,
this.state.value
)
)
)
}
});
React.render(BoatModal({}), document.getElementById('modal-body')
);
The components work fine displaying the table and selecting the correct row, changing classes. Im only having problems where the data displayed on the modal is not my selection, but always the last row. How can I fix this?
I've got it working finally. I dont know if I fully understand this yet, but I had to move all onClick events to the event handler. Modal works with correct data, with this change
var BoatRow = React.createClass({displayName: 'BoatRow',
handleClick: function(event){
this.setState({className:'selected'});
//this is where storage should happen
localStorage.setItem('boat', JSON.stringify({
"Name":this.props.boat.Name
//adding this line fixed it
})).bind(this.props.boat.Name);
console.log("storage written");
},
getInitialState: function(){
return (
{
className:'!selected'
}
)
},
render:function(){
var listed = this.state.className ? '!selected' : 'selected'
...

Using React LinkedStateMixin for text input doesn't re-render as expected

Here's the render function for one of my react components:
render: function() {
var valueLink = this.linkState.value;
var handleBlur = function(e) {
valueLink.requestChange(e.target.value);
};
return (
<input
type="text"
defaultValue={valueLink}
onBlur={handleBlur}
/>
);
}
I'm using backbone-react. After setting an attribute on the model, this component calls its render function. The backbone model gets set properly, but the input field doesn't render the value that was set on the model.
Basically when the render function gets called after the valueLink.value changes, the input field doesn't reflect this change.
I've tried using value instead of defaultValue but that makes it a controlled component.
I also don't want to use valueLink as that sets state for every key press whereas I only what to trigger that for onBlur.
Any ideas? (Please let me know if you need more info.)
From React docs
LinkedStateMixin adds a method to your React component called
linkState(). linkState() returns a ReactLink object which contains
the current value of the React state and a callback to change it.
In your example, instead of this.linkState.value, pass a state variable to linkState. Ex this.linkState('message')
var Component = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {message: 'Hello!'};
},
render: function () {
var valueLink = this.linkState('message');
var handleBlur = function(e) {
valueLink.requestChange(e.target.value);
};
return (
<div>
<input
type="text"
defaultValue={valueLink.value}
onBlur={handleBlur}
/>
<br />
{this.state.message}
</div>
);
}
});
React.render(<Component />, document.body);
http://jsfiddle.net/kirana/ne3qamq7/12/

Dynamically add new component

I've been playing with ReactJS and have two components like:
/** #jsx React.DOM */
var MyDiv = React.createClass({
render: function () {
return (
<div>
Hello World
</div>
)
}
});
var MyClickableDiv = React.createClass({
addDiv: function () {
alert("Hello World"); //For testing
}
render: function () {
return (
<div>
<MyDiv />
<a href='#' onClick={this.addDiv}>Add new MyDiv</a>
</div>
)
}
});
What I'm trying to do is: upon clicking "Add new MyDiv", I want to add a new MyDiv component under the first MyDiv that I already have.
I think a more react-minded approach would be to make the state of whether the second div is present or not explicit and put all the rendering inside the render function instead of splitting it, half in render and the other half in addDiv.
var MyClickableDiv = React.createClass({
getInitialState: function(){
return {showDiv: false};
},
addDiv: function () {
this.setState({showDiv: true});
},
render: function () {
return (
<div>
<MyDiv />
{this.state.showDiv ? <MyDiv /> : null}
<a href='#' onClick={this.addDiv}>Add new MyDiv</a>
</div>
)
}
});
If you want addDiv to keep adding divs when you click it again, change the state so that instead of a boolean flag you keep a list of the extra divs that should be rendered.

ReactJS Cortex Object updated by render not invoked?

I am trying to implement Emberjs's Todo app as a practice exercise for Cortex by mquan on github. I am currently implementing the "All", "Active", "Completed" filter where clicking an anchor will result in the anchor being highlighted (class added).
I created the following:
var filtercortex = new cortex([
{title:'all', selected:true, key:1},
{title:'completed', selected:false, key:2},
{title:'active', selected:false, key:3}
]);
With the following render function (in the parent):
render: function() {
var filters = filterCortex.map(function(filter) {
return (
<li>
<FilterAnchor cortex={filterCortex} filter={filter} />
</li>
)
});
...
return ...
<ul id='filters'>
{filters}
</ul>
And FilterAnchor's definition:
var FilterAnchor = React.createClass({
handleClick: function() {
var that = this;
this.props.cortex.forEach(function(filter) {
if (filter.key.getValue() == that.props.filter.key.getValue()) {
console.log(filter.title.getValue(), true);
filter.selected.set(true);
} else {
console.log(filter.title.getValue(), false);
filter.selected.set(false);
}
});
return false;
},
render: function() {
var className = (this.props.filter.selected.getValue()) ? 'selected' : '';
return (
<a className={className} href="#" onClick={this.handleClick}>
{this.props.filter.title.getValue()}
</a>
)
}
});
right now, I do not see the class 'selected' being applied to the anchor links when I am clicking.
However, upon investigation I notice this:
Clicking "All":
All true
Completed false
Active false
Clicking "Completed":
All true
Completed false
Active false
So I am certain that the objects inside filtercortex has been updated properly (you can open up firebug to check). However, FilterAnchor.render is not being triggered.
Is this a bug?
Source code: https://github.com/vicngtor/ReactTodo/blob/cortex/script.jsx
The sample at the top of the Cortex readme has this at the bottom:
orderCortex.on("update", function(updatedOrder) {
orderComponent.setProps({order: updatedOrder});
});
Is there an equivalent section in your code? If not, then the problem is that the update event for the cortex data store isn't set to trigger an update of the view, which is made through a call to setProps on the top level React component in this example.

Categories