Search code examples
reactjsarraysmongodbformscrud

Array saves each letter I type instead of the whole text, React


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



Solution

  • ...state.ingredients this code causes the previous data to be copied.

    case ACTIONS.SET_INGREDIENTS:
        return {
            ...state,
            ingredients: [action.payload]
        }