Should react component delegate onChange events to parent? - reactjs

I'am using react with Flux architecture. And I have a question about best practices.
We have two component: container and item:
var Contaner = React.createClass({
getInitialState: function () {
return {
items: [ 'hello', 'world' ]
}
},
onClick: function () {
console.log('clicked!');
},
render: function () {
return
<div>
{this.state.items.map(function (item) {
return <Item data={item} onClick={this.onClick} />
})}
</div>;
}
});
var Item = React.createClass({
getInitialState: function () {
render: function () {
return
<div>
<button onClick={this.props.onClick}>{this.props.data}</button>
</div>
}
});
So, the question is: should I delegate onClick to parent component (container) or implement it in item component and why?

Depends on what you need as this in the click handler. Do you need to access container state or member variables or methods in the click handler? If yes, then by all means define the handler as method of container and pass it to the item as a property. If not, then the logical place for the handler is the item itself.
If you need to access both, for example to alter states of both components, then use both: implement two handlers and have the item onClick handler call the one passed from the parent as a property.

Related

reactjs edit child state from parent

I have an app with 3 components.
The first is App.jsx
which calls the TodoList component as follows:
<TodoList items={this.state.items} loaded={this.state.loaded} />
the TodoList component renders multiple TodoListItem components
module.exports = React.createClass({
render: function(){
return (
<ul>
{this.renderList()}
</ul>
)
},
renderList: function(){
var children = [];
for(var key in this.props.items) {
if(this.props.items[key].text){
var listItem = this.props.items[key];
listItem.key = key;
children.push(
<TodoListItem item={listItem} key={key} onEdit={this.handleItemEdit} />
)
}
}
return children;
},
handleItemEdit: function(component){
console.log(component);
}
});
Then in my TodolistItem component im rendering multiple li elements
module.exports = React.createClass({
getInitialState: function(){
return {
text: this.props.item.text,
done: this.props.item.done
}
},
render: function(){
return (
<li onClick="this.props.onEdit.bind(null,this)">{this.state.text}</li>
)
},
});
When i click on the li the function handlItemEdit on the parent element function is fired, my question is how i can change the text value of the child element in it's parent's handleItemEdit function?
What im trying to do is when you click on a li open a bootstrap modal with an input field, change its text,save and pass the new props to TodoListItem
In TodoList component you should write getInitialState and save items from props to state, then in render pass items from state (not from props like you did) to TodoListItem component:
for(var key in this.state.items) {
if(this.state.items[key].text){
var listItem = this.state.items[key];
listItem.key = key;
children.push(
<TodoListItem item={listItem} key={key} onEdit={this.handleItemEdit} />
)
}
}
If you call setState within handleItemEdit method, render() will see the updated state and will be executed.
In this case all you need to do is change state in your handleItemEdit and this will be passed to TodolistItem where you should use props, not state in render, like this:
render: function(){
return (
<li onClick="this.props.onEdit.bind(null,this.props)">{this.props.text}</li>
)
},
Notice you are passing props onEdit, so you have to chane handleItemEdit like this:
handleItemEdit: function(itemProps){
console.log('You clicked: ' + this.props.items[itemProps.key]);
}
I have tested solution of your problem in ECMAScript 6 syntax. It would be very easy to use this solution in your notation too. Basicaly what you have to do is to pass this from parent to child, and then use it when you bind onClick event to parent (so you can have this in event handler function) as secound parameter you pass props of the child, where key prop will point the very cilcked element. Then it is very easy to chane parent's state, this will trigger render on parent, and cascade to render child with the new (changed by user) props.
TodoList.js
class TodoList extends Component {
render (){
return (
<ul>
{this.props.items.map(function(listItem, key) {
return (listItem.text &&
<TodoListItem item={listItem} todoList={this} key={key} onEdit={this.handleItemEdit}/>
);
})}
</ul>
)
};
handleItemEdit(itemProps){
console.log('You clicked: ' + this.props.items[itemProps.key].text);
};
}
export default TodoList;
TodoListItem.js
import React, { Component } from 'react';
class TodoListItem extends Component {
render() {
return (
<li onClick={this.props.onEdit.bind(this.props.todoList, this.props.item)}>{this.props.item.text}</li>
)
}
}
export default TodoListItem;
I have tested it and it prints the very text you have clicked in console. This means that oyu have access to this TodoListItem and you can change state of it in TodoList component.

Access parent context when using this.props.children in React

Given the following, is it possible to access the parent context rather than the containers from a child (non-react component) element?
The example logs container, ideally it would log parent. I would like for Parent to be self contained, not to have it's state managed by its container.
var Container = React.createClass({
getInitialState: function () {
return {
context: 'container'
}
},
render: function () {
return (
<Parent>
<a href="#" onClick={function () {console.log(this.state.context);}.bind(this)}>click me</a>
</Parent>
);
}
});
var Parent= React.createClass({
getInitialState: function () {
return {
context: 'parent'
}
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
If there is another pattern for handling this, please share as well.
Note: To be clear, I understand how the this keyword works and why the above example works as it does. The example is simply meant to illustrate the problem.
You can import some React helpers for that:
var React = require('react')
...
var children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
context: this.state.context
})
})
render() {
return <div>{ children }</div>
}
...
Then your child component will have this.props.context which will be the string 'parent', but this must be a React component, as this needs to refer to the component using the parent prop
var YourComponent = React.createClass({
render() {
return (
<a href="#" onClick={() => console.log(this.props.context)}>
click me
</a>
)
}
})
------
var Parent = require('./Parent')
var YourComponent = require('./YourComponent')
...
render() {
return <Parent><YourComponent /></Parent>
}
I do not know about the first part of your question, but since you commented about dynamically creating components, here's how I do it:
You can set a state variable in the constructor of the class and its parent:
if (typeof this.state == 'undefined') {
this.state = {
componentsToRender: <div></div>
};
}
Then in the parent component, in the componentDidMount() function:
var componentsToRender = [];
if ([conditional]) {
// some logic so you know which component to render
componentsToRender.push(<customChildComponentToRender key={} />);
}
else {
componentsToRender.push(<otherComponentToRender key={} />);
}
this.setState({
componentsToRender: <div>{componentsToRender}</div>
});
Make sure to put a key (lines 4 and 7 of the second code block) or React will scream at you.
In response to your initial question, I would watch this video from the ReactJS Conference 2015 to get more of the heart behind a container. After hearing what the guys at Facebook say (who have radical views on containers!), you might want to rethink the design to make your container more of a data layer.
I would check out THIS article from the react website. I think it might give you some intuition on solving your problem.
As a general rule of thumb, I try and only use this.state to handle internal UI state of a specific component. Everything else is passed via props. If you're needing the full context of a component, I would either pass it as a prop or checkout something like flux or redux which will help you manage state between components.

