I’ve made a lot of progress on my Recipe box app, and I may even be done soon. However, I have a couple more steps left. One being that when trying to push new recipes to my box, how do I tell my Recipe box component to update when the var Recipes (which is an array of JSON objects) updates? I know there are specific functions to tell components to update based on state and props, but is there any other way to tell them to update?
Wouldn’t you just want to update your state when adding a new recipe to your box? React should take care of updating components on changes to the state.
Well but I’m not keeping my recipes in state @alightedlamp
What is the reason for not keeping them in state?
I found this that looks like what you want:
If it is not in the state than you are probably doing something wrong. Put it in local state or use a state container like redux.
@nr-mihaylov the way I currently have it is I have a component that calls a recipe panel for each JSON recipe in an array called recipes. I was doing this as a medium until I put these into local storage. Maybe you can direct me in a better way to hold the recipes?
Yeah, I think the point of React is to keep every variable that affects the output in state so React can do it’s thing.
Is there a reason why you can’t put the recipes in state? Maybe it’s a matter of structure. I had the following component structure:
App
Header
RecipeBox
EditRecipe (hidden modal window)
Recipes
Recipe
Controls
Footer
I held state in RecipeBox because that was the lowest ancestor of all the children that needed access to the state. I think I kept everything in state. I even kept a dummy recipe in state for adding and editing recipes.
I think the point is that everything that needs to be passed between components (and especially if it affects the screen) should be kept in state.
@ksjazzguitar But isn’t the end goal to keep the data in local storage? That is one of the requirements in of the project
Can you share some code? It would be much easier if we could look at it.
Yes, I did both. I kept a copy in state but I also (whenever a recipe was added/edited/deleted I changed the browser local memory.
@nr-mihaylov
Of course. I’ve decided to use a local environment in order to improve my understanding on how react is used in standard production.
Here’s my code:
import React from 'react'
import ReactDOM from 'react-dom'
import ReactBootstrap from 'react-bootstrap'
import { ListGroup } from 'react-bootstrap'
import { ListGroupItem } from 'react-bootstrap'
import { Panel } from 'react-bootstrap'
import { ButtonGroup } from 'react-bootstrap'
import { Button } from 'react-bootstrap'
import { Modal } from 'react-bootstrap'
const recipes = [
{
"name" : "Baklava",
"ingredients": ["Flower", "Baking soda", "Pistachios", "Honey", "Puff Pastry", "Love", "Wawa"],
"image" : "http://assets.simplyrecipes.com/wp-content/forum/uploads/2008/02/baklava-horiz-a-640.jpg"
},
{
"name" : "Chips N' Dip",
"ingredients": ["Chips", "Dip"],
"image" : "http://dinnerthendessert.com/wp-content/forum/uploads/2015/09/Chips-and-Guac-Small-680x453.jpg"
}
];
//This requires a recipe props of food, ingredietns, and an optional image, and prints specifically the recipe data
//The component deals with the drop down part of every recipe along with the buttons
class CollapseableRecipe extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
}
render() {
const title = (
<div>
<a className="panelHead"onClick={()=>this.setState({open: !this.state.open})}>{this.props.food}</a>
<ButtonGroup className="add-delete">
<Button bsStyle="success">Add to shopping list</Button>
<Button bsStyle="danger">Delete Recipe</Button>
</ButtonGroup>
</div>
);
let ingredients = this.props.ingredients.map((item) => {
return (<ListGroupItem key={item}>{item}</ListGroupItem>)
});
let style = {
"width": "100%",
"height": "100%",
"borderColor": "rgb(42, 42, 42)",
"borderWidth": "5px",
"borderRadius": "10px",
"marginBottom": "2%"
};
return (
<div>
<Panel collapsible expanded={this.state.open} header={title}>
<div>
<h1 className ="text-center">{this.props.food}</h1>
{this.props.image &&
<img src={this.props.image} style={style}></img>
}
<ListGroup>
{ingredients}
</ListGroup>
</div>
</Panel>
</div>
)
}
};
class AddToList extends React.Component {
constructor(props) {
super(props);
this.state=({
showModal: false
});
}
handleClick() {
this.setState({ showModal : true});
}
close() {
this.setState({ showModal : false});
}
updateRecipes() {
if ($('#title').val() && $('#ingredients').val()) {
let recipe = {
"name" : $('#title').val(),
"ingredients" : $('#ingredients').val()
};
if ($('#image').val()) {
recipe["image"] = $('#image').val();
}
recipes.push(recipe);
this.close();
console.log(recipes[2]);
}
alert("Hold up! You gotta fill in the necessary boxes!");
}
render() {
$('body').click(function (event) {
if(!$(event.target).closest('#openModal').length && !$(event.target).is('#openModal')) {
$(".modalDialog").hide();
}
});
const myModal = (
<Modal show={this.state.showModal} onHide={() => this.close()} bsSize="large" aria-labelledby="contained-modal-title-lg">
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-lg">Add a new recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<h3>Name of Dish</h3>
<input type="text" label="Recipe" placeholder="Recipe Name" id="title" />
<h3>Ingredients</h3>
<input type="textarea" label="Ingredients" placeholder="Enter Ingredients(commas to separate)" id="ingredients"/>
<h3>Image</h3>
<input type="textarea" label="Image" placeholder="Enter a URL to an image(optional)" id="image"/>
</form>
</Modal.Body>
<Modal.Footer>
<Button bsStyle="success" id="addRec" onClick={()=> this.updateRecipes()}>Add Recipe</Button>
</Modal.Footer>
</Modal>
);
return (
<div>
<button onClick={()=> this.handleClick()} className="addThings">+</button>
{myModal}
</div>
);
}
}
class FullBox extends React.Component {
constructor(props) {
super(props);
}
render() {
let localRecipes = recipes.map((item) => {
return <CollapseableRecipe key={item["name"]} food={item["name"]} ingredients={item["ingredients"]} image={item["image"]} />
});
return (
<div>
{localRecipes}
</div>
);
}
};
ReactDOM.render(<FullBox />, document.getElementById('render-target'));
ReactDOM.render(<AddToList />, document.getElementById('render2'));
As you can see I chose to keep my dummmy recipes in an array of JSON objects at the top. I have 2 ReactDOM.render
s because I chose to have an “add to recipe box button” in the bottom right like google before I realized that materialize was a library I could’ve used. Tell me what you think
As it is right now react cannot keep track of changes in your recipes array. With react you want to let the framework do the updates. React reacts to changes in props and state. So you should move the recipes into state. I’d imagine your entire application is made from React. In that case there really isn’t a good reason to do two ReactDOM render. Its not that its wrong you can still make it work, but you would probably need redux. In other words it just gets more complicated that way.
So you should probably first change the structure of your app. @ksjazzguitar made a good suggestion earlier.
App
Header
RecipeBox
EditRecipe (hidden modal window)
Recipes
Recipe
Controls
Footer
And then move recipes to the component state.
@nr-mihaylov I only have the second DOM so I can render the add button outside of the recipe well. Am I going to be able to keep this design somehow?
I’ve moved some things around and took your advice, however currently I’m having trouble working out how to update parent state from child. Here is my new code specifically involving the “Add Recipe” button and the full app that contains recipes in its state.
class AddToList extends React.Component {
constructor(props) {
super(props);
this.state=({
showModal: false
});
}
handleClick() {
this.setState({ showModal : true});
}
close() {
this.setState({ showModal : false});
}
updateRecipes() {
if ($('#title').val() && $('#ingredients').val()) {
let recipe = {
"name" : $('#title').val(),
"ingredients" : $('#ingredients').val()
};
if ($('#image').val()) {
recipe["image"] = $('#image').val();
}
this.props.update(recipe);
this.close();
console.log(this.props.recipes[2]);
}
alert("Hold up! You gotta fill in the necessary boxes!");
}
render() {
$('body').click(function (event) {
if(!$(event.target).closest('#openModal').length && !$(event.target).is('#openModal')) {
$(".modalDialog").hide();
}
});
const myModal = (
<Modal show={this.state.showModal} onHide={() => this.close()} bsSize="large" aria-labelledby="contained-modal-title-lg">
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-lg">Add a new recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<h3>Name of Dish</h3>
<input type="text" label="Recipe" placeholder="Recipe Name" id="title" />
<h3>Ingredients</h3>
<input type="textarea" label="Ingredients" placeholder="Enter Ingredients(commas to separate)" id="ingredients"/>
<h3>Image</h3>
<input type="textarea" label="Image" placeholder="Enter a URL to an image(optional)" id="image"/>
</form>
</Modal.Body>
<Modal.Footer>
<Button bsStyle="success" id="addRec" onClick={()=> this.updateRecipes()}>Add Recipe</Button>
</Modal.Footer>
</Modal>
);
return (
<div>
<button onClick={()=> this.handleClick()} className="addThings">+</button>
{myModal}
</div>
);
}
}
class FullBox extends React.Component {
constructor(props) {
super(props);
this.state = ({
recipes:[
{
"name" : "Baklava",
"ingredients": ["Flower", "Baking soda", "Pistachios", "Honey", "Puff Pastry", "Love", "Wawa"],
"image" : "http://assets.simplyrecipes.com/wp-content/forum/uploads/2008/02/baklava-horiz-a-640.jpg"
},
{
"name" : "Chips N' Dip",
"ingredients": ["Chips", "Dip"],
"image" : "http://dinnerthendessert.com/wp-content/forum/uploads/2015/09/Chips-and-Guac-Small-680x453.jpg"
}
]
});
this.updateStatefulRecipes.bind(this);
}
updateStatefulRecipes(recipe) {
this.state.recipes.push(recipe);
}
render() {
let localRecipes = this.state.recipes.map((item) => {
return <CollapseableRecipe key={item["name"]} food={item["name"]} ingredients={item["ingredients"]} image={item["image"]} />
});
return (
<div>
{localRecipes}
<AddToList update={this.updateStatefulRecipes} recipes={this.state.recipes}/>
</div>
);
}
};
ReactDOM.render(<FullBox />, document.getElementById('render-target'));
I’ll give it a closer look tomorrow.