i am not sure if this is possible, and having trouble finding the answer. Basically:
Child component:
export function Exchanger(props, clearInputs) {
const result = useSelector(state => state.counter.value)
const dispatch = useDispatch()
let data_1 = props.data_1; //Value of the input
let data_2 = props.data_2; //What kind of currency
let data_3 = props.data_3; //That currency in PLN
return(
<div>
<div>
<button type="submit" disabled={props.disableData} aria-label="Exchange currency" onClick={
() =>{
dispatch(changeCurrency({data_1, data_2, data_3}));
clearInputs()
}
}>
Exchange currency
</button>
<span>{ result }</span>
</div>
</div>
)
}
Parent:
(other stuff here)
const clearInputs = () => {
setInputValue('')
setSelectValue('default')
}
return(
<>
<Input type="number" placeholder="Type the amount in PLN" onChange={handleChange} value={inputValue} />
<Select onChange={handleSelectChange} value={selectValue} />
<Exchanger clearInputs={clearInputs} data_1={inputValue} data_2={selectValue} data_3={selectCurrency} disableData={disableButton} />
</>
)
I want my button to dispatch the action to the state, and then clear the inputs. The ClearInputs() changes state that is in the parent component. I know i can have two onClick events in my button, but is it possible if one of them is dispatch?
Huge thanks in advance!
export function Exchanger(props) {
const { clearInputs } = props; <---
const result = useSelector(state => state.counter.value)
changing this little thing fixes everything
Related
I need to dynamically generate multiple divs with input-field in it, so the user can add values in it.
By clicking the button user can create as many divs with input-field as he needs
By typing values total amount must be calculated in real-time.
My problem is how to get all input values into one array so I could calculate the total.
I just need any kind of example how to get those values in one array.
*Note I use functional components.
const ReceiptDetails = () => {
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState(null);
const [expenseList, setExpenseList] = useState([]);
const [expenseTotal, setExpenseTotal] = useState(0);
const options = ['Food', 'Entertaiment', 'Houseware'];
const onOptionClicked = (option) => {
setSelectedOption(option);
setIsOpen(false);
};
const formHandler = (e) => {
e.preventDefault();
if (selectedOption !== null) {
setExpenseList(
expenseList.concat(
<ExpenseDetails key={expenseList.length} calcTotal={calcTotal} />
)
);
}
return;
};
return (
<Container>
<Form onSubmit={formHandler}>
<div>
<DropDownHeader onClick={() => setIsOpen(!isOpen)} required>
{selectedOption || 'Select'}
<span className='arrow-head'>⌄</span>
</DropDownHeader>
{isOpen && (
<div>
<DropDownList>
{options.map((option, indx) => (
<ListItem
onClick={() => onOptionClicked(option)}
value={option}
key={indx}
>
{option}
</ListItem>
))}
</DropDownList>
</div>
)}
</div>
<Button type='secondary'>Add expense</Button>
</Form>
{expenseList}
{expenseList.length !== 0 && (
<ExpenseTotal>Total {expenseTotal}</ExpenseTotal>
)}
</Container>
);
};
export default ReceiptDetails;
const ExpenseDetails = () => {
const [expense, setExpense] = useState(0);
return (
<DetailsContainer>
<div className='first'>
<FirstInput type='text' placeholder='Expense name' />
<SecondInput
type='number'
placeholder='€0.00'
value={expense}
onChange={(e) => setExpense(e.target.value)}
/>
</div>
</DetailsContainer>
);
};
export default ExpenseDetails;
how about you use a context? That will allow you to keep and share your state between the components. I'd do something like here. Keep the array of expenses at the context level and the state of the form. From that, you can get the values and display them in the child components.
But it would be best to read the documentation first ;)
In a small React app, I'm trying to add delete functionality via a button for a list. Presently, I'm attempting this through the deleteItem function, which makes use of array.splice prototype method.
However, I'm encountering the error, Too many re-renders. React limits the number of renders to prevent an infinite loop.. What is the cause of this error? Shouldn't this function only be invoked once, when the button is clicked?
And how can I resolve this error?
import "./styles.css";
import React, { useState, Fragment } from "react";
export default function App() {
const [items, setItems] = useState(["first item"]);
const [newItem, setNewItem] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
setItems([newItem, ...items]);
};
const handleChange = (event) => {
setNewItem(event.target.value);
};
const deleteItem = (i) => {
setItems(items.splice(i,1))
}
return (
<div>
<form>
<input type="text" value={newItem} onChange={handleChange} />
<input type="button" value="submit" onClick={handleSubmit} />
</form>
<ul>
{items.map((i) => {
return (
<Fragment>
<li>{i}</li>
<button
onClick= {() => deleteItem(i)}> // Amr recommendation
delete
</button>
</Fragment>
);
})}
</ul>
</div>
);
}
Edit: I've taken user, Amr's, recommendation and added a anonymous arrow function to the button. However, a new issue has arisen. I can delete any item up until there exists only one item in the array. The final item cannot be deleted. Why is this?
you are passing function reference on the onClick handler, change it to an arrow function that triggers the delete method onClick= {()=>deleteItem(i)}>
second thing is that you should add keys to your the parent component when you Map over components to prevent unnecessary behavior.
and the last thing is that in your delete method, you are using Array.prototype.splice(), which returns the item that will be removed, from the items, your requested/ required behavior can be achieved through the Array.prototype.filter() method
const deleteItem = (i) => {
setItems(items.filter((item) => item !== i));
};
This is the final result, it should work fine.
import React, { useState, Fragment } from "react";
export default function App() {
const [items, setItems] = useState(["first item"]);
const [newItem, setNewItem] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
setItems([...items, newItem]);
};
const handleChange = (event) => {
setNewItem(event.target.value);
};
const deleteItem = (i) => {
setItems(items.filter((item) => item !== i));
};
console.log(items);
return (
<div>
<form>
<input type="text" value={newItem} onChange={handleChange} />
<input type="button" value="submit" onClick={handleSubmit} />
</form>
<ul>
{items.map((i, idx) => {
return (
<div key={idx}>
<li>{i}</li>
<button onClick={() => deleteItem(i)}>delete</button>
</div>
);
})}
</ul>
</div>
);
}
you can use following code for deleting from an array. it copies 'items' array and delete one item and after that setstate new array.
it prevent re-render whole component,do operations on copy of state and setstate final result.
const deleteItem = (i) => {
let newItems=[...items]
newItems.splice(i,1)
setItems(newItems)
};
I'd like to react rerender component after every state edit.
App component:
let [cur1, setCur1] = useState('USD')
let [cur2, setCur2] = useState('EUR')
let [result, setResult] = useState(0)
let currenciesArr = [cur1, cur2]
async function getRate(e) {
e.preventDefault()
setCur1(cur1 = e.target.cur1.value)
setCur2(cur2 = e.target.cur2.value)
let amount = e.target.amount.value
const api_url = await fetch(`https://free.currconv.com/api/v7/convert?q=${cur1}_${cur2}&compact=ultra&apiKey=${API_KEY}`)
const data = await api_url.json()
await setResult(convert(amount, data))
}
I have used Context.Provider for rerender, but it doesn't work.
return (
<Context.Provider value={{currenciesArr}}>
<div>
<Choose getRate={getRate} chooseCur={chooseCur} chooseCur2={chooseCur2}/>
<ShowRate currencies={currenciesArr} result={result}/>
</div>
</Context.Provider>
)
Component that need to rerender
function Choose(props) {
const cProps = useContext(Context)
console.log(cProps.currenciesArr);
return(
<div>
<div>
<button onClick={ props.chooseCur } name='RUB'>RUB</button>
<button onClick={ props.chooseCur } name='AUD'>AUD</button>
</div>
<div>
<button onClick={ props.chooseCur2 } name='EUR'>EUR</button>
<button onClick={ props.chooseCur2 } name='GBP'>GBP</button>
</div>
<form onSubmit={props.getRate}>
{cProps.currenciesArr.map((item,i) => {
return(
<input type='text' key={i} name={'cur'+(i+1)} defaultValue={item}></input>
)
})
}
<input type='text' name='amount' defaultValue='1'></input>
<button onClick={(e)=>{console.log(e.target)}} ></button>
</form>
</div>
)
}
Button with prop props.chooseCur setting state in App component
function chooseCur(e) {
e.preventDefault()
setCur1(e.target.name)
}
function chooseCur2(e) {
e.preventDefault()
setCur2(e.target.name)
}
and i'd like to "choose" component will rerender after setState.
First currenciesArr should be part of the state as const [currenciesArr, setCurrenciesArr] = useState([cur1, cur2])
Next, you need to call setCurrenciesArr in your chooseCur2 functions. I used a restructuring assignment to get the value of name inside the function. Hooks are called when the event loop is complete. See Capbase Medium post for more information on hooks and the event loop.
In choose.js
You need to use value in your input instead of defaultValue and set it as readonly to prevent receiving a warning about setting the value.
Default value provides the value if none is present.
See the following codesandbox for a working version.
https://codesandbox.io/s/long-rain-8vyuh
I am making a React app that tracks your expenses. Right now in the ExpenseInput component I want to be able to extract the values of the inputs and alert the values that were entered. Later on I will want to render a list of expenses similar to a ToDo App. I have some stuff setup already. I know that the expense state variable should store all you expenses and all the other state variables are set to its respective inputs. I just am not sure what the next step should be.
import React, { useState } from "react";
export default function ExpenseInputs() {
const [expense, setExpense] = useState([]);
const [expenseName, setExpenseName] = useState("");
const [expenseAmt, setExpenseAmt] = useState();
const [expenseType, setExpenseType] = useState("");
const expenseTypes = ["Food", "Gas", "Entertainment", "Rent"];
const updateExpenseName = e => {
e.preventDefault();
setExpenseName(e.target.value);
console.log(e.target.value);
};
const updateExpenseAmt = e => {
e.preventDefault();
setExpenseAmt(e.target.value);
};
const addExpenses = () => {
};
return (
<>
<div className="field">
<label className="label">Expense</label>
<input
value={expenseName}
onChange={updateExpenseName}
className="expense-input"
type="text"
placeholder="Expense"
/>
</div>
<div className="field">
<label className="label">Amount</label>
<input
value={expenseAmt}
onChange={updateExpenseAmt}
className="expense-amount"
type="text"
placeholder="Expense amount"
/>
</div>
<div className="field">
<label className="label">Expense Type</label>
<select>
{expenseTypes.map(e => {
return <option>{e}</option>;
})}
</select>
</div>
<div className="field">
<button onClick={addExpenses} className="button">
Add
</button>
</div>
</>
);
}
You have your values in the state of each input (expenseAmt, expenseName, etc).
const [expenseName, setExpenseName] = useState("");
const [expenseAmt, setExpenseAmt] = useState();
const [expenseType, setExpenseType] = useState("");
For your purposes to add new expense you can use the state which may be array of objects, where each object represents your expense (title, type, amount).
const [expense, setExpense] = useState([]);
So you need to create handler to add new object to your array of expenses.
const addExpenses = () => {
};
And just map it. Then you pass object of expense to each expense component as props.
{expense.map((expense, index) => (
<ListItem key={index} expense={expense} />
))}
For removing you need to create remove handler and pass it to expense component as props and fire it there.
I want to grab the value of input inside the array when the button is clicked. How do i pass the input value to the function of button.
Any help would be appreciated. Thanks
import React, { useState, useEffect } from 'react'
export default function Todo(props) {
const [todo,settodo] = useState([]);
function getdata(){
//fetch data
settodo(data);
}
function SaveInput(id){
}
useEffect(() => {
getdata();
},[]);
return (
<React.Fragment>
<div>
{todo.map(function(item, key){
return <div>
<div>{item.name}</div>
<div>
<input type="text" name="inputval" onChange={() => handleChange(e)}>
<button onClick={()=> SaveInput(item.id)}></button>
</div>
</div>
})}
</div>
</React.Fragment>
)
}
You need to send item.id to your handleChange function,
<input type="text" name="inputval" onChange={(e) => handleChange(e,item.id)} />
You handleChange function should,
const handleChange = (e,id) => {
let val = e.target.value;
setInputVal(prevState =>({
...prevState,
[id]:val
}))
}
You must define a state to store input values,
const [inputVal,setInputVal] = useState({});
On the click of button you can access input state,
function SaveInput(id){
console.log(inputVal[id]);
}
Demo
You can save the inputs in a separate useState when the input is being changed, which can be later retrieved easily during the button click event.
Code below is an example and is not tested, but should give you some idea how to proceed.
import React, { useState, useEffect } from 'react'
export default function Todo(props) {
const [todo,settodo] = useState([]);
const [inputVal, setInputVal] = useState({});
function getdata(){
//fetch data
settodo(data);
}
function SaveInput(id){
let inputVal = inputVal[id];
// do other stuff.
}
useEffect(() => {
getdata();
},[]);
return (
<React.Fragment>
<div>
{todo.map(function(item, key){
return <div>
<div>{item.name}</div>
<div>
<input type="text" name="inputval" onChange={(e) => setInputVal({...inputVal, [item.id]: e.target.value })}>
<button onClick={()=> SaveInput(item.id)}></button>
</div>
</div>
})}
</div>
</React.Fragment>
)
}
One common pattern is to use the handleChange(event) function on input to set a state with the current value.
const [input,setInupt] = useState("");
function handleChange(event) {
setInput(event.target.value)
}
and when the button is clicked, you can use the value of the input state to pass on
<button onClick={()=> console.log(input))}>
First of all, If you are having an onChange method then you must have a value for that input as well or else it will display a warning for "uncontrolled input" and that input box is of no use to you unless you provide a value to it.
Secondly, you should use a state for the values of those input boxes and then you can access the values of input in the save button click function. Here is the example of how you can do it.
import React from 'react'
export default class Todo extends React.Component {
constructor(props) {
super(props);
this.state = {
inputIDs: {}
}
}
SaveInput = id => {
console.log("input value:", this.state[id]);
};
handleChange = (e, id) => {
this.setState({[id]: e.target.value});
};
render() {
const {inputIDs} = this.state;
const todo = [
{id: 1, val: "abc", name: "lorem"},
{id: 2, val: "xyz", name: "Ipsum"}
];
let todos = todo.map((item, key) => {
return <div key={key}>
<div>{item.name}</div>
<div>
<input type="text" value={this.state[item.id]} onChange={(e) => this.handleChange(e, item.id)}/>
<button onClick={() => this.SaveInput(item.id)}>Click Me!</button>
</div>
</div>
});
return (
<React.Fragment>
{todos}
</React.Fragment>
)
}
}