Good day guys , I was given an assignment to create a fullstack web app , where you can create meals , provide ingredients used , a description of the preparation method and the time it would take to cook it. Im using Mongodb , nodejs, express and React . My problem is with the ingredients . I set it as an array in my model. It works perfectly when i use postman to text my backend.
But when i fill my form it saves the each letter Iform to create meal, details page type.
For example :
If i were to type "salt" it would save this [s,sa,sal,salt]
my Form and my meals components are in one component called main
main.jsx
import React, { createContext, useEffect, useState } from 'react'
import { Route, Routes, useNavigate } from 'react-router-dom'
import Form from '../components/Form'
import Meals from '../components/Meals'
import axios from 'axios'
import Details from '../components/Details'
// create object for your initial state values
const initialState = {
name : "",
minutes : "",
directions : "",
ingredients : ""
}
export const myContext = createContext()
const Main = () => {
const [errors , setErrors] = useState([])
const [meals , setMeals] = useState([])
const [loaded , setLoaded] = useState(false)
const navigate = useNavigate()
// get errors from the backend and store in state
// get all meals
useEffect(() =>{
axios.get("http://localhost:8000/api/speedyMeals")
.then(res => {
console.log(res.data),
setMeals(res.data),
console.log(meals)
setLoaded(true)
})
.catch(err => console.log(err))
},[])
// create a meal
const createMeal = (mealObject) =>{
axios.post("http://localhost:8000/api/speedyMeals" , mealObject)
.then(res => {
console.log(res.data)
// add it to the array of existing meals , if not it will wipe the meals that where first in the state
setMeals([...meals , res.data])
navigate("/meals")
})
.catch(error => {
console.log(error)
setErrors(error.response.data.errors)
})
}
console.log(errors)
const removeFromDom = (authorId) =>{
setMeals(meals.filter(meal => meal._id != authorId))
}
return (
<>
<Routes>
<Route path = "/meals/new" element={<Form onSubmitProps = {createMeal} errors={errors} initialState = {initialState}/>}/>
<Route path = "/meals" element = { <myContext.Provider value={meals.length}><Meals loaded = {loaded} meals = {meals} setErrors = {setErrors} errors={errors}/></myContext.Provider>}/>
<Route path = "/meals/:id/details" element = {<Details removeFromDom={removeFromDom}/>}/>
</Routes>
</>
)
}
export default Main
FORM COMPONENT
import React, { useReducer, useState } from 'react'
import { Paper, FormControl, Button, OutlinedInput, InputLabel, TextField } from '@mui/material'
import { Link, useNavigate } from 'react-router-dom'
import { Styles } from './Meals'
// Styles =======================================!!!!!!!!!!!!!!!!!!!!!!!!!
const FormStyle = {
container: {
display: "flex",
gap: "1rem"
},
inputDiv: {
display: "flex",
flexDirection: "column",
gap: "1rem"
},
input: {
width: "20rem",
marginBottom: "2rem",
},
paper: {
padding: "2rem"
},
speedy: {
width: "55rem",
// border: "1px solid black",
display: "flex",
justifyContent: "space-between",
fontStyle: "italic",
alignItems: "center"
},
quote: {
width: "55rem",
marginBottom: "1rem",
display: "flex",
// border: "1px solid black",
justifyContent: "flex-start",
fontStyle: "italic",
// fontSize : "1.5rem"
}
}
// my action object to avoid hardcoding my actions.type
const ACTIONS = {
SET_NAME: "setName",
SET_MINUTES: "setMinutes",
SET_DIRECTIONS: "setDirections",
SET_INGREDIENTS: "setIngredients"
}
// reducer function =========================================================
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.SET_NAME:
return {
...state,
name: action.payload
}
case ACTIONS.SET_MINUTES:
return {
...state,
minutes: action.payload
}
case ACTIONS.SET_DIRECTIONS:
return {
...state,
directions: action.payload
}
case ACTIONS.SET_INGREDIENTS:
return {
...state,
ingredients: [...state.ingredients, action.payload]
}
default:
return state
}
}
const Form = (props) => {
const { initialState, onSubmitProps, errors } = props;
console.log(initialState)
const [state, dispatch] = useReducer(reducer, initialState);
const navigate = useNavigate();
// functions to handle setting the values ===============================+>
// handle set name
const handleSetName = (e) => {
dispatch(
{
type: ACTIONS.SET_NAME,
payload: e.target.value
}
)
}
// handle set minutes
const handleSetMinutes = (e) => {
dispatch(
{
type: ACTIONS.SET_MINUTES,
payload: e.target.value
}
)
}
// handle set directions
const handleSetDirections = (e) => {
dispatch(
{
type: ACTIONS.SET_DIRECTIONS,
payload: e.target.value
}
)
}
// handle set ingredients
const handleIngredients = (e) => {
console.log(e.target.value)
dispatch(
{
type: ACTIONS.SET_INGREDIENTS,
payload: e.target.value
}
)
}
const handleSubmit = (e) => {
e.preventDefault()
onSubmitProps(state)
}
// ==========================================================================================>
return (
<div style={Styles.main}>
<div style={FormStyle.speedy}>
<h1 >Speedy Meals</h1>
<Link to={"/meals"} >back to home</Link>
</div>
<div style={FormStyle.quote}>
<p>Add the next culinary masterpiece : </p>
</div>
<Paper elevation={3} style={FormStyle.paper}>
<form onSubmit={handleSubmit}>
{/* name */}
<div style={FormStyle.container}>
<div style={FormStyle.inputDiv}>
<FormControl>
<InputLabel>Dish name</InputLabel>
<OutlinedInput type="text" value={state.name} onChange={(e) => handleSetName(e)} style={FormStyle.input} />
{errors.name ? <small style={{ color: "red" }}>{errors.name.message}</small> : null}
</FormControl>
{/* minutes */}
<FormControl>
<InputLabel>Total Minutes</InputLabel>
<OutlinedInput type="number" value={state.minutes} onChange={(e) => handleSetMinutes(e)} style={FormStyle.input} />
{errors.minutes ? <small style={{ color: "red" }}>{errors.minutes.message}</small> : null}
</FormControl>
{/* Directions */}
<FormControl>
<InputLabel>Directions </InputLabel>
<OutlinedInput type="text" value={state.directions} onChange={(e) => handleSetDirections(e)} style={{
width: "20rem",
marginBottom: "2rem"
}} />
{errors.directions ? <small style={{ color: "red" }}>{errors.directions.message}</small> : null}
</FormControl>
</div>
{/* ingredient */} *THIS IS WHERE MY PROBLEM IS*
<div style={FormStyle.inputDiv}>
<p>Ingredient(s) - Optional</p>
<FormControl>
<InputLabel>Ingredient One</InputLabel>
<OutlinedInput value={state.ingredients} onChange={(e) => handleIngredients(e)} style={FormStyle.input} />
</FormControl>
<FormControl>
<InputLabel>Ingredient Two</InputLabel>
<OutlinedInput onChange={(e) => handleIngredients(e)} style={FormStyle.input} />
</FormControl>
<FormControl>
<InputLabel>Ingredient Three</InputLabel>
<OutlinedInput onChange={(e) => handleIngredients(e)} style={{width: "20rem",marginBottom: "2rem" }} />
</FormControl>
</div>
</div>
<div style={{ display: "flex", justifyContent: "flex-start" }}>
<Button type="submit" variant="contained" style={{ width: "13rem" }}>Create</Button>
</div>
</form>
</Paper>
</div>
)
}
export default Form
...state.ingredients
this code causes the previous data to be copied.
case ACTIONS.SET_INGREDIENTS:
return {
...state,
ingredients: [action.payload]
}