What you are trying to do is sibling communication. There are several options:
- Use context to make properties available in a hierarchy (not recommended).
- Use the observer / pub-sub pattern. There are libraries like EventEmitter or Postal that implement this pattern (recommended).
- Pass shared methods as property (recommended).
- Use redux / reflux. The latter option is feasible only in medium / large or high complexity applications.
The simplest alternative you have is to define methods in the parent component, methods that will be shared in the children.
Tip: Try not to bin a context directly in the HTML, since each time the component is rendered it will create a new instance of the function which is not very efficient. Instead, make the bind in the constructor or use the decoration @autobind
of core-decorators .
App
class App extends React.Component {
constructor(props) {
this.state = {
stock: {} // elemento activo
};
}
render() {
return (
<div className="col-lg-4">
<ul className="stocks">
{
this.stocks().map(stock => (
<StockSingle
key ={stock._id}
stock={stock}
notify={this.onStockClick.bind(null, stock)}
/>
));
}
</ul>
</div>
<div className="col-lg-8">
<myChart
stock={this.state.stock}
/>
</div>
);
}
/**
* Será llamado por StockSingle cada vez
* que se un stock sea pulsado.
* @param {SingleStock} stock Stock pulsado
*/
onStockClick(stock) {
this.setState({ stock });
}
}
StockSingle
export default class StockSingle extends Component {
constructor(props) {
super(props);
this.state={
showComponent:false
};
}
clickStock(event) {
this.setState({
showComponent:true
});
// notificamos que se ha pulsado sobre un stock
this.notify(this.props.stock);
}
render() {
return (
<div>
<li onClick={this.clickStock.bind(this)}>
{this.props.stock.name}
</li>
{
this.state.showComponent
? <Loader stock={this.props.stock}/>
:null
}
</div>
);
}
}
Chart
export default class Chart extends Component {
constructor(props) {
super(props);
this.state = {
stock: {}
};
}
/*
* Este método será llamado cuando se reciba
* una propiedad desde fuera. Si la propiedad
* es otra, actualizamos el estado, causando
* una re-renderización.
*/
componentWillReceiveProps (nextProps) {
const { stock } = nextProps;
if (this.state.stock._id && stock._id !== this.state.stock._id) {
this.setState({ stock });
}
}
render() {
return (
<!-- HTML here -->
);
}
}
Basically three things happen:
Element% co_of% pressed. Update your status and call SingleStock
.
Method notify
of notify
executed. Update the status by changing the stock .
Graph detects a change in its properties. Compare the stock property with the stock in your state. If they are different, it is re-rendered.
Redux
With Redux it's much simpler, for example, we only dispatch one action and the reducer will update the status. When you click on a App
we dispatch an action like this:
const changeStock = stock => {
return {
type: 'CHANGE_STOCK',
stock
};
}
In this way ( StockSingle
):
this.props.dispatch(changeStock(this.state.stock));
Then, by means of a reducer:
const stockReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_STOCK':
return Object.assign({}, state, { action.stock });
default: return state;
}
};
We update the status (called store , which represents the state of the application, not local) overwriting the stock clicked. Finally, we get it in SingleStock
:
@connect(
state => { stock: state.stock }
)
class Graph extends Component {
constructor (props) {
this.state = {
stock: {}
};
}
componentWillReceiveProps (nextProps) {
const { stock } = nextProps;
if (this.state.stock._id && this.sate.stock._id !== stock._id) {
this.setState({ stock });
}
}
render() {
return (
<!-- HTML aquí -->
);
}
}
The decoration Graph
connects the state of the application ( store ) with that component. The first parameter indicates what data from the store we want to be available in @connect
.
Redux may seem complicated at first, if you are starting with React I recommend you go with the first approach.