ReactJS: Control a child state from the child and the parent - reactjs

I have a rather simple problem and I'm not sure how to solve it with React's one way data flow.
Say you have a link in the parent that shows a modal
In the modal, you have an "X" that closes it.
I know I can change the state of the modal from the parent via props
// In the parent
<Modal display={this.state.showModal} />
// In the modal
<div className={this.props.display ? "show" : "hide"}>
<a className="close">×</a>
...
</div>
And I know how to close the modal, but not both. Not sure how to keep a state that is shared and controllable by both the parent and the child modal.
UPDATE
In trying to keep this as modular as possible, I think the React way would be to store the open/close logic in the modal variable.
var ParentThing = React.createClass({
...
render (
<Modal /> // How can I call this.open in the modal from here?
)
});
var Modal = React.createClass({
setInitialState: function() {
return {
display: false
}
},
close: function() {
this.setState({ display: false });
},
open: function() {
this.setState({ display: true });
},
render: function() {
return (
<div className={this.state.display ? "show" : "hide"}>
<a className="close" onClick={this.close}>×</a>
</div>
)
}
});
I saw this method, but it seems to be a little more than I need to do here. Reactjs: how to modify child state or props from parent?

There are two ways to handle this kind of thing in React:
Make the child "controlled," just like a form input with a value and onChange property, where the owner of the input controls the input.
Make the child "uncontrolled," just like a form input without a value.
The second choice seems faster up front, but just like managing a collection of form inputs in React, the advantage to using fully controlled components becomes apparent as complexity builds and the need to fully describe your UI at any point and time increases. (See this excellent answer from FakeRainBrigand if you're curious exactly why controlled components is better than uncontrolled in most cases.)
However, just like form inputs, there's no reason your component can't be either controlled or uncontrolled. If the user passes a display and onClose property, like Austin Greco's answer, you have a controlled modal, and the parent fully decides when to show or hide the modal.
If the user doesn't, you can skip using the properties and instead delegate to internal state managed by public methods on the modal component:
var ParentThing = React.createClass({
...
render: function() {
return <Modal ref="modal" />;
},
handleSomeClick: function() {
this.refs.modal.open();
}
});
var Modal = React.createClass({
setInitialState: function() {
return {
display: false
}
},
close: function() {
this.setState({ display: false });
},
open: function() {
this.setState({ display: true });
},
render: function() {
return (
<div className={this.state.display ? "show" : "hide"}>
<a className="close" onClick={this.close}>×</a>
</div>
)
}
});
If you like the idea of a controlled Modal component, but don't want to do all the boilerplate typing, you could even go so far as to implement something like the valueLink property for the Modal to simplify this pattern.
var ParentThing = React.createClass({
...
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return { showModal: false };
},
render: function() {
return <Modal displayLink={this.linkState("showModal")} />;
},
handleSomeClick: function() {
this.setState({showModal: true});
}
});
var Modal = React.createClass({
close: function() {
this.props.displayLink.requestChange(false);
},
render: function() {
return (
<div className={this.props.displayLink.value? "show" : "hide"}>
<a className="close" onClick={this.close}>×</a>
</div>
)
}
});
(See my blog post on creating custom components that work with linkState/valueLink for more info.)
So now you get the benefit of using a fully parent-controlled Modal, but you've removed a portion of the boilerplate around creating a function that sets the value to false and passing it to the modal.

You could pass a callback as a prop to the child component:
// In the parent
<Modal display={this.state.showModal} onClose={this.closeModal} />
// In the modal
<div className={this.props.display ? "show" : "hide"}>
<a className="close" onClick={this.props.onClose}>×</a>
...
</div>
Then when you click the close button on the child, it will call the function of the parent

Related

Why are component `refs` `undefined` when I try to access them?

I am new to React and I am using React v0.13.3 and JSXTransformer v0.13.3 to create a couple of simple components, each of which renders an input field along with a button inside a paragraph. When any button is clicked, I want to show the associated input value using an alert. I am trying to use refs to get the value, but for some reason it is not working, and shows undefined.
Here is my code:
var CommentBox = React.createClass({
show: function() {
alert(this.refs.test.value);
},
render: function() {
return(<p><input type="text" ref="test" /><button type="button" onClick={this.show}>Show</button></p>);
}
});
React.render(<div><CommentBox /><CommentBox /></div>, document.getElementById('commentbox'));
I would suggest to bind onChange of the input to set the value on the state, like so:
<input onChange={event => this.setState({value: event.target.value})} />
Now this.state.value always has the current value of the field. Then on the show function, just do:
show: function() {
alert(this.state.value);
}
Your code is working just fine! I put it in a jsfiddle.
However, that's not a good approach for your specific use-case. In general, you must try not to overuse refs. Here's a quote from the ReactJS related docs:
Your first inclination may be to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy.
So, here is a better approach:
For similar purposes, just like the one you need, using a controlled component is the preferred way. I suggest you to consider using your Component state.
Therefore, here's an example how you can achieve the same result, using the Component state. I am using your code snippet as a base:
var CommentBox = React.createClass({
getInitialState() {
return {
// That's the default input value
value: ''
};
},
show: function() {
alert(this.state.value);
},
handleChange: function(event) {
// Each time the input's value is changed, the state gets an update
this.setState({
value: event.target.value
});
},
render: function() {
return(
<p>
<input onChange={this.handleChange} type="text" />
<button type="button" onClick={this.show}>Show</button>
</p>
);
}
});
React.render(
<div><CommentBox /><CommentBox /></div>,
document.getElementById('commentbox')
);