how do I pass data upwards in reactjs?

Say I have an app with some path app -> taskbar -> button -> modal -> textfield. I want the textfield to be some setting a user inputs and is used elsewhere in the app, maybe app -> differentButton -> differentModal displays this user setting for example
I'm brand new to react, but it seems data can only go downwards through props, right? Is it expected that I store this state externally in a db? I don't mind doing that, but it seems like there should be an easy way to do this that I'm overlooking?
You can store the state in the parent component and pass not only the value, but also the function that modifies the value to the child. Eg:
const App = React.createClass({
getInitialState () {
return {
name: 'Dave'
};
},
render () {
return (
<div>
<MyComponent name={this.state.name} changeName={this.onChangeName} />
</div>
)
},
onChangeName (name) {
this.setState({ name });
}
});
const MyComponent = React.createClass({
propTypes: {
name: React.PropTypes.string,
changeName: React.PropTypes.func.isRequired
},
render () {
return (
<div>
<input value={this.props.name} onChange={this.props.changeName} />
</div>
);
}
});
The canonical way would be to pass a callback function from a component which is higher up in the view hierarchy through props.
That higher ordered component would encapsulate the state that you wish to modify, triggering a re-render of the sub-tree.
In your case, it looks like you would have to use App as the shared parent component for sharing state. So in App, you'd probably have a function such as:
handleTextInput: function(text) {
// handle the text input here (set some state, make an ajax call, etc)
},
And App's render function might look like this:
render: function() {
return (
<TaskBar onTextSubmit={this.handleTextInput} />
);
}
In your TaskBar component, you'd pass the callback down to Button, and so on.
Finally, in your modal component, you'd have a render function like:
render: function() {
return (
<form onSubmit={this.props.onTextSubmit}>
...
</form>
);
}
Of course, this is can quickly get quite clumsy if you have a deeply nested hierarchy, so a better approach would be to use a library for state management such as Redux.

