1

I've been stuck on this problem for quite some time. I am using a couple of different states to render values inside a card after they have been input into the form. The first state takes in the values and changes the initial state. the second state takes in the values after a mathematical function has been performed and renders them onto the card. My desired outcome is a fully reset state that allows for new values to be entered, and then subsequently having a new card rendered beneath the initial card all whilst maintaining the previous values that have already been input.

    import Navbar from '../../components/NavBar'
import { FormControl, InputLabel, Input, Button, FormHelperText } from '@mui/material'
import ExpenseCard from '../../components/ExpenseCard'
import axios from 'axios'
import { React, useState } from 'react'
import ReactDOM from 'react-dom'
import { Grid } from '@mui/material'
import { categoryResult, calcSumTotal } from '../../utils/CategoryResult'

const Budget = () => {
  const [ categoriesToAdd, setCategoriesToAdd] = useState(0);
  const [ committedCategoriesToAdd, setCommittedCategoriesToAdd] = useState(0);
  const [ renderCardsAdded, setRenderCardsAdded] = useState({
    category: '',
    actualValue: 0,
    goalValue: 0,
    result: 0
  });
  
  // by building out the states in the budget page we have negated the need to use an expenseContext and import it, removing confusion. doing this also allows us to compile the functions that we need to use to handle changes in the form.
  const handleAddExpense =  (category, actualValue, goalValue) => {
    let result = categoryResult(actualValue, goalValue);
    setRenderCardsAdded({...renderCardsAdded, category: category, actualValue, goalValue, result });
  }

  const renderTheCard = () => {
    document.getElementById('renderhere').innerText = 
    <ExpenseCard category={renderCardsAdded.category} actualValue={renderCardsAdded.actualValue} goalValue={renderCardsAdded.goalValue} result={renderCardsAdded.result} />
  }
  const handleInputChange = ({ target: { name, value } }) => setExpenseState({ ...expenseState, [name]: value })

  const [ expenseState, setExpenseState ] = useState({ 
    category: ' ',
    goalValue: 0,
    actualValue: 0,
  })


 /// this is basically the expenseform from the components folder but i circumvented the necessity to import it by building it out on the budget page.
  const CategoryForm = () => [
  

    <FormControl>
      <Grid container rowSpacing={1} columnSpacing={{ xs: 1 }}>
        <Grid item xs={2}>
          <Input name="category" aria-describedby="expense category" value={expenseState.category} onChange={handleInputChange}/>
          <FormHelperText id="my-helper-text-1">expense category</FormHelperText>
        </Grid>
        <Grid item xs={2}>
          <Input type="number" name="actualValue" aria-describedby="actual value" value={expenseState.actualValue} onChange={handleInputChange}/>
          <FormHelperText id="my-helper-text-2">actual expense</FormHelperText>
        </Grid>
        <Grid item xs={2}>
          <Input type="number" name="goalValue" aria-describedby="goal value" value={expenseState.goalValue} onChange={handleInputChange}/>
          <FormHelperText id="my-helper-text-3">goal expense</FormHelperText>
        </Grid>
        <Grid item xs={2}>
          <Button onClick={
            () => {
            handleAddExpense(expenseState.category, expenseState.actualValue, expenseState.goalValue)
            renderTheCard()}}>Add</Button>
        </Grid>
      </Grid>
    </FormControl>
  ]
  return (
    <>
      <Navbar />
      <hr />
      <CategoryForm />
      <div id="renderedCategories">
      {[...Array(committedCategoriesToAdd)].map((value: undefined, index: number) => (
        <CategoryForm id={index + 1} key={index} />))}
        </div> */}
      <h1>This is the Budget Page</h1>
      <div id="renderhere"></div>
    </>

  )
}

export default Budget

this code currently renders Object Object to the page with no information displayed outside of that. Further, another issue I've encountered is the input form is only allowing me to input 1 character at a time and to further input characters i have to refocus the input. Not sure what is causing this... using MUI styling, and react.

Claude M
  • 23
  • 6

3 Answers3

0

I didn't read/grasp the whole idea that you're trying to solve, but why are you creating many variables stored in states when you can just create one array of objects storing it in a useState, then when you want to add items, you can just keep older items (objects) using spread operator, and add then add new ones. Example:

const [inputs, setInputs] = useState([ {key1: val1}, {key2: val2} ])

Then upon each time you want to modify/add objects or items, do it this way:

setInputs([... Inputs, newObj])

Where newObj is the new object you want to add (pre-defined before setting state)

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 01 '22 at 04:19
0

I've been using react this past year--still learning. Following this post to ensure I'm clear on the issue.

It sounds like you would like a full page refresh onClick(). If so, then we can scratch the need for useEffect(). Though I'm not sure why that would be the case.

Regarding the form input: I would start by looking at the handleChangeInput(). A recent project I put together was using controlled forms courtesy of react.js. I took this snippet from a component containing one of the project's handful of forms.

function handleOnChange(e) {
        setFormData({...formData, e.target.name]:e.target.value})
    }

Here we pass the typing event "e" into the handleOnChange() which triggers setFormData() who then uses the event to set the state. It appears you've attempted something more like this response/answer. Either way - best of luck!

0

I've been able to render a new card for each instance of the changed state by adding an empty array to the expenseState and pushing the changes to that array. Once the array has the changes pushed, we map over said array and create a card for each instance of an expense, displaying the respective category, actualValue, and goalValue, like so;

 const handleAddExpense =  (category, actualValue, goalValue) => {
const expenses = JSON.parse(JSON.stringify(expenseState.expenses))
let result = categoryResult(actualValue, goalValue);
expenses.push({ category, goalValue, actualValue, result })
setExpenseState({ ...expenseState, result, expenses });

}

This handles the changes when clicking add expense by updating the expenseState. the following code handles the card rendering for each expense that is pushed into the new array.

{
    expenseState.expenses.map(expense => (
      <ExpenseCard category={expense.category} goalValue={expense.goalValue} actualValue={expense.actualValue} result={expense.result}/>
    )
    )
  }
Claude M
  • 23
  • 6