1

So I need to split a credit card expiration date into two separates values in my formData.

Here is how I have the component setup.

const [ formData, setFormData ] = useState({
email: '',
password: '',
username: '',
card_num: '',
cvv_code: '',
cc_exp: '',
cc_exp_year: '',
cc_exp_month,
first_name: '',
last_name: '',
    })

I also desctructure all the formData here:

const { email, password, username, card_num, cvv_code, cc_exp, cc_exp_year, cc_exp_month, first_name, last_name } = formData;

I have my onChange function here:

const onChange = e =>  {
setFormData({ ...formData, [e.target.name]: e.target.value })
  }

a

and when I type in the expiration date for cc_exp I get the correct format of MM/YY but I need to split the value into cc_exp_month and cc_exp_year. So my question is, where is the best place to split the string at the / and then how can I add them to the those values in formData.

I have tried setFormData in the onSubmit function to just add the values after the fact with a simple:

const onSubmit = e => {
        e.preventDefault();
        setFormData(formData => ({
            ...formData,  // shallow copy previous state
            cc_exp_month: cc_exp.split('/')[0], // add new property values
            cc_exp_year: cc_exp.split('/')[1],
            username: email
          }));
        dispatch(createMembership(formData, id));
    }

I thought that the best place to do this would be onSubmit before I sent the data anyway. Just so I wouldn't have to chain functions together and because I really just wasnt sure where to add it. So After cc_exp is set and all the form data is there I am trying to split that value and then apply it to these other values cc_exp_month & cc_exp_year before I submitted the data. But when I click onSubmit the month and year dont show. But if I was to click the submit twice (which no one would ever do) then the data shows up correctly in formData. So its like setFormData doesnt want to add the new exp data for some reason in onSubmit.

Kelley Muro
  • 41
  • 1
  • 6

2 Answers2

1

When you enqueue multiple updates like

setFormData({...formData,  cc_exp_month : cc_exp.split('/')[0] })
setFormData({...formData,  cc_exp_year : cc_exp.split('/')[1] })

Each one uses the same un-updated formData value and overwrites the previous enqueued updates. You should use a functional state update when updating from previous state. You can also combine all the updates into a single update if no individual update update requires previous temp updates. Use array destructuring assignment to create the cc_exp_month and cc_exp_year variables and then use object shorthand assignment in the returned state value.

const [cc_exp_month, cc_exp_year] = cc_exp.split('/');

setFormData(formData => ({
  ...formData,  // shallow copy previous state
  cc_exp_month, // add new property values
  cc_exp_year,
}));

Update

React state updates are asynchronously processed, so splitting the CC expiration and enqueueing a state update won't update the formData immediately. See this answer with explanation.

const onSubmit = e => {
  e.preventDefault();
  setFormData(formData => ({ // <-- returns state for next render cycle
    ...formData, 
    cc_exp_month: cc_exp.split('/')[0],
    cc_exp_year: cc_exp.split('/')[1],
    username: email
  }));
  dispatch(createMembership(formData, id)); // <-- state from this render cycle
}

You've a couple options:

  1. Use an useEffect hook to update the cc_exp_month and cc_exp_year properties specifically when the formData.cc_exp property updates.

    useEffect(() => {
      setFormData(formData => {
        const [cc_exp_month, cc_exp_year] = formData.cc_exp.split('/');
        return {
          ...formData,
          cc_exp_month,
          cc_exp_year,
        }
      });
    }, [formData.cc_exp]);
    

    Use caution with this method as it is generally to be avoided updating anything within an useEffect callback that is potentially in the effect's dependency array. Don't update the formData.cc_exp value as this will create a render loop!!

  2. Compute the form data object that you want to submit in the submit handler and don't bother with storing what is essentially duplicate data in state since there's already the formData.cc_exp state.

    const onSubmit = e => {
      e.preventDefault();
    
      const [cc_exp_month, cc_exp_year] = formData.cc_exp.split('/');
    
      const data = {
        ...formData,
        cc_exp_month,
        cc_exp_year,
      };
    
      dispatch(createMembership(data, id));
    }
    

    In my opinion this second option would be the preferred method as it has less moving parts and avoids the extraneous state updates and rerenders. The cc_exp_month and cc_exp_year are easily derived from state, and derived state doesn't belong in state.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • This solution only works on the second onSubmit click. For some reason its not setting the new values on the first click. Any insight as to why that would happen? – Kelley Muro Dec 02 '21 at 17:27
  • @KelleyMuro Why are you clicking submit twice? That doesn't make any sense. There is also nothing about submitting anything in your question so it is anyone's guess as to what submitting is doing. Is submitting relevant to your question of splitting the CC expiration string? If so then please update question to include all relevant details, if it is not then check if an answer here resolves current question and post a new question regarding any form submission. (*if you do this then feel free to ping (@ me) here with a link to the new post*) – Drew Reese Dec 02 '21 at 18:11
  • Where could I set form form data after cc_exp info has been set if not in onSubmit? – Kelley Muro Dec 02 '21 at 18:32
  • @KelleyMuro Is the code above in a submit handler? Can you clarify when and where you are trying to enqueue state updates so we've a much clearer idea what the code is trying to do? You can edit your question with new relevant details. – Drew Reese Dec 02 '21 at 18:51
  • @KelleyMuro Ok, thanks, I understand better what you are wanting to do. Please check updated answer. – Drew Reese Dec 02 '21 at 20:19
0

The newly-updated value of formData cannot be counted on by the second call to setFormData because the hook is asynchronous. You may want to try calling setFormData() once with both new values.

setFormData({
  ...formData,
  cc_exp_month: cc_exp.split('/')[0],
  cc_exp_year: cc_exp.split('/')[1]
})
Ed Lucas
  • 5,955
  • 4
  • 30
  • 42
  • This works on the second click of onSubmit. For some reason when I go to submit it the first time the state isnt set with the split values. but if I click it again it will work. I'm not sure why. – Kelley Muro Dec 02 '21 at 17:22