I'm trying to create a todo list where after you finish one task,
only then will the next task be enabled (to tick as finished).
Here is what I have so far:
/** #jsx React.DOM */
$(function(){
var tasks = [
{title: "Wake up"},
{title: "Eat dinner"},
{title: "Go to sleep"}
];
var Task = React.createClass({
getInitialState: function(){
return {locked:true, done:false}
},
handleClick: function(e){
this.setState({done: !this.state.done});
var selector = '.toggle[data-order="'+(this.props.order+1)+'"]';
this.setState({locked: true})
console.log(selector)
console.log($(selector).removeAttr("disabled"))
},
render: function() {
var locked;
//Fix first task to not be disabled
if(this.props.order == 0 && this.state.done === false)
locked = false;
else
locked = this.state.locked;
var done = this.state.done ? "Done":"Not done";
var classView = "task" + (this.state.done ? " done":" not-done");
return (
<div class="todo well well-sm" class={classView}>
<span class="description">{this.props.title}</span>
<button type="button" onClick={this.handleClick} data-order={this.props.order} disabled={locked} class="toggle btn btn-default btn-xs pull-right">
<span class="glyphicon glyphicon-unchecked"></span> Done
</button>
</div>
);
}
});
var TaskList = React.createClass({
render: function(){
var i = -1;
var taskNodes = this.props.data.map(function (task) {
return <Task title={task.title} order={++i} />;
});
return (
<div class="task-list">
{taskNodes}
</div>
);
}
});
var Guider = React.createClass({
render: function(){
return (
<div>
<TaskList data={this.props.data} />
</div>
);
}
});
React.renderComponent(<Guider data={tasks} />, document.body);
});
The next buttons are still not disabled, and I feel that I'm doing something wrong in general (not in accordance with the react "zen").
Btw:
How can I change the state for a dom element without the user triggering it? is there any id I should use?
If you initiate the data into non-root component, it becomes hard to update other components. So I prefer keeping data into root component, Then pass a click handler as props. Now you'll have access to that handler inside non-root component. Calling that will update root component and so the other non-root components.
Here's working jsFiddle - http://jsfiddle.net/ammit/wBYHY/5/
Example -
var Task = React.createClass({
handleClick: function (e) {
// Passing order of task
this.props.clicked(order);
},
render: function () {
return ( <button type="button" onClick={this.handleClick}></button> );
}
});
var TaskList = React.createClass({
getInitialState: function(){
// initiate tasks here
},
whenClicked: function(order){
// Revise the tasks using `order`
// Finally do a setState( revised_tasks );
},
render: function(){
return ( <Task clicked={this.whenClicked} /> );
}
});
Related
I saw some questions speaking about similar issues but somehow I still do not manage to solve my issue so here I am asking for your kind help. I am pretty new to React and would like to send a function from a Parent to a child and then use it from the Child but somehow when I want to use it it says
Uncaught TypeError: Cannot read property 'props' of undefined"
Edited Code after first answers were helping:
var Menu = React.createClass({
links : [
{key : 1, name : "help", click : this.props.changePageHelp}
],
render : function() {
var menuItem = this.links.map(function(link){
return (
<li key={link.key} className="menu-help menu-link" onClick={link.click}>{link.name}</li>
)
});
return (
<ul>
{menuItem}
</ul>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function() {
console.log('help');
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
Pass a value from Menu function and recieve it in the changePageHelp function and it works.
var Menu = React.createClass({
render : function() {
return (
<div>
{this.props.changePageHelp('Hello')}
</div>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function(help) {
return help;
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="admin"></div>
For performance reasons, you should avoid using bind or arrow functions in JSX props. This is because a copy of the event handling function is created for every instance generated by the map() function. This is explained here: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
To avoid this you can pull the repeated section into its own component. Here is a demo: http://codepen.io/PiotrBerebecki/pen/EgvjmZ The console.log() call in your parent component receives now the name of the link. You could use it for example in React Router.
var Admin = React.createClass ({
_changePageHelp : function(name) {
console.log(name);
},
render : function () {
return (
<div>
<div id="menu-admin">
<Menu changePageHelp={this._changePageHelp} />
</div>
</div>
);
}
});
var Menu = React.createClass({
getDefaultProps: function() {
return {
links: [
{key: 1, name: 'help'},
{key: 2, name: 'about'},
{key: 3, name: 'contact'}
]
};
},
render: function() {
var menuItem = this.props.links.map((link) => {
return (
<MenuItem key={link.key}
name={link.name}
changePageHelp={this.props.changePageHelp}
className="menu-help menu-link" />
);
});
return (
<ul>
{menuItem}
</ul>
);
}
});
var MenuItem = React.createClass ({
handleClick: function() {
this.props.changePageHelp(this.props.name);
},
render : function () {
return (
<li onClick={this.handleClick}>
Click me to console log in Admin component <b>{this.props.name}</b>
</li>
);
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
My project keeps crashing when I attempt to insert a new recipe element. I use the this.state.recipes.map... in RecipeList to be able to update the recipes as needed(e.g. delete,edit,etc.). The delete functionality works, but I am unable to add a new recipe element.
If I switch the statement to this.props.recipes.map..., I am able to insert elements without an issue, but am unable to delete since the delete triggers a state change, and needs the state change to reflect the update instead of the props. Anyone have any tips on this issue? Thanks!
Recipe List:
var RecipeList = React.createClass({
getInitialState: function(){
return {recipes: []};
},
deleteRecipe: function(recipe){
var curRecipes = this.state.recipes.slice('');
curRecipes.splice(recipe.recipeKey,1);
this.setState({recipes: curRecipes});
},
componentWillMount: function(){
this.setState({recipes: this.props.recipes});
},
render: function(){
var recipeNodes = this.state.recipes.map(function(recipe,index){
return <Recipe onDelete={this.deleteRecipe} recipeKey={index} key={index} recipeTitle={recipe.recipeTitle} ingredients={recipe.ingredients} instructions={recipe.instructions} />
},this);
return(
<div>
{recipeNodes}
</div>
);
}
});
Recipe Container:
var RecipeBox = React.createClass({
getInitialState: function(){
return {showForm: false,
recipes: []
};
},
openForm: function(){
this.setState({showForm: true});
},
handleRecipeSubmit: function(recipe){
var curRecipes = this.state.recipes.slice('');
curRecipes.push({recipeTitle: recipe.recipeTitle,ingredients: recipe.ingredients, instructions: recipe.instructions});
this.setState({recipes: curRecipes});
},
render: function(){
return(
<div id="recipeBox">
<RecipeList recipes={this.state.recipes} />
<div className="recipeButtons">
<button id="addRecipeButton" className="btn-style" onClick={this.openForm}>Add Recipe</button>
</div>
{this.state.showForm ? this.refs.dialogWithCallBacks.show() : null}
<SkyLight
dialogStyles={formDialog}
ref="dialogWithCallBacks"
title="Add Recipe">
<RecipeForm onRecipeSubmit={this.handleRecipeSubmit} skylightRef={this.refs.dialogWithCallBacks} />
</SkyLight>
</div>
);
}
});
Recipe Form:
var RecipeForm = React.createClass({
getInitialState: function(){
return {hideDialog: false};
},
getFormData: function(){
var ingredients= document.getElementsByClassName("ingredient"),
recipeName = document.getElementsByName('recipeName')[0].value,
instructions = document.querySelector('textarea').value,
data = [];
ingredients = [].slice.call(ingredients).map(function(ingredient,index){
return {
"quantity": ingredient.childNodes[0].value,
"ingredient": ingredient.childNodes[1].value,
"unit": ingredient.childNodes[2].value
};
});
// Combine results into output array
data.push(recipeName);
data.push(ingredients);
data.push(instructions);
return data;
},
submitRecipe: function(event){
event.preventDefault();
var data = this.getFormData();
// Hide the SkyLight modal container
this.setState({hideDialog: true});
// Submit form
this.props.onRecipeSubmit({recipeTitle: data[0], ingredients: data[1], instructions: data[2]});
},
render: function(){
return(
<form onSubmit={this.submitRecipe}>
<section className="recipe-main">
<h2 style={{'border-bottom': 'none'}}>Recipe Name</h2>
<RecipeFormName />
<h2 style={{'border-bottom': 'none'}}>Ingredients</h2>
<RecipeFormIngredients />
</section>
<RecipeFormInstructions />
<input type="submit" value="Add Recipe" />
{this.state.hideDialog ? this.props.skylightRef.hide() : null}
</form>
)
}
});
You should move the code in componentWillMount to getInitialState.
getInitialState: function(){
return {recipes: this.props.recipes};
},
Needed to change the RecipeList component to
<RecipeList recipes={this.state.recipes} onChange={this.handleChange}/>
and then handle the deletion change from the RecipeBox instead of directly in RecipeList. Have to use this.props.map... to display new recipes and also delete visible ones.
var RecipeList = React.createClass({
getInitialState: function(){
return {recipes: this.props.recipes};
},
deleteRecipe: function(recipe){
var curRecipes = this.props.recipes.slice('');
curRecipes.splice(recipe.recipeKey,1);
this.props.onChange({recipes: curRecipes});
},
render: function(){
var recipeNodes = this.props.recipes.map(function(recipe,index){
return <Recipe onDelete={this.deleteRecipe} recipeKey={index} key={index} recipeName={recipe.recipeName} ingredients={recipe.ingredients} instructions={recipe.instructions} />
},this);
return(
<div>
{recipeNodes}
</div>
);
}
});
Ok, I do know through refs communicate between parent and child or use this.props.onClick = {this.props.onClick}, I got stuck in situation communicate between grandparent and child like this:
Says we have some blogs, once we click a blog title, the corresponding blog content will show, so we create three components: BlogAdmin, BlogTitle and Blog (Here let's just focusing on BlogAdmin and BlogTitle)
When BlogTitle is clicked, I want to notify BlogAdmin set currentblog to specify blog. But I got stuck on how to pass the data and how to trigger the event, better with out using pubSub.
Below is my example, I removed some data get/set and grammars making it clear.
var BlogTitle = React.createClass({
render: function() {
return
<li>{this.props.blog.title}</li>
}
});
var BlogTitles = React.createClass({
render: function() {
return
<ul>
{this.state.blogs.map}
<BlogTitle blog={blog} />
}
})
var BlogAdmin = React.createClass({
render: function() {
return
<BlogTitles />
<BlogContent />
}
})
The easy solution is to add a callback function and send it down all the way like this:
var BlogTitle = React.createClass({
render: function() {
return
<li onClick={this.handleTitleClick}>{this.props.blog.title}</li>
},
handleTitleClick: function() {
this.props.onBlogTitleSelection(this.props.blog);
}
});
var BlogTitles = React.createClass({
render: function() {
return
<ul>
{this.state.blogs.map}
<BlogTitle blog={blog} onBlogTitleSelection={this.props.onBlogTitleSelection} />
}
})
var BlogAdmin = React.createClass({
selectBlogTitle: function(blog) {
// act!
},
render: function() {
return
<BlogTitles onBlogTitleSelection={this.selectBlogTitle} />
<BlogContent />
}
})
I have a dynamic list of children, that are form inputs.
ex:
var FormRows = React.createClass({
getInitialState: function() {
return {
rows: []
}
},
createRows: function() {
this.props.values.maps(value){
rows.push(<FormRow ...handlers... ...props... value={value} />
}
},
addNewRow{
// add a new row
},
render: function() {
return (
<div>
{this.state.rows}
</div>
);
});
var FormRow = React.createClass({
getInitialState: function() {
return {
value: this.props.value || null
}
},
render: function() {
<input type='text' defaultValue={this.state.value} ...changeHandler ... }
}
});
This is a dumbed down version , but the idea, is a its a dynamic form, where the user can click a plus button to add a row, and a minus button, which will set the row to visibility to hidden.
This state is nested n levels deep. What is the best way to actually get the state out of the children, and submit the form? I can use 'ref' add a function to getFormValue(): { return this.state.value } to the FormRow button, but i'm not sure if thats the best practice way.
I find myself using this pattern quite often, an array of undetermined size of children, that need to pass the state up.
Thanks
It’s not a dumb question at all, and a good example of using flux principals in React. Consider something like this:
var App
// The "model"
var Model = {
values: ['foo', 'bar'],
trigger: function() {
App.forceUpdate()
console.log(this.values)
},
update: function(value, index) {
this.values[index] = value
this.trigger()
},
add: function() {
this.values.push('New Row')
this.trigger()
}
}
var FormRows = React.createClass({
addRow: function() {
Model.add()
},
submit: function() {
alert(Model.values);
},
render: function() {
var rows = Model.values.map(function(value, index) {
return <FormRow key={index} onChange={this.onChange} index={index} value={value} />
}, this)
return (
<div>{rows}<button onClick={this.addRow}>Add row</button><button onClick={this.submit}>Submit form</button></div>
)
}
})
var FormRow = React.createClass({
onChange: function(e) {
Model.update(e.target.value, this.props.index)
},
render: function() {
return <input type='text' defaultValue={this.props.value} onChange={this.onChange} />
}
});
App = React.render(<FormRows />, document.body)
I used a simplified model/event example using Array and forceUpdate but the point here is to let the model "own" the form data. The child components can then make API calls on that model and trigger a re-render of the entire App with the new data (Flux).
Then just use the model data on submit.
Demo: http://jsfiddle.net/ekr41bzr/
Bind values of inputs to some model (for example build in Backbone or Flux) and on submit retrieve values from there, without touching inputs.
I am learning ReactJS and trying to develop small CRUD form using it. At this point, I am trying to set input values from other sibling component to perform update.
So, if I click on edit button in first row on grid, Organzation Name and Description input boxes should get "abc com" and "company Abc" values respectively.
var OrganizationHandler = React.createClass(
render: function() {
return (
<div>
<OrganizationAPP onOrganizationSubmit=this.handleOrganizationSubmit} />
<OrganizationList external_DeleteOrganization={this.DeleteOrganizationFromServer} data= {this.state.data} />
</div>
);
}
});
var OrganizationList = React.createClass({
internal_DeleteOrganization: function(id) {
this.props.external_DeleteOrganization(id);
},
render: function() {
var results = this.props.data;
var parsed_results = results.objects;
var that = this;
var organizations = parsed_results.map(function(organization){
return <Organization onDeleteOrganization={that.internal_DeleteOrganization} id={organization.id} name={organization.name} description={organization.description} />
});
return(
<div>
{organizations}
</div>
);
}
});
var Organization = React.createClass({
handleDeleteClick: function() {
console.log(this.props);
this.props.onDeleteOrganization(this.props.id);
},
handleEditClick: function () {
alert(this.props.name);
},
render: function() {
return (
<div className="row">
<div className="small-2 large-2 columns">{this.props.id}</div>
<div className="small-4 large-4 columns">{this.props.name}</div>
<div className="small-4 large-4 columns"> this.props.description}</div>
<div className="small-2 large-2 columns">
<input type="button" onClick={this.handleDeleteClick} data-order={this.props.id} value="Delete" />
<input type="button" onClick={this.handleEditClick} data-order={this.props.id} value="Edit" />
</div>
</div>
);
}
});
var OrganizationAPP= React.createClass({
getInitialState: function() {
return {name: '', description:''};
},
onChangename: function(e) {
this.setState({name: e.target.value});
},
onChangedescription: function(e) {
this.setState({description: e.target.value});
},
handleSubmit: function() {
var name = this.refs.name.getDOMNode().value.trim();
var description = this.refs.description.getDOMNode().value.trim();
if (!description || !name) {
return false;
}
this.props.onOrganizationSubmit('{"name":"' + name +'", "description": "' + description +'"}');
this.refs.name.getDOMNode().value = '';
this.refs.name.getDOMNode().value = '';
this.setState({name: '', description: ''});
return false;
},
render: function() {
return (
<div>
<h1>Organization Setup:</h1>
<form onSubmit={this.handleSubmit}>
<div className="row">
<div className="small-12 large-3 columns">
<label>Organization Name:</label>
<input type="text" ref="name" required value={this.props.name} onChange={this.onChangename}/>
</div>
</div>
<div className="row">
<div className="small-12 large-7 columns">
<label>description:</label>
<input type="text" required ref="description" value={this.props.description} onChange={this.onChangedescription} />
</div>
</div>
<div className="row">
<div className="small-2 large-3 columns">
<button type="submit"> Add </button>
</div>
</div>
</form>
</div>
)
}
});
I have successfully performed addition and deletion operations but I don't know how can I set values to be edit in input boxes from sibling component. Please let me know if you don't understand anything as I just started learning reactjs and I know my code is not up to the mark.
Here is an example list editing app where editing an item content is done with a parent input (could easily be a sibling).
/** #jsx React.DOM */
//React is for rendering our app UI.
//Not for writing the app.
//So the first step is building out your logic in whatever you prefer.
//That could be Backbone, some other framework, or plain JS as we do here.
//our app
var app = {
//we'll tell this listener when we change
listener: null,
//a cheap way of creating IDs for our new data
nextId: 3,
//pre-populate our data
rows: [
{id: 1, name: "entry 1"},
{id: 2, name: "entry 2"}
],
//what data are we focused on
focusedId: 1,
//add a new row of data and set it to focused
addRow: function () {
var id = this.nextId++;
this.rows.push({id: id, name: ("entry " + id)});
this.setFocusedId(id);
},
//get the name property given the data id
getName: function(id){
return _.findWhere(this.rows, {id: id}).name;
},
//update the name property of the currently focused data
updateName: function (name) {
var id = this.focusedId;
_.findWhere(this.rows, {id: id}).name = name;
this.listener.changed();
},
//set the focused data
setFocusedId: function (id) {
this.focusedId = id;
this.listener.changed();
},
};
//a row component
var Row = React.createClass({
render: function () {
if (this.props.focused) {
return <span>Value: {this.props.name} [editing]</span>;
} else {
return <span>
Value: {this.props.name}
[<a href='#' onClick={this.props.focus}>edit</a>]
</span>;
}
}
});
//the main view
var View = React.createClass({
//our state is the app
getInitialState: function () {
return {
app: app
};
},
//before we render, start listening to the app for changes
componentWillMount: function () {
this.state.app.listener = this;
},
//update if the app tells us it changed
changed: function () {
this.forceUpdate();
},
//a handler we'll use for input fields
textChanged: function (event) {
this.state.app.updateName(event.target.value);
},
//let's render
render: function () {
var app = this.state.app;
//build an array of row components
var rows = _.map(app.rows, function (row) {
var focus = function () {
app.setFocusedId(row.id);
};
//the actual row component
//give the row a unique id
//give it a name, the focus handler function,
//and tell it if it has the current focus
return <li key={row.id}>
<Row
name={row.name}
focused={row.id == app.focusedId}
focus={focus}
/>
</li>;
});
//the main app view
return <div>
EDIT:
<input
type="text"
value={app.getName(app.focusedId)}
onChange={this.textChanged}
/>
<ul>{rows}</ul>
<a href="#"
onClick={function(){app.addRow()}}>
add row
</a>
</div>;
}
});
React.renderComponent(
<View />
, document.body);
Here is a working example:
http://jsbin.com/laxejufila/2/edit