I am working on creating a clear button that once clicked will clear all the transactions that have been added to the transaction list with localStorage. My button works but its buggy, once it gets clicked I get the following error about a separate function I have to get the balance. If I refresh the page afterwords though all the transactions will be cleared.
The error I am receiving ...
TypeError: amounts.reduce(...).toFixed is not a function
my component
import react, {useState, useEffect} from 'react'
import Transaction from './Transaction'
const Form = () => {
//initial state
const [transaction, setTransaction] = useState({
description: '',
amount: ''
})
const [list, setList] = useState(
JSON.parse(localStorage.getItem('list')) || []
)
const [balance, setBalance] = useState('')
const [income, setIncome] = useState(
JSON.parse(localStorage.getItem('income'))
)
const [expense, setExpense] = useState(JSON.parse(localStorage.getItem('expense')))
//updates based onChange value
const updateBalance = (e) => {
setTransaction({
...transaction,
[e.target.name]:
e.target.type == 'number' ? parseInt(e.target.value) : e.target.value
})
}
//identify if transaction is income/expense
const plusMinus = () => {
transaction.amount > 0
? setIncome(income + transaction.amount)
: setExpense(expense + transaction.amount)
}
// updates balance after transaction is added
const getBalance = () => {
const amounts = list.map(i => i.amount);
const money = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2);
setBalance(money)
}
useEffect(() => {
getBalance()
localStorage.setItem('list', JSON.stringify(list))
localStorage.setItem('income', JSON.stringify(income))
localStorage.setItem('expense', JSON.stringify(expense))
}, [list])
//clear transaction list
const clearBudget = () => {
localStorage.clear();
}
const onSubmit = e => {
e.preventDefault();
setList([transaction, ...list])
plusMinus()
setTransaction({ description: '', amount: ''})
}
return (
<div>
<div className='totals'>
<h2 className='balance'> Current Balance </h2>
<h3> ${balance} </h3>
<h4> Income: ${income} Expense: ${expense} </h4>
</div>
< br />
< br />
< br />
<h2 className='trans-history'> Transaction History </h2>
{list.map(i => {
return (
<div className='trans'>
<ul key={i.description}>
{i.description} ${parseInt(i.amount)}
</ul>
</div>
)
})}
<br />
<br />
<h2 className='enter-item'> Enter an Item </h2>
<form onSubmit={onSubmit}>
<div>
<input
type='text'
className="input-trans"
placeholder='Enter Transaction'
value={Transaction.description}
name='description'
onChange={updateBalance}
>
</input>
</div>
<div>
<input
type='number'
className='input-trans'
placeholder='Enter Amount'
name='amount'
value={transaction.amount}
onChange={updateBalance}
>
</input>
</div>
<br/>
<div className='button-container'>
<button type='submit' className='button is-primary'> Submit </button>
<button className='button is-danger' onClick={clearBudget}> Clear </button>
</div>
</form>
</div>
)
}
export default Form
Looks like amounts.reduce is returning something that is not a number. You could check the type before to perform toFixed function.
E.g.:
const amounts = list.map((i) => i.amount).map(Number);
const money = amounts.reduce((acc, item) => (acc += item), 0)
if (typeof money === 'number') {
setBalance(money.toFixed(2))
} else {
setBalance(money)
}
Related
I am using handleChange event to set the task using setTask()
then I am using handleSubmit event to push that same task into an array
when I console log the task and the array length they are both correct
so I know I am modifying the array , but its still not mapping correct in my Unordered List element :
import React, { useState } from 'react';
const App = () => {
const [task, setTask] = useState('');
const myTaskList = [];
const allTasks = myTaskList.map((eachTask) => {
return <li key={eachTask.id}>{eachTask.text}</li>;
});
const handleChange = (e) => {
setTask(e.target.value);
console.log(task);
return task;
};
const handleSubmit = (e) => {
myTaskList.push({
id: Math.floor(Math.random() * 10000),
text: task,
});
setTask('');
console.log(myTaskList.length);
};
return (
<div className="todo-app">
<center>
<h1>MONO To Do APP</h1>
<p>Track your progress , add tasks, then mark complete when done</p>
<div className="space"></div>
<form onSubmit={handleSubmit}>
<label>Enter your next task:</label>
<br />
<input
onChange={handleChange}
value={task}
type="text"
name="task"
placeholder="enter a task to track your progress..."
/>
<br />
<button type="submit">Add</button>
</form>
<div className="space"></div>
<div className="task-list">
<h2>Your Active Tasks List</h2>
<ul>{allTasks.length > 0 ? allTasks : <li>no tasks found</li>}</ul>
</div>
</center>
</div>
);
};
export default App;
You should put myTaskList in a state to make the UI update when it changes.
const [myTaskList, setMyTaskList] = useState([])
const handleSubmit = (e) => {
setMyTaskList(prev => {
const myTaskList = [...prev]
myTaskList.push({
id: Math.floor(Math.random() * 10000),
text: task,
});
return myTaskList
})
setTask('');
};
You need to create a state using useState so that when you update the state then React will re-render the component
const [myTaskList, setMyTaskList] = useState( [] );
CODESANDBOX LINK
Since you are using form here so you also have to preventDefault
const handleSubmit = ( e ) => {
e.preventDefault(); // CHANGE
setMyTaskList( ( oldTask ) => { // CHANGE
return [
...oldTask,
{
id: Math.floor( Math.random() * 10000 ),
text: task,
}
];
} );
setTask( '' );
console.log( myTaskList.length );
};
You can also do as:
const handleSubmit = (e) => {
e.preventDefault();
setMyTaskList([ // CHANGE
...myTaskList,
{
id: Math.floor(Math.random() * 10000),
text: task
}
]);
setTask("");
console.log(myTaskList.length);
};
Several issues in your code.
you did not use useState in your taskList
You did not call preventDefault()
Heres mine:
import React, { useState } from 'react';
const App = () => {
const [task, setTask] = useState('');
const [myTaskList, setMyTaskList] = useState([])
// const allTasks = myTaskList.map((eachTask) => {
// return <li key={eachTask.id}>{eachTask.text}</li>;
// });
const handleChange = (e) => {
setTask(e.target.value);
console.log(task);
return task;
};
const handleSubmit = (e) => {
e.preventDefault()
// setMyTaskList.push({
// id: Math.floor(Math.random() * 10000),
// text: task,
// });
setMyTaskList(arry=>[...arry, {
id: Math.floor(Math.random() * 10000),
text: task,
}])
setTask('');
console.log(myTaskList.length);
};
return (
<div className="todo-app">
<center>
<h1>MONO To Do APP</h1>
<p>Track your progress , add tasks, then mark complete when done</p>
<div className="space"></div>
<form onSubmit={handleSubmit}>
<label>Enter your next task:</label>
<br />
<input
onChange={handleChange}
value={task}
type="text"
name="task"
placeholder="enter a task to track your progress..."
/>
<br />
<button type="submit">Add</button>
</form>
<div className="space"></div>
<div className="task-list">
<h2>Your Active Tasks List</h2>
<ul>{myTaskList.length > 0 ? myTaskList.map((eachTask) => {
return <li key={eachTask.id}>{eachTask.text}</li>;
}) : <li>no tasks found</li>}</ul>
</div>
</center>
</div>
);
};
export default App;
I was just testing around building a dummy todo list and was trying to figure out something. While setting the new state with the new task object that includes an id and a text. Well everything works well just my issue when I console.log(allTasks) it starts only to show the array of data after I have added the second task ?
const SearchInput = () => {
const [taskValue, setTaskValue] = useState("");
const [allTasks, setAllTasks] = useState([]);
const handleChange = (e) => {
setTaskValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (taskValue !== "") {
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
]);
}
setTaskValue("");
console.log(allTasks);
};
return (
<>
<Form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a task..."
value={taskValue}
onChange={handleChange}
/>
<button>Submit the Task</button>
</Form>
<div>
{allTasks.length <= 0 ? (
<p>No tasks</p>
) : (
<ul>
{allTasks.map((task) => (
<li key={task.id}> {task.text} </li>
))}
</ul>
)}
</div>
</>
);
};
Here you get updated value and there is a conditional change I hope you will like.
Thanks
const SearchInput = () => {
const [taskValue, setTaskValue] = React.useState("");
const [allTasks, setAllTasks] = React.useState([]);
const handleChange = (e) => {
setTaskValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (taskValue !== "") {
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
]);
}
setTaskValue("");
};
console.log(allTasks);
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a task..."
value={taskValue}
onChange={handleChange}
/>
<button>Submit the Task</button>
</form>
<div>
{!allTasks.length && <p>No tasks</p>}
{!!allTasks.length &&
<ul>
{allTasks.map((task) => (
<li key={task.id}> {task.text} </li>
))}
</ul>
}
</div>
</>
);
};
According to the docs, setState is async in nature, which means it will execute only after execution of all synchronous code. And setState takes a callback as the second parameter which you can use to log it as expected.
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
], ()=>{
console.log(alltasks)
});
Reference
Hi i am working on a React application where there are four inputs.when a user add an input the element will be added to the wrapper.In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can achieve removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders
So when I refresh the page and click to remove an input it will clear all other input data. How can I fix this problem ?
Update added full component to question:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rule_0,
rule_1: teamData.rule_1,
rule_2: teamData.rule_2,
rule_3: teamData.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(teamData.rules);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule-${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
console.log(updateTeamData);
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
<p className="team-agreement-rules-description">{description}</p>
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={`${data}-${idx}`}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
handleRemoveClick(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
type="text"
placeholder={`Rule ${idx + 1}`}
defaultValue={teamData.rules[idx]}
name={`rule_${idx}`}
onChange={handleChange}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
I am trying to create a CRUD using React Hooks but I am having some problems on the updateItem function.
The first input using useState works perfectly (I can type inside the input) but when I click Rename Item, the second input appears but I can’t type inside and doesn’t log any errors from console.
Here is my code:
import ReactDOM from 'react-dom';
import React, { useState } from 'react';
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: 'Test' }]);
//Inputs
const [input, setInput] = useState('');
const [newInput, setNewInput] = useState('');
const [edit, setEdit] = useState();
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value }
setList([...list, obj])
}
function deleteItem(id) {
setList(list.filter(item => item.id !== id));
}
function updateItem(id) {
setEdit(
<div>
//I can't type anything in here
<input type="text" value={newInput} onChange={e => setNewInput(e.target.value)} />
<button onClick={() => {
let array = [...list];
array.map((item, i) => {
if (item.id === id) array[i] = { id, newInput }
})
setList([...array])
setEdit('') //Remove the edit from the DOM
}}>Rename</button>
</div>
)
}
return (
<div>
<input type="text" value={input} onChange={e => setInput(e.target.value)} />
<button onClick={() => createItem(input)}>Add Item</button>
{list.map(item => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => updateItem(item.id)}>Rename Item</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
</div>
))}
{edit}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Try to use setEdit to show/hide through boolean value. You will have ability to type in the input
import ReactDOM from "react-dom";
import React, { useState } from "react";
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: "Test" }]);
//Inputs
const [input, setInput] = useState("");
const [newInput, setNewInput] = useState("");
const [edit, setEdit] = useState(false);
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value };
setList([...list, obj]);
}
function deleteItem(id) {
setList(list.filter(item => item.id !== id));
}
function updateItem() {
setEdit(true);
}
return (
<div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={() => createItem(input)}>Add Item</button>
{list.map(item => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => updateItem(item.id)}>Rename Item</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
{edit && (
<div>
//I can't type anything in here
<input
type="text"
value={newInput}
onChange={e => {
setNewInput(e.target.value);
}}
/>
<button
onClick={() => {
let array = [...list];
array.map((o, i) => {
if (o.id === item.id) array[i] = { id: item.id, newInput };
});
setList([...array]);
setEdit(false); //Remove the edit from the DOM
}}
>
Rename
</button>
</div>
)}
</div>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
The problem is with how you run updateItem() function.
You are using the following call <button onClick={() => updateItem(item.id)}>Rename Item</button>
In updateItem(), you do a setEdit and it adds an input box and button to your screen.
But now, you have trouble updating the input box. This is because the input box will only be updated when you run the updateItem() function again.
So in order for your input box to be updated, you would have to run setEdit again
Here is a solution of how to put it in your main component so that the input box updates with each render
import ReactDOM from "react-dom";
import React, { useState } from "react";
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: "Test" }]);
//Inputs
const [input2, setInput2] = useState("");
const [newInput, setNewInput] = useState("");
const [edit, setEdit] = useState(null);
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value };
setList([...list, obj]);
}
function deleteItem(id) {
setList(list.filter((item) => item.id !== id));
}
function renameItem() {
// Rename item
alert("Rename item " + edit + " to " + newInput);
setEdit(null);
setNewInput(null);
}
return (
<div>
<input
type="text"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button onClick={() => createItem(input2)}>Add Item</button>
{list.map((item) => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => setEdit(item.id)}>
Rename Item {item.id}
</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
</div>
))}
{edit && (
<div>
<input
type="text"
value={newInput}
onChange={(e) => setNewInput(e.target.value)}
/>
<button onClick={() => renameItem()}>Rename</button>
</div>
)}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
There are other better solutions, such as turning the {edit && ...} part to an component. This should get your code working for now.
I also left out the code to actually rename the item, you should be able to do that yourself also.
I'm trying to send data from Child component to parent component using call back function.
This is my parent component
const handleSubmit = async (e) => {
e.preventDefault()
try {
const response = await promiseApp.post('/promises', {
id: "",
uuid: "",
content: promise,
date: date,
time: time,
place: place,
phone_number: phone
})
addPromise(response.data.promise)
history.push('/promises')
} catch (err) {
alert("An error has occured")
console.log(err)
console.log('zzzzzzzz')
}
}
const updateNumbersInParent = (number) => {
console.log(number)
console.log('hhhhhhhhhh')
let value = number.map((element) => {
return element.newNumber
})
console.log(value)
for (let i = 0; i < value.length; i++) {
console.log(value[i])
setPhone(value[i])
}
}
return (
<div className="tile is-parent">
<article className="tile is-child box">
<PhoneNumber phone={phone} setPhone={setPhone} onChange={value => setPhone(value)}
updateNumbersInParent={updateNumbersInParent}/>
</article>
</div>
)
This is my child component
function PhoneNumber (props) {
// const [count, setCount] = useState(1)
const [phoneNumber, setPhoneNumber] = useState([])
const addNumberButton = (e) => {
e.preventDefault()
addPhoneNumber(props.phone)
props.setPhone('')
}
const addPhoneNumber = (newNumber) => {
const addNewPhoneNumber = [...phoneNumber, {newNumber}]
setPhoneNumber(addNewPhoneNumber)
props.updateNumbersInParent(addNewPhoneNumber)
}
const deletePhoneNumber = (number) => {
const filteredPhoneNumbers = phoneNumber.filter(currentPhoneNumbers => (currentPhoneNumbers !== number))
setPhoneNumber(filteredPhoneNumbers)
props.updateNumbersInParent(filteredPhoneNumbers)
}
return (
<>
<p className="title">Type a phone number and click add</p>
<div className="field">
<div className="control">
<button className="button is-primary" onClick={addNumberButton}>Add</button>
<p><b>No. of phone numbers:</b></p>
<input className="input is-danger" pattern='^\+[1-9]\d{1,14}$' value={props.phone} onChange={e => props.onChange(e.target.value)} type="tel" placeholder="Enter your phone number here"></input>
<p>(Format: +10000000000)</p>
</div>
<ul>
{phoneNumber.map((number, index) => {
return (
<>
<li key={index}>{number.newNumber}</li>
<button onClick={() => deletePhoneNumber(number)}>Delete</button>
</>
)
})}
</ul>
</div>
</>
)
}
I can see that the data from Child component has been passed to the Parent component but keep getting an error when I do POST request. phone_number property in the parent component has an empty value every time I do the POST request.