ReactJS encourages one-way data flow but I want to break it for easier development where I need to two-way bound Input box.
I want a component like this
var App = React.createClass({
getInitialState: function(){
return {
user: {
name: ''
}
}
},
render: function(){
return <TwoWayBinder type="input" model="user.name" />;
}
});
where user.name is a variable in this.state. So, I want <TwoWayBinder /> component to access the state of it's parent component (which is an anti-pattern according to React philosophy). I see that parent component is available in _owner property of TwoWayBinder component.
Is that the only way to access the owner? I don't want to use valueLink for multiple reasons.
There is no documented api for accessing the owner. _owner is the only undocumented way (as far as I know).
Update: "component._owner is no longer available in 0.13" -zbyte
I'm not a fan of valueLink personally. I've been working on a similar but more powerful system.
In its lowest level form your code looks like this: jsbin 1
var App = React.createClass({
mixins: [formMixin],
getInitialState: function(){
return {
data: { name: "", email: "" }
}
},
render: function(){
var formData = this.stateKey("data");
return (
<div>
<input type="text" value={this.state.data.name} onChange={formData.forKey("name").handler()} />
<input type="text" value={this.state.data.email} onChange={formData.forKey("email").handler() } />
</div>
);
}
});
That's not bad, and gives you a lot of control, but you might want something even quicker. jsbin 2
var Input = React.createClass({
render: function(){
var link = this.props.link;
return <input type="text"
{...this.props}
value={link.getCurrentValue()}
onChange={link.handler()} />
}
});
var App = React.createClass({
mixins: [formMixin],
getInitialState: function(){
return { data: { name: "", email: "" } }
}
},
render: function(){
var formData = this.stateKey("data");
return (
<div>
<Input link={formData.forKey("name")} />
<Input link={formData.forKey("email")} />
</div>
);
}
});
For completeness, here's the full mixin:
var formMixinHandler=function(thisArg,keys,parent){return{forKey:function(key){return formMixinHandler(thisArg,keys.concat(key),this)},bindTo:function(newThisArg){return formMixinHandler(newThisArg,keys,this)},transform:function(fn){var obj=formMixinHandler(thisArg,keys,this);obj.transforms=obj.transforms.concat(fn);return obj},transforms:parent?parent.transforms:[],handler:function(){var self=this;return function(event){var value=event;if(event.target instanceof HTMLInputElement)if(event.target.type==="checkbox"||event.target.type==="radio")value=event.target.checked;else value=event.target.value;self.transforms.reduce(function(last,fn){return fn(last,event)},value);var targetObj=keys.slice(0,-1).reduce(function(obj,key){if(!obj[key])obj[key]={};return obj[key]},thisArg.state);targetObj[keys[keys.length-1]]=value;var updateObject={};updateObject[keys[0]]=thisArg.state[keys[0]];thisArg.setState(updateObject)}},getCurrentValue:function(){return keys.reduce(function(obj,key){return obj?obj[key]:null},thisArg.state)}}};var formMixin={stateKey:function(key){return formMixinHandler(this,[].concat(key))}};
Just for the question, there is really internal API to get the owner in version 0.13:
this._reactInternalInstance._currentElement._owner._instance
As you know, it's really not recommended.
Related
I've built a basic restuarant recommendation app that filters by location using the YELP api. The api was responding to my requests with the response object and everything was appending to my divs perfectly, but I realized that for my project, I needed to make a new layer for the data listing. Here are the relevant portions of my two components as they are now:
display-recs:
var DisplayRecs = React.createClass({
render: function() {
var recsLoop = [];
if (this.props.recommendations) {
for (var i=0; i < this.props.recommendations.length; i++) {
recsLoop.push(<Recommendations item={this.props.recommendations[i]} />)
}
}
console.log(this.props.recommendations);
return (
<div className="DisplayRecs">
{recsLoop}
</div>
);
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
recommendations:
var Recommendations = React.createClass({
render: function() {
<div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
I cannot figure out why the nameList, phoneList, and ratingList will not print onto the dom. When I view the elements tab in my devtools, all i see is an empty displayrecs div. I've tried to just change things by guessing, but it's not been fruitful. Can any of you see an obvious problem with the current code?
Thanks
Your Recommendations react component's render function doesn't have any return statement. Try doing this:
var Recommendations = React.createClass({
render: function() {
return ( <div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>);
}
});
Also add a key to the Recommendations components as #Vikramaditya recommends:
recsLoop.push(<Recommendations key={i} item={this.props.recommendations[i]} />)
I am building a recipe box in reactJS. My objective is hide ingredients within the button, listing the recipe title. Thus when a person clicks on a button titled "cheesecake" they will see its respective ingredients. The booleans in communicating when and when not to do this makes sense as it relates to "Onclick". However, I'm unsure of how to coordinate this action when fetching data given that my ingredients data (this.props.ingredients) is implicated within recipetitlebutton component. I tried re-initializing the ingredients component within the title button thinking that I can just define it within the recipeTitleButton when I mapped over the data. However, this didn't work and it didn't feel clean. Anyway, I hope this makes sense. Any help is greatly appreciated.
Thanks
var recipes = [{
recipe_title: "Cheesecake",
ingredients: "cream cheese, graham crackers, butter, eggs"
}, {
recipe_title: "Lasagna",
ingredients: " ricotta cheese, ground beef, pasta shells, parsely"
}, {
recipe_title: "Spaghetti",
ingredients: "noodles, pasta sauce, ground beef"
}]
var RecipeTitleButton = React.createClass({
getInitialState: function() {
return {
showIngredients: false
}
},
onClick: function() {
this.setState({
showIngredients: true
})
},
render: function() {
<Ingredients ingredients={this.props.ingredients}/>
return (
<div>
<button type="button" className="recipe_title_button" class="btn btn=primary btn-lg">{this.props.recipe_title}</button>
{this.state.showIngredients ? <Ingredients/>: null}
</div>
)
}
})
var Ingredients = React.createClass({
render: function() {
return (
<div id="ingredients" className="recipe_title_ingredients">
{this.props.ingredients}
</div>
)
}
})
var MainRecipeDisplay = React.createClass({
getInitialState: function() {
return {
recipeDataObject: recipes
}
},
render: function() {
var Pages = this.state.recipeDataObject.map(function(recipeContents) {
<RecipeTitleButton recipe_title={recipeContents.recipe_title} ingredients={recipeContents.ingredients}/>
})
return (
<div>
{Pages}
</div>
)
}
})
ReactDOM.render(<MainRecipeDisplay/>, document.getElementById('content'))
It appears that you're not actually passing the click handler to any of the elements. So you've defined the onClick method in RecipeTitleButton, but you're not passing this to anything. The solution is probably as simple as passing the <button> element a property onClick={/* The function that you want to fire on click */}.
First of all, change the name of the click handler to something like onClickHandler, for sanity. Click handlers are called with an event argument, which you probably don't need for your purposes but is important to know about (for example, if you need to prevent event propagation or have the click handler figure out which button was clicked).
Then the render function for RecipeTitleButton should look like:
render: function() {
<Ingredients ingredients={this.props.ingredients}/>
return (
<div>
<button type="button" className="recipe_title_button" onClick={this.onClickHandler.bind(this)} class="btn btn=primary btn-lg">{this.props.recipe_title}</button>
{this.state.showIngredients ? <Ingredients/>: null}
</div>
)
}
In case you're wondering, we bind the function to this so that the context remains the RecipeTitleButton component, instead of the window.
Oh, as an aside, it'd be easier to test this if you put it in a JSFiddle
Actually I figured it out. Was very simple. First, React doesn't recognize changing the setState value with a mere {setState({showingredients: true}). I had to use a function to make this explicit {setState(function(){return showIngredients:true)}. Second,in order to render the ingredients component within in my recipeTitle component, I only had to indicate props within {this.showIngredients ? :"null"/>. This way, I was allowed to define the ingredients prop within my recipeTitleButton. Code is Below:
var recipes = [{
title: "Cheesecake",
ingredients: "cream cheese, graham crackers, butter, eggs"
}, {
title: "Lasagna",
ingredients: " ricotta cheese, ground beef, pasta shells, parsely"
}, {
title: "Spaghetti",
ingredients: "noodles, pasta sauce, ground beef"
}]
var Layout = React.createClass({
getInitialState: function() {
return {
recipeDataObject: recipes,
showIngredients: false
}
},
render: function() {
var recipeContents = this.state.recipeDataObject.map(function(currentRecipe) {
return (
<RecipeTitleButton title={currentRecipe.title} ingredients={currentRecipe.ingredients}/>
)
})
return (
<div>
{recipeContents}
</div>
)
}
})
var RecipeTitleButton = React.createClass({
getInitialState: function() {
return {
showIngredients: false
}
},
handleRecipeButtonClick: function() {
**this.setState(function() {
return {
showIngredients: true
}**
});
},
render: function() {
return (
<div>
<button type="button" onClick={this.handleRecipeButtonClick.bind(this)} className="recipe_button">{this.props.title}</button>
**{this.state.showIngredients && <Ingredients ingredients={this.props.ingredients}/>}**
</div>
)
}
})
var Ingredients = React.createClass({
render: function(){
return(
<div className= "ingredients_list">
{this.props.ingredients}
</div>
)
}
})
Note: I'm using the beautiful library react-rails though it should not impact the answer as far as I understand my problem.
I have a <Component /> which loads a <Map />, which implies client-side rendering as it doesn't make sense on the server side (at least the lib I'm using doesn't do that).
So instead, I want to display an image before the clientside is ready, to apply the Skeuomorphism principle.
Basically, this means I have:
var Component = React.createClass({
render: function() {
var content;
if (this.state.clientSideReady) { // How can I change my component state here?
content = <Map />
} else {
content = <PlaceholderImage />
}
return (<div>{content}</div>)
}
});
From my current understanding, componentDidMount is called on the server-side, when the template string is generated. How can I know the component actually mounted on the clientside so I can replace my image by the actual map?
My mistake. As from this answer componentDidMount isn't called on the server-side.
So I can go:
var Component = React.createClass({
getInitialState: function() {
return {
clientSideReady: false
}
},
componentDidMount: function() {
this.setState({
clientSideReady: true
});
},
render: function() {
var content;
if (this.state.clientSideReady) {
content = <Map />
} else {
content = <PlaceholderImage />
}
return (<div>{content}</div>)
}
});
Hitting a roadbump, I’m seeking some help. I'm starting to move more of my "state" pojo's out of my React components, due to for example being unsure how how my pojo’s setter methods should be utilized now (one may want setter methods to validate, etc.). I now find myself either defying React docs' warning to NEVER touch this.state directly or moving most code except rendering – including state - outside of the React component into my own js variables/objects (and holding a reference to the rendered object then using forceUpdate() to rerender). What is the recommended way to freely use whatever plain old js data/model objects I want, including with setter methods?
This barebones example, where I’m wanting a form-backing data object, demonstrates this difference I’m facing: http://jsfiddle.net/jL0rf0ed/ vs. http://jsfiddle.net/rzuswg9x/. Also pasted the code for the first below.
At the very least, I have this specific question: following a custom/manual update of this.state, does a this.setState(this.state) line, which would be from within the React component, and a component.forceUpdate() line, which would likely be from outside the React component, work just as fast and correctly as the standard this.setState({someKey: someValue})?
Thanks.
//props: dataObj, handleInputChange
test.ComponentA = React.createClass({
getInitialState: function() {
return {
age: 21,
email: 'a#b.com', //TODO make members private
setEmail: function(val) { //TODO utilize this
this.email = val;
if(val.indexOf('#') == -1) {
//TODO set or report an error
}
}
}
},
handleInputChange: function(e) {
this.state[e.target.name]=e.target.value; //defying the "NEVER touch this.state" warning (but it appears to work fine)!
this.setState(this.state); //and then this strange line
},
render: function() {
return (
<div>
<input type='text' name='age' onChange={this.handleInputChange} value={this.state.age}></input>
<input type='text' name='email' onChange={this.handleInputChange} value={this.state.email}></input>
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
});
React.render(<test.ComponentA />, document.body);
For your code example in your pasted snippet, you can do the following.
handleInputChange: function(e) {
var updates = {};
updates[e.target.name] = e.target.value;
this.setState(updates);
},
In your second example, you should never call forceUpdate or setState from outside the component itself. The correct way would be for the state to be contained in whatever renders your component and pass in the data as props.
Usually this means you have a wrapper component.
var RootComponent = React.createClass({
getInitialState: ...
onInputChange: function() {
this.setState({yourKey: yourValue});
},
render: function() {
return <SubComponent yourKey={this.state.yourKey} onInputChange={this.onInputChange} />;
}
};
In your case, I would recommend creating this wrapper component. Another solution is just to rerender the same component into the same DOM node.
test.handleInputChange = function(e) {
// update test.formPojo1 here
React.render(<test.ComponentA dataObj={test.formPojo1} handleInputChange={...} />);
}
Because it is the same component class and DOM node, React will treat it as an update.
Stores
Facebook uses the concept of a Store in their Flux architecture.
Stores are a very targeted POJO. And I find that it is pretty simple to use the Store metaphors without the using the entirety of Flux.
Sample Store
This is a Store that I pulled out of one of our production React apps:
ChatMessageStore = {
chatMessages: [],
callbacks: [],
getAll: function() {
return this.chatMessages;
},
init: function() {
this.chatMessages = window.chat_messages.slice();
return this.emitChange();
},
create: function(message) {
this.chatMessages.push(message);
return this.emitChange();
},
emitChange: function() {
return this.callbacks.forEach(callback, function() {
return callback();
});
},
addChangeListener: function(callback) {
return this.callbacks.push(callback);
},
removeChangeListener: function(callback) {
return this.callbacks = _.without(this.callbacks, callback);
}
};
Hooking it up to a React Component
In your component you can now query the store for its data:
var Chat = React.createClass({
componentWillMount: function() {
return ChatMessageStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
return ChatMessageStore.removeChangeListener(this._onChange);
},
getInitialState: function() {
return this.getMessageState();
},
getMessageState: function() {
return {
messages: ChatMessageStore.getAll()
};
}
});
The Component registers a callback with the Store, which is fired on every change, updating the component and obeying the law of "don't modify state."
I'm trying to write a simple page slider. Here, when I click a page, it creates a new Page with random content, and re-renders the App component. On App render(), instead of the TransitionGroup holding both state.pages until animation completes, it just switches out the pages, never attaching the enter-leave classes and not performing the css animation. I'm sure I'm messing something up in the LifeCycle, but can't think of it.
Thanks for looking!
var Page = React.createClass({
handleClick: function(){
var pgs = ['page-one','page-two','page-three','page-four']
currentIdx = Math.floor(Math.random() * pgs.length);
var pg = pgs[ currentIdx ];
var newPg = <Page html={pg} title={'Title for ' + pg} />;
React.renderComponent(<App newPage={newPg} />, document.body)
},
render: function(){
return (<div className="content" style={{paddingTop: 44}} onClick={this.handleClick}>{this.props.html}</div>);
}
})
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var App = React.createClass({
getInitialState: function() {
return {pages: [<Page html="initial page" title="initial title" />]};
},
componentWillMount: function(){
this.setState({pages: [this.props.newPage]})
},
componentWillReceiveProps: function(nextProps) {
this.setState({pages: [nextProps.newPage]});
},
render: function() {
var title = this.state.pages.length ? this.state.pages[ this.state.pages.length - 1 ].props.title : 'none';
return (
<div id="body">
<TitleBar title={title} />
<ReactCSSTransitionGroup transitionName="pg" id="transdiv" component={React.DOM.div}>
{this.state.pages}
</ReactCSSTransitionGroup>
</div>
);
}
});
The problem was that I was setting the Page keys in Page.render() (not shown above), and not in App.render() I'm not sure why you can't set keys in the child/owned component as long as they're unique, but this fixed my problem.
var App = React.createClass({
// other methods are same
render: function(){
var title = 'Title';
var pgs = this.state.pages.map(function(pg){
// SET KEY HERE
pg.props.key = pg.props.title;
return pg;
}
return (
<div id="body">
<TitleBar title={title} />
<ReactCSSTransitionGroup transitionName="pg" id="transdiv" component={React.DOM.div}>
{pgs}
</ReactCSSTransitionGroup>
</div>
);
}
}
Also, if anyone can tell me the correct way to set props on unmounted components, please tell me. Setting them directly works, but it doesn't feel right.