Sending data to an indirect child in React.js on a click event

I have an application that has two major components (Landing and Skills):
App = React.createClass({
render() {
return (
<div>
<Landing />
<Skills category={category}/>
</div>
);
}
});
Within "Landing", I have a SocialMenu component, that has a list of items (the list of items is fed to SocialMenu like: <SocialMenu items={ ['Home', 'Services', 'About', 'Contact us']} />. On click, the item that is clicked is highlighted.
SocialMenu = React.createClass({
getInitialState: function(){
return { focused: 0 };
},
clicked: function(index){
this.setState({focused: index});
},
var self = this;
return (
<div>
<ul className="testblocks">{ this.props.items.map(function(m, index){
var style = '';
if(self.state.focused == index){
style = 'focused';
}
return <li key={index} className={style} onClick={self.clicked.bind(self, index)}>{m}</li>;
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
});
What I would like to have happen, is have the index data from SocialMenu, passed to the Skills component.
However, I am not sure how to do that, because SocialMenu is a child of Landing, and not of Skills. (pretty much I would like to keep the list in landing, but have the output of the click of that list be put in Skills).
How would I do this?
You need something like Flux to have communication betweeen components that don't have parent-child relationship:
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
So, for your case, you need to have a event like SELECT_SOCIAL that will pass the index information and your Landing component will have a listener to receive the index information.
You really don't need Flux for this. You probably will at some point, but if this is all you need to do, why don't you move the state up to the App component, and send it Landing & Skills components as props. You would probably also need to pass the clicked handler as a prop to SocialMenu.

pass variable to function via bind in react component

Below is the code snippet from a navigation menu example, which is from this blogpost. What I don't understand is the use of self variable and bind method.
I think this refers to MenuExample when it is stored as self, but I don't know how this changed inside <li>?
Also why is that onClick={this.clicked(index)} doesn't work? In this context what does this refers to?
var MenuExample = React.createClass({
getInitialState: function(){
return { focused: 0 };
},
clicked: function(index){
// The click handler will update the state with
// the index of the focused menu entry
this.setState({focused: index});
},
render: function() {
// Here we will read the items property, which was passed
// as an attribute when the component was created
var self = this;
// The map method will loop over the array of menu entries,
// and will return a new array with <li> elements.
return (
<div>
<ul>{ this.props.items.map(function(m, index){
var style = '';
if(this.state.focused == index){
style = 'focused';
}
// Notice the use of the bind() method. It makes the
// index available to the clicked function:
return <li className={style} onClick={self.clicked.bind(self, index)}>{m}</li>;
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
});
// Render the menu component on the page, and pass an array with menu options
ReactDOM.render(
<MenuExample items={ ['Home', 'Services', 'About', 'Contact us'] } />,
document.getElementById('container')
);
onClick={this.clicked(index)} doesn't work because onClick expects a function, not a result of it. Normally, in a click handler, an event will be passed to it, but here they are using bind() to override that behavior and pass the component and the index as 'this' and the first argument.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Passing a function down to a component through props - REACT

I'm trying to pass the 'handleItemClick' function from the 'Dropdown' component, down to they 'Team' component, however, I can't get it past the 'List' component. The strange thing is, is that when I log the props inside the 'List' it says that 'itemClick' is in the 'this.props' object and yet when I set it as a prop on the 'Team' component it says "Cannot read property 'itemClick' of undefined".
Any help would be much appreciated.
Dropdown Component:
var Dropdown = React.createClass({
getInitialState: function(){
return {
display: false
};
},
handleClick: function(e){
this.setState({display: !this.state.display})
},
handleItemClick: function(e){
console.log("Something");
},
render: function(){
return (
<div className="dropdown">
<Button whenClicked={this.handleClick} className="btn-default" title={this.props.data.title} number={this.props.data.teams.length} />
<List teams={this.props.data.teams} display={this.state.display} itemClick={this.handleItemClick}/>
</div>
);
}
});
List Component:
var List = React.createClass({
render: function(){
console.log(this.props)
var teams = this.props.teams.map(function(team){
return <Team key={team} team={team} teamChoice={this.props.itemClick} />
});
var displayStyle = {
display: this.props.display ? 'block' : 'none'
};
return (
<ul style={displayStyle} className="dropdown-menu">
{teams}
</ul>
);
}
});
Kristofen44 was close:
Array.prototype.map() loses the this from your parent scope inside it's callback. But the function includes a variable input for accessing this within it:
var teams = this.props.teams.map(function(team){
return <Team key={team} team={team} teamChoice={this.props.itemClick} />
}, this);
I'm not sure but I think the error resides in List Component's render function when you map team to generate nodes. the map callback function loses the context 'this'. I think you have to bind the callback function to 'this' explicitly.
Like so :
var teams = this.props.teams.map(function(team){
return <Team key={team} team={team} teamChoice={this.props.itemClick} />
}.bind(this)); // <------
BTW, being quite new to react, I don't know if it's good practice to pass an whole object to the attribute 'key' of your Team component... I wonder if it's not better to just pass an id or something like that..
Hope it helps

Smoothly Transitioning Between ReactJS Prop Changes

There are two buttons that toggle the layout of an item. When either button is clicked, I'd like the item to fade out, change, then fade back in.
Using ReactJS, I'm running into two problems:
Using componentDidUpdate to trigger the "fade back in" event causes a loop; changing the state re-triggers componentDidUpdate endlessly.
Using componentWillReceiveProps allows me to update the class on the element to have it fade out, but it also immediately changes the layout. I need to delay the change until it's invisible.
Thoughts? Have I constructed this wrong?
(The code is below, but something is broken in Stack's version that works in JSFiddle: https://jsfiddle.net/nathanziarek/69z2wepo/15009/)
var Hello = React.createClass({
getInitialState: function() {
return { visible: " x-hidden" };
},
render: function() {
return <div className={"hello" + this.state.visible}>Hello {this.props.name}</div>;
},
componentDidMount: function(){
this.setState({ visible: "" })
},
componentWillReceiveProps: function(){
// The item is changing, make it invisible
// Wait until it's done being invisible before changing
// anything?
this.setState({ visible: " x-hidden" })
},
componentDidUpdate: function(){
// The item has changed, make it visible
// Setting anything here causes this function
// to get called again, creating a loop
// this.setState({ visible: "" })
}
});
var Button = React.createClass({
render: function() {
return <button onClick={this.handleClick}>{this.props.label}</button>;
},
handleClick: function() {
React.render(
<span>
<Button label="Universe"/>
<Button label="World"/>
<Hello name={this.props.label} />
</span>,
document.getElementById('container')
);
}
});
React.render(
<span>
<Button label="Universe"/>
<Button label="World"/>
<Hello name="_______" />
</span>,
document.getElementById('container')
);
.hello {
opacity: 1;
transition: 300ms opacity linear;
-webkit-transition: 300ms opacity linear;
}
.x-hidden { opacity: 0 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/react-with-addons.js"></script>
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I would look into using React's CSSTransitionGroup add-on component. You'll have to do some restructuring, but this will give you the behavior you want without having to add a bunch of logic around setting CSS classes; React will take care of animating (in your case, fading) components that are entering/leaving the DOM.
Unrelated: I would also avoid re-rendering your entire component like how you're doing it in your button click event. This breaks the flow of React and will inevitably cause problems. Prefer changing state and pushing down new props.
Easiest way to do this is to have both on screen then to do the fade entirely with css, and essentially completely omit React from the process other than to place the appropriate classes.
If you're fading between them you need both sets of contents anyway. That, in turn, means there's no reason to not have these in the document, and that in turn means it's just CSS hide/show tomfoolery, and there's no need for any kind of React lifecycle stuff.
You're overcomplicating it by trying to have React handle way too much.
Supporting CSS:
#foo span {
opacity: 0;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
transition: all 0.5s ease;
}
#foo span.show {
opacity: 1;
}
Then the relevant React:
var Foo = react.createComponent({
render: function() {
var showKey = this.props.showKey,
Things = {
a: 'Apple',
b: 'Bear',
c: 'Cold inevitable doom'
};
return (
<div id="foo">
{ Things.map(X,k) {
return <span class={(showKey === k)? 'show':undefined}>{X}</span>
}}
</div>
);
}
});
You'll find that as you switch that control's showKey, it will fade in and out its relevant contents as appropriate.

Resources