I have a simple Expense Tracker with a form to add transactions to a mongoDB back-end. I'm using the context API and material UI form sub components. I cannot for the life of me figure out how to clear the text fields after onSubmit is fired, but I'm very new to React and Javascript in general. I've seen some other posts about creating a function to fire as onClick with the button, but I'm not sure this is the right route since I have an "initialState" in my Global Context. I've also seen other routes that used the appReducer to do this, but I couldn't figure out how to apply that. To be clear, I'm wanting this to clear the form onSubmit, not a button to click "clear fields". Below is my code:
Form Component
import React, { useState, useContext } from "react";
import { GlobalContext } from "../context/GlobalState";
// UI for Text Field
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
// UI and utils for date picker
import "date-fns";
import DateFnsUtils from "@date-io/date-fns";
import {
MuiPickersUtilsProvider,
KeyboardDatePicker
} from "@material-ui/pickers";
const useStyles = makeStyles(theme => ({
root: {
"& > *": {
align: "center",
margin: theme.spacing(1),
width: 200,
flexgrow: 1
}
},
textfield: {
height: 38
},
button: {
height: 38,
align: "center"
},
grid: {
fullwidth: true,
direction: "row",
justify: "center",
alignItems: "center",
display: "flex",
flexDirection: "column",
justifyContent: "center"
}
}));
export const AddTransaction = () => {
const classes = useStyles();
const [transactionDate, setTransactionDate] = useState(new Date());
const [text, setText] = useState('');
const [amount, setAmount] = useState(0);
const { addTransaction } = useContext(GlobalContext);
const onSubmit = e => {
e.preventDefault();
const newTransaction = {
transactionDate,
text,
amount: +amount
};
addTransaction(newTransaction);
};
return (
<React.Fragment>
<h3 align="center">Add new transaction</h3>
<Grid container className={classes.grid}>
<form
className={classes.root}
noValidate
autoComplete="off"
onSubmit={onSubmit}
>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
variant="inline"
format="MM/dd/yyyy"
margin="normal"
id="Transaction Date"
label="Transaction Date"
onChange={e => setTransactionDate(e)}
value={transactionDate}
KeyboardButtonProps={{
"aria-label": "change date"
}}
/>
</MuiPickersUtilsProvider>
<TextField
className={classes.textfield}
id="Transaction Name"
label="Transaction Name"
variant="outlined"
size="small"
type="text"
margin="dense"
onChange={e => setText(e.target.value)}
value={text}
required = {true}
/>
<TextField
className={classes.textfield}
id="Amount"
label="Amount"
variant="outlined"
size="small"
type="number"
margin="dense"
onChange={e => setAmount(e.target.value)}
value={amount}
required = {true}
/>
<Button
className={classes.button}
variant="contained"
color="primary"
type="submit"
fullwidth
>
Add transaction
</Button>
</form>
</Grid>
</React.Fragment>
);
};
Global State
import React, { createContext, useReducer } from 'react';
import AppReducer from './AppReducer';
import axios from 'axios';
// Initial state
const initialState = {
transactions: [],
error: null,
loading: true
}
// Create context
export const GlobalContext = createContext(initialState);
// Provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
// Actions
async function getTransactions() {
try {
const res = await axios.get('/api/v1/transactions');
dispatch({
type: 'GET_TRANSACTIONS',
payload: res.data.data
});
} catch (err) {
dispatch({
type: 'TRANSACTION_ERROR',
payload: err.response.data.error
});
}
}
async function deleteTransaction(id) {
try {
await axios.delete(`/api/v1/transactions/${id}`);
dispatch({
type: 'DELETE_TRANSACTION',
payload: id
});
} catch (err) {
dispatch({
type: 'TRANSACTION_ERROR',
payload: err.response.data.error
});
}
}
async function addTransaction(transaction) {
const config = {
headers: {
'Content-Type': 'application/json'
}
}
try {
const res = await axios.post('/api/v1/transactions', transaction, config);
dispatch({
type: 'ADD_TRANSACTION',
payload: res.data.data
});
} catch (err) {
dispatch({
type: 'TRANSACTION_ERROR',
payload: err.response.data.error
});
}
}
return (<GlobalContext.Provider value={{
transactions: state.transactions,
error: state.error,
loading: state.loading,
getTransactions,
deleteTransaction,
addTransaction
}}>
{children}
</GlobalContext.Provider>);
}
App Reducer
export default (state, action) => {
switch(action.type) {
case 'GET_TRANSACTIONS':
return {
...state,
loading: false,
transactions: action.payload
}
case 'DELETE_TRANSACTION':
return {
...state,
transactions: state.transactions.filter(transaction => transaction._id !== action.payload)
}
case 'ADD_TRANSACTION':
return {
...state,
transactions: [...state.transactions, action.payload],
}
case 'TRANSACTION_ERROR':
return {
...state,
error: action.payload
}
default:
return state;
}
}
I was finally able to solve this with help from my brother. He suggested it wasn't ideal to manage the form's private state separate from my Global State, but this got the job done. I reset the form state following the addTransaction call.
Form Component
//No changes to import or styling //
export const AddTransaction = () => {
const classes = useStyles();
const [transactionDate, setTransactionDate] = useState(new Date());
const [text, setText] = useState('');
const [amount, setAmount] = useState(0);
const { addTransaction } = useContext(GlobalContext);
const onSubmit = e => {
e.preventDefault();
const newTransaction = {
transactionDate,
text,
amount: +amount
};
addTransaction(newTransaction);
// Here is what I've added that will return the form to it's original state
setText('') //this resets the textfield to an empty string
setAmount(0) //same as above but 0 amount
};
//no changes to return//