I am trying to edit the recipes record from a ReactJS app on the Cloud Firestore database. I am not sure how to access all the parameters of the update useState hook, below is the code:
import React,{useState} from 'react'
function Recipes(props) {
const [edit, setEdit] = useState(false)
const[update,setUpdate] = useState({
id:null,
recipeName: '',
ingredients:[],
desc:''
});
const [toUpdateId, setToUpdateId] = useState('')
function showEdit(){
setEdit(true);
openUpdateDialog(props.recipeName)
}
const openUpdateDialog = (newRecipe) => {
setEdit(true);
setToUpdateId(newRecipe.id);
setUpdate({
id:newRecipe.id,
recipeName: newRecipe.title,
ingredients:newRecipe.ingredients,
desc:newRecipe.desc
});
}
function closeModal(){
if(edit){
setEdit(false)
}
}
const editRecipe = () => {
db.collection('recipes').doc(toUpdateId).update({
title: update,
ingredients:update,
// desc:update.desc
});
setEdit(false);
}
const handleClose = () => {
setEdit(false)
}
return (
<Accordion>
<Card>
<Card.Title>{props.newRecipe.title}</Card.Title>
<Card.Body>
<ol>
{props.newRecipe.ingredients.split(',').map((item)=> {
return (
<li key={item}>{item}</li>
)
})}
</ol>
<Card.Text>
{props.newRecipe.desc}
</Card.Text>
<ButtonToolbar>
<Button variant="danger" onClick={event => db.collection('recipes').doc(props.newRecipe.id).delete()}>Delete</Button>
<Button variant="info" onClick={() => {openUpdateDialog(props.newRecipe)}}>Edit</Button>
</ButtonToolbar>
</Card.Body>
</Card>
<Modal show={edit} onHide={closeModal}>
<Modal.Header closeButton>
<Modal.Title>Edit Recipe</Modal.Title>
<Modal.Body>
<FormGroup>
<FormLabel>Recipe Name:</FormLabel>
<FormControl type="text"
value={update.recipeName}
name="recipeName"
placeholder="Enter Recipe Name"
onChange={e=>setUpdate(e.target.value)}
>
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>Recipe Ingredients:</FormLabel>
<FormControl type="textarea"
value={update.ingredients}
name="ingredients"
placeholder="Enter Recipe Ingredients separated by commas"
onChange={e=>setUpdate(e.target.value)}
>
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>Recipe Description:</FormLabel>
<FormControl type="textarea"
value={update.desc}
name="desc"
placeholder="Enter Recipe description"
onChange={e=>setUpdate(e.target.value)}
>
</FormControl>
</FormGroup>
</Modal.Body>
<Modal.Footer>
<Button onClick={editRecipe}>Save</Button>
<Button onClick={handleClose}>Cancel</Button>
</Modal.Footer>
</Modal.Header>
</Modal>
</Accordion>
)
}
export default Recipes
Can someone help me with the editRecipes function which is updating the records in the database? I don't know how to access all the three variables(title,ingredients,desc) in this function using the update useState hook I have defined above.
If I remember rightly, .split converts a string into an array. Without knowing exactly what you are trying to split I expect you will need to render as an array. I suggest taking a look at this good example here. I have added a few examples of ReactJS map that you might find helpful:
render() {
return (<div>
{this.state.people.map((person, index) => (
<p key={index}>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
If you are using Cloud Firestore, then you might find these tutorials and guides helpful:
Getting started with Cloud Firestore and React Native.
Create React Native Firebase CRUD App with Firestore.
Related
I am taking input from a search input field using searchInput and setSearchInput useState hook and after I press submit button, I call fetchSearchData function providing it the input text and setCompanies hook, where companies are updated with the fetched list of companies from the API.
Then companies are passed to another component CompanyList where a map function is called there.
The problem is whenever I type in the search field, the CompanyList component is re-rendered although I did not press submit. I understand that setSearchInput will re-render SearchBar component whenever I type in it, but I don't get why CompanyList re-renders.
Search page source code:
const Search = () => {
const [companies, setCompanies]=useState([]); //List of companies returned from searching
const [searchInput, setSearchInput] = useState(""); //Search field input
//Update search text whenever the user types in
const onSearchChange = (e) => {
setSearchInput(e.target.value)
}
//use the API providing it the search input, and
//setCompanies hook to update list of companies
const onSearchSubmit = (e) => {
e.preventDefault()
fetchSearchData(searchInput, setCompanies)
}
return (
<div>
<Container>
<Row className={"searchFilterBar"}>
<Col sm={6} md={8} className={"searchBar"}>
<SearchBar onSubmit={onSearchSubmit} onChange={onSearchChange} value={searchInput} />
</Col>
<Col sm={6} md={4} className={"filterBar"}>
</Col>
</Row>
<CompanyList companies={companies} ></CompanyList>
<Row>
</Row>
</Container>
</div>
)
}
export default Search;
SearchBar component source code:
const SearchBar = ({value,onSubmit, onChange}) => {
return (
<Form
className="search-form"
onSubmit={onSubmit}
>
<div className="input-group">
<span className="input-group-text rubik-font">
<i className="icon ion-search"></i>
</span>
<input
className="form-control rubik-font"
type="text"
placeholder="Search for companies that start with..."
onChange={onChange}
value={value}
/>
<Button className="btn btn-light rubik-font" type="submit">Search </Button>
</div>
</Form>
)
}
CompanyList component source code:
function MapDataToCompanyList(response) {
console.log(response); //Logging occurs here
if(!response || response===undefined || response.length===0)
{
return (<ErrorBoundary message={noCompaniesError.message}></ErrorBoundary>)
}
return response.map((company) => {
return (
<Col key={company._id} xs={12} md={6} lg={4} className="mt-2">
<CompanyCard
id={company._id}
logo={company.logo}
title={company.name}
logoBackground={company.logoBackground}
progLangs={company.progLangs}
backend={company.backend}
frontend={company.frontend}
url={company.url}
>
</CompanyCard>
</Col>
)
})
}
const CompanyList = (props) => {
const {companies} = props
return (
<div>
<Container className="mt-3">
<Row>
{
MapDataToCompanyList(companies)
}
</Row>
</Container>
</div>
)
}
export default CompanyList;
FetchSearchData function source code:
export const fetchSearchData = (query, cb)=>{
const uri = process.env.NODE_ENV === 'development' ?
`http://localhost:3000/api/companies/name/${query}` :
``;
axios.get(uri, {
timeout: MAX_TIMEOUT
})
.then((response)=>{
cb(response.data.data)
})
.catch((error)=>{
console.log(error)
})
}
As seen above, empty list of companies is logged when the page first loads, then I typed three characters and the it logged three time which means the map function called three times.
Even then if I pressed submit and retrieved list of companies normally, whenever I type it will keep printing the array of companies that was fetched.
Sorry if I missed something, I am still new to React.
When you call setSearchInput(e.target.value), Search component will re-render cause its state has changed. Search component re-renders means every tag nested in it will re-render (except the ones passed via children). That is the normal behaviour of React. If you want to avoid that, you would wanna use React.memo for CompanyList. Or you could use useRef to bind the input like so:
const Search = () => {
const [companies, setCompanies] = useState([]); //List of companies returned from searching
const inputRef = React.useRef(null);
//use the API providing it the search input, and
//setCompanies hook to update list of companies
const onSearchSubmit = (e) => {
e.preventDefault();
fetchSearchData(inputRef.current.value, setCompanies);
inputRef.current.value = "";
};
return (
<div>
<Container>
<Row className={"searchFilterBar"}>
<Col sm={6} md={8} className={"searchBar"}>
<SearchBar inputRef={inputRef} onSubmit={onSearchSubmit} />
</Col>
<Col sm={6} md={4} className={"filterBar"}></Col>
</Row>
<CompanyList companies={companies}></CompanyList>
<Row></Row>
</Container>
</div>
);
};
export default Search;
const SearchBar = ({ onSubmit, inputRef }) => {
return (
<Form className="search-form" onSubmit={onSubmit}>
<div className="input-group">
<span className="input-group-text rubik-font">
<i className="icon ion-search"></i>
</span>
<input
ref={inputRef}
className="form-control rubik-font"
type="text"
placeholder="Search for companies that start with..."
/>
<Button className="btn btn-light rubik-font" type="submit">
Search
</Button>
</div>
</Form>
);
};
I don't get why CompanyList re-renders.
Because it's nested in your Search component, and it's not React.memo'd (or a PureComponent).
Yes, the component is updated, but that doesn't mean it necessarily causes a DOM reconciliation.
In any case, React is completely at liberty of calling your component function as many times as it likes (and indeed, in Strict Mode it tends to call them twice per update to make sure you're not doing silly things), so you should look at side effects (such as console logging) in your component function (which you shouldn't have in the first place) as performance guidelines.
You do not need to maintain a state for input field. You can use useRef and pass it to input like below.
<input
ref={inputRef}
className="form-control rubik-font"
type="text"
placeholder="Search for companies that start with..."
/>
And you can get get value inside onSearchSubmit using inputRef.current.value
This will not re-render you component on input change.
getting this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
const MapBox = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [visible, setVisible] = useState(false)
const onClick = () => setVisible(true);
const closeHandler = () => {
setVisible(false);
}
useEffect(() => {
}, [visible])
return (
<>
<MapContainer ref={mapContainer} className="map-container" onClick={onClick}></MapContainer>
<ModalContainer visible={visible} closeHandler={closeHandler} />
</>
)
}
The modal container is just a simple modal pop, only returning a jsx component. That only shows if visible is true, and if runs closeHandler is a button is pressed.
const ModalContainer = ({ visible, closeHandler }) => {
useEffect(() => {}, [visible])
return (
<>
{visible && (
<div>
<Modal
closeButton
aria-labelledby="modal-title"
open={visible}
onClose={closeHandler}
>
<Modal.Header>
<Text id="modal-title" size={18}>
Welcome to
<Text b size={18}>
NextUI
</Text>
</Text>
</Modal.Header>
<Modal.Body>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Email"
/>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Password"
/>
<Row justify="space-between">
<Checkbox>
<Text size={14}>
Remember me
</Text>
</Checkbox>
<Text size={14}>
Forgot password?
</Text>
</Row>
</Modal.Body>
<Modal.Footer>
<Button auto flat color="error" onClick={closeHandler}>
Close
</Button>
<Button auto onClick={closeHandler}>
Sign in
</Button>
</Modal.Footer>
</Modal>
</div>)
}
</>
);
}
Any help would be much appreciated, all other people on here with this error message are running async code etc. This one is a lot simpler and the error only comes up the first time the click to close event happens.
I am trying to create a post request in this post request I am creating an app
after that, I want to create its liens so I need to get the id of the created items to create the lien so my question is there a method to get the id of the item created by the post request using Axios and react hooks I hope this image can explain something of what I want to do
I tryed this solution:
const [idapp, setIdapp] = useState();
const changeIdapp = (newFruit) => {
setIdapp(newFruit)
}
function CreateApplication() {
axios.post('http://localhost:8080/app/', {
applicationName: nameapp,
dateAjoute: dateajout
},{ headers: authHeader() })
.then((response) => {changeIdapp(response.data)
}, (error) => {
console.log(error);
});
}
(the post is only returning the id (LONG) )
and the code on the button create :
<Button
className="btn-fill pull-right"
type="submit"
variant="info"
onClick={CreateApplication}
>
Create App
</Button>
but the problem is that the application is created but it's id not saved,
if someone can help me with my problem I will be grateful.
I want to add that the app and the lien are in OneToMany relation
so one app contains many liens
so what exactly I want to do when creating the app I want to save it id to create many liens for that app.
Update :
this is all my code
import React, { useState } from "react";
import Select from 'react-select'
// react-bootstrap components
import {
Badge,
Button,
Card,
Form,
Navbar,
Nav,
Container,
Row,
Col,
} from "react-bootstrap";
import axios from 'axios';
import authHeader from "./services/auth-header";
import Moment from 'moment';
function User() {
var k;
const [currentSelect, setCurrentSelect] = useState();
const changeSelect = (newFruit) => {
setCurrentSelect(newFruit)
}
const [nameapp, setNameapp] = useState();
const changeNameapp = (newFruit) => {
setNameapp(newFruit)
}
const [dateajout, setDateajout] = useState('2021-06-25');
const changeDateajout = (newFruit) => {
setDateajout(newFruit)
}
console.log(dateajout);
const [idapp, setIdapp] = useState();
const changeIdapp = (newFruit) => {
setIdapp(newFruit)
}
console.log(idapp);
function CreateApplication() {
axios.post('http://localhost:8080/app/', {
applicationName: nameapp,
dateAjoute: dateajout
},{ headers: authHeader() })
.then((response) => {
setIdapp(response.data.idApplication);
console.log(response.data);
}, (error) => {
console.log(error);
});
}
console.log(dateajout);
console.log(idapp);
return (
<>
<Container fluid>
<Row>
<Col md="10">
<Card>
<Card.Header>
<Card.Title as="h4">creer Application</Card.Title>
</Card.Header>
<Card.Body style={{backgroundColor: '#F3AD7C'}}>
<Form>
<Row>
<Form.Group>
<label>Application name</label>
<Form.Control
placeholder="applicationName"
type="text"
onChange={(event) => changeNameapp(event.target.value)}
value={nameapp}
></Form.Control>
</Form.Group>
<Form.Group>
<label>Date d'ajoute</label>
<Form.Control
placeholder="date d'ajoute"
type="date"
format="YYYY-MM-DD"
onChange={(event) => changeDateajout(event.target.value)}
value={dateajout}
></Form.Control>
</Form.Group>
</Row>
<p></p>
<Button
className="btn-fill pull-right"
type="submit"
variant="info"
onClick={CreateApplication}
>
Create App
</Button>
<div className="clearfix"></div>
</Form>
</Card.Body>
</Card>
<p></p>
<Card>
<Card.Header>
<Card.Title as="h4">creer Liens</Card.Title>
</Card.Header>
<Card.Body style={{backgroundColor: '#F3AD7C'}}>
<Form>
<Row>
<Form.Group>
<label>URL</label>
<Form.Control
placeholder="URL du lien"
type="text"
></Form.Control>
</Form.Group>
<Form.Group>
<label>Type Test</label>
<p></p>
<select
onChange={(event) => changeSelect(event.target.value)}
value={currentSelect}
>
<option value="1">Repense</option>
<option value="2">Authantification</option>
<option value="3">Loop</option>
</select>
</Form.Group>
</Row>
<p></p>
<p></p>
<Button
className="btn-fill pull-right"
type="submit"
variant="info"
>
Create Lien
</Button>
<div className="clearfix"></div>
</Form>
</Card.Body>
</Card>
</Col>
<Col md="4">
</Col>
</Row>
</Container>
</>
);
console.log(dateajout);
}
export default User;
If response.data looks something like this:
{
id: 42
}
Then you can do
setIdapp(response.data.id);
If response.data is a string instead of a JavaScript object, then you need to use JSON.parse() on it first.
I find a solution to my problem
the problem is that the state is always refreshed because whenever I click on create app the page is refreshed so that why my id is never saved so the solution was to stop the page from refreshing when I click on create app
so the solution is modifying the function creatApplication of the post to
const CreateApplication = event => {
axios.post('http://localhost:8080/app/', {
applicationName: nameapp,
dateAjoute: dateajout
}, { headers: authHeader() })
.then((response) => {
setIdapp(response.data.idApplication);
setShowcreateApp(false);
console.log(response.data);
}, (error) => {
console.log(error);
});
event.preventDefault();
}
so now the page is not refreshed and I have the stat saved
Describe the bug
Hi everyone,
I'm trying to generate a PDF which includes some user-provided info. In particular, I want my user to choose the filename of the pdf which will be also the title of it.
In order to do this I'm using:
"#react-pdf/renderer": "^1.6.12" for the pdf rendering
"react-bootstrap": "^1.3.0" for the page layout
"formik": "^2.2.0" for form management
The problem I have consists in the fact that whenever the user starts typing the desired filename/title in the apposite input the page crashes with the following error:
Unhandled Rejection (Error): write after end
3 stack frames were expanded.
writeAfterEnd
node_modules/stream-browserify/node_modules/readable-stream/lib/_stream_writable.js:288
(anonymous function)
node_modules/stream-browserify/node_modules/readable-stream/lib/_stream_writable.js:332
addContent
node_modules/#react-pdf/pdfkit/dist/pdfkit.browser.es.js:4184
After deeper investigations, I noticed that the problem resides in the "dynamicity" of the generated pdf content.
The code is the following:
const DownloadModal = ({configuration}) => {
const [show, setShow] = useState(false);
const formik = useFormik({
initialValues: {
name: "",
}
});
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary"
onClick={() => {
handleShow()
}}
>
Download PDF <DownloadIcon/>
</Button>
<Modal size={"lg"} show={show} onHide={handleClose} animation={true}>
<Modal.Header closeButton>
<Modal.Title>Choose filename</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={(event) => {
event.preventDefault();
}}>
<Form.Group as={Row}>
<Form.Label column sm={2}>
Filename
</Form.Label>
<Col sm={10}>
<InputGroup className="mb-3">
<FormControl
type="text"
id={"name"}
name={"name"}
placeholder="filename"
onChange={formik.handleChange}
value={formik.values.name}
autoComplete={"off"}
/>
<InputGroup.Append>
<InputGroup.Text id="basic-addon2">.pdf</InputGroup.Text>
</InputGroup.Append>
</InputGroup>
</Col>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<DownloadPDFButton
data={configuration}
title={formik.values.name}
/>
</Modal.Footer>
</Modal>
</>
);
};
const DownloadPDFButton = ({data, title}) => {
return (
<PDFDownloadLink
document={
<PdfDocument
data={data}
title={{title}}
/>}
fileName={`${title}.pdf`}
className={"btn btn-link"}
>
{({blob, url, loading, error}) =>
loading ? <Spinner animation="border" variant="primary"/> : "Download"
}
</PDFDownloadLink>
)
}
I think that the problem resides here, in the way I display title.title. But I really cannot find a way to make it works.
const Title = ({title}) => {
return (
<View style={styles.titleContainer}>
<Text>{title.title}</Text>
</View>
)
}
;
const PdfDocument = (props) => {
const configurationData = {
plant: props.data.plant,
flow: props.data.flow,
optionals: props.data.optionals
};
const utilitiesData = {
total_installed_power: props.data.total_installed_power,
total_absorbed_power: props.data.total_absorbed_power,
makeup_water: props.data.makeup_water,
compressed_air: props.data.compressed_air
};
return (
<Document>
<Page key={"page-0"} style={styles.page}>
<Image style={styles.logo} src={logo}/>
<Title title={props.title}/>
<ConfigurationPDF configuration={configurationData}/>
<ResultsPDF results={utilitiesData}/>
</Page>
</Document>
);
};
Thanks to everyone in advance!!
Running environment:
OS: [MacOS]
Browser [chrome, safari]
I think your problem is solved now
I only convert formik state to react state
Codesandbox: https://codesandbox.io/s/reverent-browser-1bn4n
I've now gone through my code with a fine tooth comb and I just cannot seem to see where the "recipe" property is not defined. I'm hoping some more experienced eyes would help me out and spot where I've made the mistake. Any help will be appreciated. Thank you.
Ps. Please find my code below... it's the Recipe Box project from FreeCodeCamp and I followed the walk through from Dylan Israel from CodingTutorials360. As far as I can tell my code is identical to his except for some changes to React-Bootstrap as stipulated by the Documentation.
import React, { Component } from 'react';
import './App.css';
import Panel from 'react-bootstrap/lib/Panel'
import Button from 'react-bootstrap/lib/Button'
import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar'
import Modal from 'react-bootstrap/lib/Modal'
import FormGroup from 'react-bootstrap/lib/FormGroup'
import ControlLabel from 'react-bootstrap/lib/ControlLabel'
import FormControl from 'react-bootstrap/lib/FormControl'
import PanelGroup from 'react-bootstrap/lib/PanelGroup'
class App extends Component {
state = {
showAdd: false,
showEdit: false,
currentIndex: 0,
recipes: [
],
newestRecipe: {recipeName:"", ingredients: []}
}
deleteRecipe(index){
let recipes = this.state.recipes.slice();
recipes.splice(index, 1);
localStorage.setItem('recipes', JSON.stringify(recipes));
this.setState({recipes});
}
updateNewRecipe(value, ingredients){
this.setState({newestRecipe:{recipeName: value, ingredients: ingredients}});
}
close = () => {
if(this.state.showAdd){
this.setState({showAdd: false});
} else if(this.state.showEdit){
this.setState({showEdit: false});
}
}
open = (state, currentIndex) => {
this.setState({[state]: true});
this.setState({currentIndex});
}
saveNewRecipe = () => {
let recipes = this.state.recipes.slice();
recipes.push({recipeName: this.state.newestRecipe.recipeName, ingredients: this.state.newestRecipe.ingredients});
localStorage.setItem('recipes', JSON.stringify(recipes));
this.setState({ recipes });
this.setState({newestRecipe: {recipeName: '', ingredients:[]}});
this.close();
}
updateRecipeName(recipeName, currentIndex){
let recipes = this.state.recipes.slice();
recipes[currentIndex] = {recipeName: recipeName, ingredients: recipes[currentIndex].ingredients};
this.setState({recipes});
localStorage.setItem('recipes', JSON.stringify(recipes));
this.close();
}
updateIngredients(ingredients, currentIndex){
let recipes = this.state.recipes.slice();
recipes[currentIndex] = {recipeName: recipes[currentIndex].recipeName, ingredients: ingredients};
localStorage.setItem('recipes', JSON.stringify(recipes));
this.setState({recipes});
}
componentDidMount(){
let recipes = JSON.parse(localStorage.getItem("recipes")) || [];
this.setState({recipes});
}
render() {
const {recipes, newestRecipe, currentIndex} = this.state;
return (
<div className="App container" id="display-box">
{recipes.length > 0 && (
<div>
<PanelGroup accordion id="recipe-list" defaultActiveKey="2">
{recipes.map((recipe, index)=>(
<Panel eventKey={index} key={index}>
<Panel.Heading>
<Panel.Title toggle>{recipe.recipeName}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
<ol>
{recipe.ingredients.map((item)=>(
<li key={item}>{item}</li>
))}
</ol>
<ButtonToolbar>
<Button bsStyle="danger" onClick={(event)=>this.deleteRecipe(index)}>Delete Recipe</Button>
<Button bsStyle="default" onClick={(event) => this.open("showEdit", index)}>Edit Recipe</Button>
</ButtonToolbar>
</Panel.Body>
</Panel>
))}
</PanelGroup>
</div>
)}
<Modal show={this.state.showEdit} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Edit Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormGroup controlId="formBasicText">
<ControlLabel>Recipe Name</ControlLabel>
<FormControl
type="text"
value={recipes[currentIndex].recipeName}
placeholder="Enter Text" onChange={(event) => this.updateRecipeName(event.target.value, currentIndex)}
/>
</FormGroup>
<FormGroup controlId="formControlsTextarea">
<ControlLabel>Ingredients</ControlLabel>
<FormControl
componentClass="textarea"
onChange={(event) => this.updateIngredients(event.target.value.split(","), currentIndex)}
placeholder="Enter Ingredients [Seperate by Commas]"
value={recipes[currentIndex].ingredients}>
</FormControl>
</FormGroup>
<Modal.Footer>
<Button bsStyle="primary" onClick={(event) => this.saveNewRecipe()}>Save Changes</Button>
</Modal.Footer>
</Modal.Body>
</Modal>
<Modal show={this.state.showAdd} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Add Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormGroup controlId="formBasicText">
<ControlLabel>Recipe Name</ControlLabel>
<FormControl
type="text"
value={newestRecipe.recipeName}
placeholder="Enter Recipe Name"
onChange={(event) => this.updateNewRecipe(event.target.value, newestRecipe.ingredients)}
>
</FormControl>
</FormGroup>
<FormGroup controlId="formControlTextarea">
<ControlLabel>Ingredients</ControlLabel>
<FormControl
type="textarea"
placeholder="Enter Ingredients [Seperate by Commas]"
onChange={(event) => this.updateNewRecipe(newestRecipe.recipeName, event.target.value.split(','))}
value={newestRecipe.ingredients}
>
</FormControl>
</FormGroup>
</Modal.Body>
<Modal.Footer>
<Button onClick={(event) => {this.saveNewRecipe()}}>Save</Button>
</Modal.Footer>
</Modal>
<Button bsStyle="primary" onClick={(event)=>this.open("showAdd", currentIndex)}>Add Recipe</Button>
</div>
);
}
}
export default App;
The problem is that you are accessing (twice) to the current recipe data when there is not a current recipe. That is because, although you are not showing the "modal", you are rendering it.
One possible solution is to render that modal only if you are editing something, you could do this replacing lines 110-139 with this:
{ this.state.showEdit &&
<Modal show={this.state.showEdit} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Edit Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormGroup controlId="formBasicText">
<ControlLabel>Recipe Name</ControlLabel>
<FormControl
type="text"
value={recipes[currentIndex].recipeName}
placeholder="Enter Text" onChange={(event) => this.updateRecipeName(event.target.value, currentIndex)}
/>
</FormGroup>
<FormGroup controlId="formControlsTextarea">
<ControlLabel>Ingredients</ControlLabel>
<FormControl
componentClass="textarea"
onChange={(event) => this.updateIngredients(event.target.value.split(","), currentIndex)}
placeholder="Enter Ingredients [Seperate by Commas]"
value={recipes[currentIndex].ingredients}>
</FormControl>
</FormGroup>
<Modal.Footer>
<Button bsStyle="primary" onClick={(event) => this.saveNewRecipe()}>Save Changes</Button>
</Modal.Footer>
</Modal.Body>
</Modal>
}
Note that the only thing I did is to add the first and the last line of that block of code.
Correct me if I'm wrong, but it looks like you are not defining your initial state correctly, which may be the reason why you are getting an undefined error.
Usually, state is initially defined in the constructor function, and not as a separate object. When I tried to define the state like you did, I got an error on codepen.
Check out how they define state in this cheat sheet (under States): https://devhints.io/react.
I've gone through the same FreeCodeCamp react projects, and some of these can take a long time to figure out, but in the end it's really worth it. Hopefully this will help you find the error :)
You should use a constructor to define the state and bind your function saveNewRecipe
constructor(props) {
super(props);
state = {
showAdd: false,
showEdit: false,
currentIndex: 0,
recipes: [
],
newestRecipe: {recipeName:"", ingredients: []}
};
this.saveNewRecipe = this.saveNewRecipe.bind(this);
}