React: Child communication in TodoList example

I'm learning React and modifying one of the tutorials. It's a Todo list, and I've come to a roadblock. I am trying to add the ability to delete items of the list. So I added an "x" to the end of each TodoList. But how can the child modify the parent's state.items? What is the "React" way of solving this?
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText, index) {
return <li key={index + itemText}>{itemText} <a onClick={function() { // modify the parent item variable somehow?; }}>x</a></li>
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.render(<TodoApp />, mountNode);
If you want to modify a parent's state from its child, you will need to create a function in your parent component that modifies the state in the way you want, then pass it to the child as a prop. Then, the child can just call this.props.functionName(args), and it will fire the function you created on the parent.
To follow your list example, let's say we have a List component that looks like this:
var List = React.createClass({
getInitialState : function () {
return ({items : []});
},
removeItem : function (num) {
var items = this.state.items;
items.splice(num, 1);
this.setState({items : items});
},
addItem : function () {
var value = React.findDOMNode(this.refs.itemName).value;
var items = this.state.items;
items.push(value);
this.setState({items : items});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return <Item name={item} key={i} num={i} remove={this.removeItem} />
}.bind(this));
return (
<div>
List:<br/>
{items}
<br/>
<input type='text' ref='itemName'/>
<button onClick={this.addItem}>Add Item</button>
</div>
);
}
});
...and an Item component that looks like this:
var Item = React.createClass({
render : function () {
return (
<div>
{this.props.name}
<button onClick={this.props.remove.bind(null, this.props.num)}>Remove</button>
</div>
);
}
});
All this List does is hold on to the Items and provide an interface for which users can add and remove Items. Adding an Item is easy, we just need a button that, when clicked, takes the value of an <input> and adds it to our List's state. React will see the state transition and re-render the List for us, which will display the new Item.
Unlike adding an Item to the end of a list, the remove function needs to be a bit more specific regarding which Item needs to be removed. For that reason, we need a button on each Item that, when clicked, will remove the item from the list by calling this.props.remove and binding its num prop to the arguments. This will cause the removeItem function on List to fire, which will remove that item from our state. Again, React will see the state transition, and re-render our List for us, with the removed Item omitted.
Here is what that code looks like in action: https://jsfiddle.net/rxnpr9sq/
Hope that helps! Let me know if you have any additional questions.
I think this is what you need :https://facebook.github.io/react/tips/expose-component-functions.html
Michael Parker took the time to write your specific implementation this way.

How do I set state of sibling components easily in React?

I have got the beginnings of a clickable list component that will serve to drive a select element. As you can see from the below, onClick of the ListItem, I'm passing the state of a child element (ListItem in this case) to the parents (SelectableList, and CustomSelect component). This is working fine. However, what I would also like to do is change the state of the sibling components (the other ListItems) so that I can toggle their selected states when one of the ListItems is clicked.
At the moment, I'm simply using document.querySelectorAll('ul.cs-select li) to grab the elements and change the class to selected when it doesn't match the index of the clicked ListItem. This works - to an extent. However, after a few clicks, the state of the component has not been updated by React (only by client side JS), and things start to break down. What I would like to do is change the this.state.isSelected of the sibling list items, and use this state to refresh the SelectableList component. Could anyone offer a better alternative to what I've written below?
var React = require('react');
var SelectBox = require('./select-box');
var ListItem = React.createClass({
getInitialState: function() {
return {
isSelected: false
};
},
toggleSelected: function () {
if (this.state.isSelected == true) {
this.setState({
isSelected: false
})
} else {
this.setState({
isSelected: true
})
}
},
handleClick: function(listItem) {
this.toggleSelected();
this.props.onListItemChange(listItem.props.value);
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('ul.cs-select li'), function (el) {
// below is trying to
// make sure that when a user clicks on a list
// item in the SelectableList, then all the *other*
// list items get class="selected" removed.
// this works for the first time that you move through the
// list clicking the other items, but then, on the second
// pass through, starts to fail, requiring *two clicks* before the
// list item is selected again.
// maybe there's a better more "reactive" method of doing this?
if (el.dataset.index != listItem.props.index && el.classList.contains('selected') ) {
el.classList.remove('selected');
}
});
},
render: function() {
return (
<li ref={"listSel"+this.props.key}
data-value={this.props.value}
data-index={this.props.index}
className={this.state.isSelected == true ? 'selected' : '' }
onClick={this.handleClick.bind(null, this)}>
{this.props.content}
</li>
);
}
});
var SelectableList = React.createClass({
render: function() {
var listItems = this.props.options.map(function(opt, index) {
return <ListItem key={index} index={index}
value={opt.value} content={opt.label}
onListItemChange={this.props.onListItemChange.bind(null, index)} />;
}, this);
return <ul className="cs-select">{ listItems }</ul>;
}
})
var CustomSelect = React.createClass({
getInitialState: function () {
return {
selectedOption: ''
}
},
handleListItemChange: function(listIndex, listItem) {
this.setState({
selectedOption: listItem.props.value
})
},
render: function () {
var options = [{value:"One", label: "One"},{value:"Two", label: "Two"},{value:"Three", label: "Three"}];
return (
<div className="group">
<div className="cs-select">
<SelectableList options={options}
onListItemChange={this.handleListItemChange} />
<SelectBox className="cs-select"
initialValue={this.state.selectedOption}
fieldName="custom-select" options={options}/>
</div>
</div>
)
}
})
module.exports = CustomSelect;
The parent component should pass a callback to the children, and each child would trigger that callback when its state changes. You could actually hold all of the state in the parent, using it as a single point of truth, and pass the "selected" value down to each child as a prop.
In that case, the child could look like this:
var Child = React.createClass({
onToggle: function() {
this.props.onToggle(this.props.id, !this.props.selected);
},
render: function() {
return <button onClick={this.onToggle}>Toggle {this.props.label} - {this.props.selected ? 'Selected!' : ''}!</button>;
}
});
It has no state, it just fires an onToggle callback when clicked. The parent would look like this:
var Parent = React.createClass({
getInitialState: function() {
return {
selections: []
};
},
onChildToggle: function(id, selected) {
var selections = this.state.selections;
selections[id] = selected;
this.setState({
selections: selections
});
},
buildChildren: function(dataItem) {
return <Child
id={dataItem.id}
label={dataItem.label}
selected={this.state.selections[dataItem.id]}
onToggle={this.onChildToggle} />
},
render: function() {
return <div>{this.props.data.map(this.buildChildren)}</div>
}
});
It holds an array of selections in state and when it handles the callback from a child, it uses setState to re-render the children by passing its state down in the selected prop to each child.
You can see a working example of this here:
https://jsfiddle.net/fth25erj/
Another strategy for sibling-sibling communication is to use observer pattern.
The Observer Pattern is a software design pattern in which an object can send messages to multiple other objects.
No sibling or parent-child relationship is required to use this strategy.
Within the context of React, this would mean some components subscribe to receive particular messages and other components publish messages to those subscribers.
Components would typically subscribe in the componentDidMount method and unsubscribe in the componentWillUnmount method.
Here are 4 libraries that implement the Observer Pattern. The differences between them are subtle - EventEmitter is the most popular.
PubSubJS: "a topic-based publish/subscribe library written in JavaScript."
EventEmitter: "Evented JavaScript for the browser." It's actually an implementation of a library that already exists as part of nodejs core, but for the browser.
MicroEvent.js: "event emitter microlibrary - 20lines - for node and browser"
mobx: "Simple, scalable state management."
Taken from: 8 no-Flux strategies for React component communication which also is a great read in general.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}

Resources