i have a scenario where i have to maintain the state of multiple checkboxes dynamically.
but when it is in on state on clicking again it doesn't changes to off state.
the number of checkboxes depenmds on a constant number say num-=10
const handleAmountChange = ev => {
const idx = ev.target.id;
const val = ev.target.value;
setEmiAmount(prev => {
const nd = [...prev];
nd[idx].emiAmount = (val);
values.push(nd[idx].emiAmount)
return nd;
});
};
useEffect(() => setEmiAmount(
(numberOfInstallments && numberOfInstallments > 0)
? ([...Array(+numberOfInstallments).keys()].map(
id => ({ id, emiAmount: '' })
)) : null
), [numberOfInstallments]);
console.log("emiAmount", emiAmount)
emiAmount && emiAmount.map((emi, idx) => {
values.push(emi.emiAmount)
})
// ui
emiAmount.map(
({ id, loan }) => (
<div className="relative px-2 mt-2 w-9">
<label
className="block uppercase text-blueGray-600 text-xs font-bold mb-2"
htmlFor="grid-password"
>
</label>
<input
onChange={handleAmountChange}
key={id} id={id}
type="checkbox" role="switch"
required={true}
/>
</div>
Related
I have a deck of cards with assigned numbers using dataset-keyMatch.
When I click on the first card I want it to assign the keyMatch value to the first state (choiceOne) and then when I click a second card I want to check that choiceOne has a value and if that is true I then assign choiceTwo the keyMatch value of the second card.
The card elements are imported from a MatchingCard Component.
Then the onClick event is assigned in the [classId] Component using the function handleChoice.
For choiceOne & choiceTwo
I tried with a default state of null
Then I tried with a default state of 0
// -----------------Matching Card Game Functionality --------------------
// --------------------------------------------------------------------------
// States
const [doubledDeck, setDoubledDeck] = useState([]);
const [shuffledDeck, setShuffledDeck] = useState([]);
const [deckReady, setDeckReady] = useState([]);
const [matchingGameActive, setMatchingGameActive] = useState(false);
const [turns, setTurns] = useState(0);
const [choiceOne, setChoiceOne] = useState(0);
const [choiceTwo, setChoiceTwo] = useState(0);
// test the logic that I want for the onClick event on the MatchingCard
// This works....so I don't know why it won't work when I click the cards
const random = Math.floor(Math.random() * 10);
const check = () => {
choiceOne != 0 ? setChoiceTwo(random) : setChoiceOne(random);
if (turns === 0) {
setChoiceOne(random);
setTurns((prevTurn) => prevTurn + 1);
}
if (turns === 1) {
setChoiceTwo(random);
setTurns(0);
}
};
// Take the original double sided deck - split it - and combine into big deck for game
const doubleTheDeck = (deck: any) => {
const FlashcardsEnglish = deck.map((card: { props: { children: { props: any }[] } }) => {
return card.props.children[0].props;
});
const FlashcardsJapanese = deck.map((card: { props: { children: { props: any }[] } }) => {
return card.props.children[1].props;
});
const joinedDeck = FlashcardsEnglish.concat(FlashcardsJapanese);
setDoubledDeck(joinedDeck);
};
// shuffle deck -----
const shuffle = (deck: any[]) => {
const shuffledCards = deck
.sort(() => Math.random() - 0.5)
.map((card) => ({
...card,
}));
setShuffledDeck(shuffledCards);
};
// choice functionality
const handleChoice = (e: { target: { dataset: { keyMatch: any } } }) => {
const parsed = parseInt(e.target.dataset.keyMatch);
// const str = e.target.dataset.keyMatch;
// attempt #1 using "null" as the default state for choiceOne and Two
// choiceOne ? setChoiceTwo(parsed) : setChoiceOne(parsed);
// attempt #2 using 0 as the default state for choiceOne and Two
if (turns === 0) {
setChoiceOne(parsed);
setTurns((prevTurn) => prevTurn + 1);
}
if (turns === 1) {
setChoiceTwo(parsed);
setTurns(0);
}
};
console.log(choiceOne, choiceTwo);
// create JSX elements------
const finalDeck = shuffledDeck.map((card: { matchId: any; word: any }) => {
const { matchId, word } = card;
return (
<MatchingCards key={matchId + word[0]} matchId={matchId} word={word} handleChoice={handleChoice}></MatchingCards>
);
});
// prepare deck for game start -----
const handleMatchingGameClick = () => {
// take flahscards and double split them into two - doubling size
doubleTheDeck(cardsForMatchingGame);
// shuffle the deck & sets the shuffledCards
shuffle(doubledDeck);
// create JSX elements from the new cards
setDeckReady(finalDeck);
// set game to active
setMatchingGameActive((prevState) => !prevState);
};
// useEffect(() => {}, []);
return (
<div>
<div>
<Header pageHeader={className} />
<div className="bg-white">choice 1: {choiceOne}</div>
<div className="bg-white">choice 2: {choiceTwo}</div>
<button onClick={check}>check</button>
</div>
{/* <ToggleButton /> */}
<div className="flex items-center justify-between bg-slate-200 dark:bg-bd-1 p-4 ">
<HomeButton />
{/* <button onClick={handleMatchingGameClick}>start game</button> */}
{/* <ShuffleButton onClick={shuffle(doubledDeck)} /> */}
<MatchingGameButton
content={matchingGameActive ? "Back to Regular Deck" : "Go to Matching Game"}
onClick={handleMatchingGameClick}
/>
<ToggleButton />
</div>
<div
className="
dark:bg-bd-1
p-10
bg-slate-200 gap-5 flex flex-col items-center justify-center
sm:items-center sm:justify-center
sm:grid
sm:grid-cols-2
md:grid
md:grid-cols-3
lg:grid-cols-4
"
>
{matchingGameActive ? deckReady : cards};
</div>
</div>
);
What I return from the MatchingCards Component
return (
<div>
<div>
<Header pageHeader={className} />
<div className="bg-white">choice 1: {choiceOne}</div>
<div className="bg-white">choice 2: {choiceTwo}</div>
<button onClick={check}>check</button>
</div>
{/* <ToggleButton /> */}
<div className="flex items-center justify-between bg-slate-200 dark:bg-bd-1 p-4 ">
<HomeButton />
{/* <button onClick={handleMatchingGameClick}>start game</button> */}
{/* <ShuffleButton onClick={shuffle(doubledDeck)} /> */}
<MatchingGameButton
content={matchingGameActive ? "Back to Regular Deck" : "Go to Matching Game"}
onClick={handleMatchingGameClick}
/>
<ToggleButton />
</div>
<div
className="
dark:bg-bd-1
p-10
bg-slate-200 gap-5 flex flex-col items-center justify-center
sm:items-center sm:justify-center
sm:grid
sm:grid-cols-2
md:grid
md:grid-cols-3
lg:grid-cols-4
"
>
{matchingGameActive ? deckReady : cards};
</div>
</div>
);
When I click on a card my function only assigns choiceOne and then continues to reassign choiceOne. ChoiceTwo never gets given a value despite ChoiceOne already having a value.
I made a test function called "check" and the logic works there so I have no idea why it is not working on my card element.
When a component renders MatchingCards Component is called leads to new state creation.
I want to update the text whatever users types in the input field and then join that text with another text (i.e ".com" in this example). So somehow I managed to join the extension with the user's input text but when the input field is empty the extension is still showing. Can someone help me to remove that extension text when the input field is empty?
Here's my code
import React, { useState } from "react";
import Check from "./Check";
export default function App() {
const [inputValue, setInputValue] = useState("");
const [inputExtension, setInputExtension] = useState("");
const handleChange = (e) => {
setInputValue(e.target.value);
if (setInputValue == inputValue) {
setInputExtension(inputExtension);
}
if (inputValue == inputValue) {
setInputExtension(".com");
}
};
return (
<>
<h1 className="text-[2.1rem] font-semibold text-black">
Find Your Perfect Domain
</h1>
<form>
<label
for="default-search"
class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300"
>
Search
</label>
<div class="relative">
<input
type="search"
id="in"
value={inputValue}
onChange={handleChange}
class="block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
placeholder="Search for domains..."
/>
</div>
</form>
<Check domain={inputValue} extension={inputExtension} />
<Check domain={inputValue} extension={inputExtension} />
</>
);
}
Here you have the code
if (inputValue == inputValue) {
setInputExtension(".com");
}
Change this to
if(inputValue.length > 0) {
setInputExtension(".com");
}
If you are passing both inputValue and inputExtension as props to the Check component then it should be trivial to just check the inputValue length, i.e. use the fact that empty strings are falsey, and conditionally append the extension value.
Example:
const value = ({
inputValue,
inputExtension = ".com"
}) => inputValue + (inputValue ? inputExtension : "");
console.log({ value: value({ inputValue: "" }) }); // ""
console.log({ value: value({ inputValue: "someValue" })}); // "someValue.com"
Code:
const Check = ({ inputValue = "", inputExtension = "" }) => {
const value = inputValue + (inputValue ? inputExtension : "");
...
};
Hello I am working on a shopping cart project, where user can add/subtract quantity depending on the button clicked . The problem I am having is if I move state up to the parent component the itemQty is the same for all entities. So I moved it down to child component and that keeps the states separate for each product. This works up until I need to access that itemQty to calculate the subtotal.
I have tried passing a function from parent to child to return the itemQty but it ended up returning itemQty for each product.
I have tried useRef, but I don't think it is ment for this situation and I ended up with some error.
Any help will be very appreciated.
Parent Component
export default function cart() {
let total = useContext(getTotalContext)
let setTotal = useContext(setTotalContext)
const [products, setProducts] = useState([])
const [sum, setSum] = useState(0)
/* const prices = products.map((x) => x.price).reduce((a, b) => a + b, 0) // use to calculate total price */
const prices = products.map((x) => x.price).reduce((a, b) => a + b, 0)
const itemQtyRef = useRef(null)
useEffect(() => {
// localStorage.clear();
setProducts(JSON.parse(localStorage.getItem("products"))) // get add to cart data initially to create cart
localStorage.getItem('count') === 0 ? setTotal(JSON.parse(localStorage.getItem("products")).length) : setTotal(localStorage.getItem('count')) // return length if no count total
}, [])
useEffect(() => {
localStorage.setItem('count', total) // stores total for navbar after every change in dropdown etc
}, [total])
useEffect(() => { // upload changes to products to storage every change
localStorage.setItem("products", JSON.stringify(products))
setSum(prices) //
console.log(products)
}, [products])
return (
<div className="Page-Container">
<div className="Product-Container">
{products.map((product, i) => {
return (
<div key={i}>
<Image
className=""
alt="Image Unavailable"
src={product.image}
width={300}
height={300} />
<h4 className="text-sm text-gray-700">{product.title}</h4>
<h5 className="text-lg font-medium ">${product.price}</h5>
<h6 className="no-underline hover:no-underline">{product.rate}/5 of {product.count} Reviews</h6> {/*Add stars to */}
<button className="bg-black-500 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded-full"
onClick={() => { //remove product on click of x
setProducts(products.filter((x) => x.id !== product.id))//filters out by product id clicked
setTotal(total - 1)
// setTotal(total-product.itemQty) // removed item qty from total
}}>x</button>
<QtyButton product={product} setTotal={setTotal} total={total} />
</div>
)
})}
Child Component
export default function QtyButton(props) {
const [itemQty, setItemQty] = useState(1)
return (
<div>
<button className="bg-green-500 hover:bg-gray-400 font-bold py-2 px-4"
onClick={() => {
setItemQty(itemQty + 1)
}}>+</button>
<button className="bg-red-500 hover:bg-gray-400 font-bold py-2 px-4" onClick={() => {
if (itemQty > 0) {
setItemQty(itemQty - 1)
}
}}>-</button><div>Quantity: {itemQty}</div>
</div>
)
}
Lifting the state to the parent component is the right choice here.
But you seem to have gotten a bit of tunnel vision with the state. You should instead send in functions to modify parent state and have another state object which hold "purchase product data", indexed on product.id
// at the top
const [purchases, setPurchases] = useState({})
// in the state initializing useEffect
const _products = JSON.parse(localStorage.getItem("products"))
setProducts(_products)
setPurchases(_products.reduce((acc,p) => ({ ...acc, [p.id]:
{quantity:0}}),{}))
// in returning render
<QtyButton onPlus={() => setPurchases(purchases => {...purchases, purchases[product.id]: { quantity: purchases[product.id]++}})}
// onMinus={}
/>
first of all, it's not the good way.means calling a child function from parent .Normally what we do is passing the function to the child component from parent.means you can define the function in parent and pass it to child
but if you must need like this, please use ref.
wrap up the child function(which u need to call from parent) with forwardRef and useImperativeHandle
for example
const QtyButton = forwardRef((props, ref) => {
const [itemQty, setItemQty] = useState(1)
useImperativeHandle(ref, () => ({
handleqty(qty) {
setItemQty(itemQty)
}
}));
return (
<div>
<button className="bg-green-500 hover:bg-gray-400 font-bold py-2 px-4"
onClick={() => {
setItemQty(itemQty + 1)
}}>+</button>
<button className="bg-red-500 hover:bg-gray-400 font-bold py-2 px-4" onClick={() => {
if (itemQty > 0) {
setItemQty(itemQty - 1)
}
}}>-</button><div>Quantity: {itemQty}</div>
</div>
)
});
in parent
const qtyButtonRef = useRef();
<QtyButton ref={qtyButtonRef} product={product} setTotal={setTotal} total={total} />
and call the child function by using
qtyButtonRef.current.handleqty();
Initial State
const [variation, setVariation] = useState([
{
name: "",
value: [""],
},
]);
function on button click
const addValueField = (e, index) => {
e.preventDefault();
const field = [...variation];
field[index].value.push("");
setVariation(field);
};
html render:
<div>
{variation.map((value, index) => {
return (
<div className="form-control">
<input
key={index}
className="bg-base-200 m-1 shadow appearance-none border rounded text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
placeholder="Enter Variation Type"
/>
<div className="form-control">
{variation[index].value.map((value, i) => {
return (
<input
type="text"
key={i}
className="bg-base-200 m-1 shadow appearance-none border rounded text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Enter Value"
/>
);
})}
<button onClick={(e, index) => addValueField(e, index)}>
Add another value
</button>
</div>
</div>
);
})}
</div>
When addValueField(e,index) is called on button click, it does not pick the index of parent map(), but button is within parent map() scope, so it does not pass index to the function and error is generated.
onClick doesn't give you the index. You have to get the index from the map directly if you use as
<button onClick={(e) => addValueField(e, index)}>
Add another value
</button>;
and you can set variation state as
const addValueField = (e, index) => {
e.preventDefault();
setVariation((state) =>
state.map((o, i) => (i === index ? { ...o, value: [...o.value, ""] } : o))
);
};
I want to implement an edit dialog for some metadata that I get from the backend. For this, I'm using Formik. When the user changes one metadata field then an icon is shown, which indicates that the field was changed. On submit, only the updated values should be sent to the backend. I read in other posts that you should compare the current field values with the initial values provided to the Formik form. This works perfectly fine for single values, like a changed title. However, the form I need to implement has also multi-value fields like creators. I implemented a custom field for that where the user can choose/provide multiple values for one field. The values of this field are saved as an array in the Formik form. The problem is that Formik also changes the initialValue of this field to the currently saved array and therefore I'm no longer able to check if the field is updated or not. Furthermore, I save the metadata fields provided by the backend in a state value because the response contains some further information needed for further implementation. This state value also contains the current value (before update) that a metadata field has and is used as initial values for the Formik form. Strangely, the multi-field component not only overwrites the initialValue of the field of the Formik form but also the value of the field in the state which is only read and never directly updated.
My dialog for editing metadata looks as follows:
const EditMetadataEventsModal = ({ close, selectedRows, updateBulkMetadata }) => {
const { t } = useTranslation();
const [selectedEvents, setSelectedEvents] = useState(selectedRows);
const [metadataFields, setMetadataFields] = useState({});
const [fetchedValues, setFetchedValues] = useState(null);
useEffect(() => {
async function fetchData() {
let eventIds = [];
selectedEvents.forEach((event) => eventIds.push(event.id));
// metadata for chosen events is fetched from backend and saved in state
const responseMetadataFields = await fetchEditMetadata(eventIds);
let initialValues = getInitialValues(responseMetadataFields);
setFetchedValues(initialValues);
setMetadataFields(responseMetadataFields);
}
fetchData();
}, []);
const handleSubmit = (values) => {
const response = updateBulkMetadata(metadataFields, values);
close();
};
return (
<>
<div className="modal-animation modal-overlay" />
<section className="modal wizard modal-animation">
<header>
<a className="fa fa-times close-modal" onClick={() => close()} />
<h2>{t('BULK_ACTIONS.EDIT_EVENTS_METADATA.CAPTION')}</h2>
</header>
<MuiPickersUtilsProvider utils={DateFnsUtils} locale={currentLanguage.dateLocale}>
<Formik initialValues={fetchedValues} onSubmit={(values) => handleSubmit(values)}>
{(formik) => (
<>
<div className="modal-content">
<div className="modal-body">
<div className="full-col">
<div className="obj header-description">
<span>{t('EDIT.DESCRIPTION')}</span>
</div>
<div className="obj tbl-details">
<header>
<span>{t('EDIT.TABLE.CAPTION')}</span>
</header>
<div className="obj-container">
<table className="main-tbl">
<tbody>
{metadataFields.mergedMetadata.map(
(metadata, key) =>
!metadata.readOnly && (
<tr key={key} className={cn({ info: metadata.differentValues })}>
<td>
<span>{t(metadata.label)}</span>
{metadata.required && <i className="required">*</i>}
</td>
<td className="editable ng-isolated-scope">
{/* Render single value or multi value input */}
{console.log('field value')}
{console.log(fetchedValues[metadata.id])}
{metadata.type === 'mixed_text' &&
!!metadata.collection &&
metadata.collection.length !== 0 ? (
<Field name={metadata.id} fieldInfo={metadata} component={RenderMultiField} />
) : (
<Field
name={metadata.id}
metadataField={metadata}
showCheck
component={RenderField}
/>
)}
</td>
</tr>
),
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{/* Buttons for cancel and submit */}
<footer>
<button
type="submit"
onClick={() => formik.handleSubmit()}
disabled={!(formik.dirty && formik.isValid)}
className={cn('submit', {
active: formik.dirty && formik.isValid,
inactive: !(formik.dirty && formik.isValid),
})}
>
{t('UPDATE')}
</button>
<button onClick={() => close()} className="cancel">
{t('CLOSE')}
</button>
</footer>
<div className="btm-spacer" />
</>
)}
</Formik>
</MuiPickersUtilsProvider>
</section>
</>
);
};
const getInitialValues = (metadataFields) => {
// Transform metadata fields provided by backend
let initialValues = {};
metadataFields.mergedMetadata.forEach((field) => {
initialValues[field.id] = field.value;
});
return initialValues;
};
Here is RenderMultiField:
const childRef = React.createRef();
const RenderMultiField = ({ fieldInfo, field, form }) => {
// Indicator if currently edit mode is activated
const [editMode, setEditMode] = useState(false);
// Temporary storage for value user currently types in
const [inputValue, setInputValue] = useState('');
useEffect(() => {
// Handle click outside the field and leave edit mode
const handleClickOutside = (e) => {
if (childRef.current && !childRef.current.contains(e.target)) {
setEditMode(false);
}
};
// Focus current field
if (childRef && childRef.current && editMode === true) {
childRef.current.focus();
}
// Adding event listener for detecting click outside
window.addEventListener('mousedown', handleClickOutside);
return () => {
window.removeEventListener('mousedown', handleClickOutside);
};
}, []);
// Handle change of value user currently types in
const handleChange = (e) => {
const itemValue = e.target.value;
setInputValue(itemValue);
};
const handleKeyDown = (event) => {
// Check if pressed key is Enter
if (event.keyCode === 13 && inputValue !== '') {
event.preventDefault();
// add input to formik field value if not already added
if (!field.value.find((e) => e === inputValue)) {
field.value[field.value.length] = inputValue;
form.setFieldValue(field.name, field.value);
}
// reset inputValue
setInputValue('');
}
};
// Remove item/value from inserted field values
const removeItem = (key) => {
field.value.splice(key, 1);
form.setFieldValue(field.name, field.value);
};
return (
// Render editable field for multiple values depending on type of metadata field
editMode ? (
<>
{fieldInfo.type === 'mixed_text' && !!fieldInfo.collection && (
<EditMultiSelect
collection={fieldInfo.collection}
field={field}
setEditMode={setEditMode}
inputValue={inputValue}
removeItem={removeItem}
handleChange={handleChange}
handleKeyDown={handleKeyDown}
/>
)}
</>
) : (
<ShowValue setEditMode={setEditMode} field={field} form={form} />
)
);
};
// Renders multi select
const EditMultiSelect = ({ collection, setEditMode, handleKeyDown, handleChange, inputValue, removeItem, field }) => {
const { t } = useTranslation();
return (
<>
<div ref={childRef}>
<div onBlur={() => setEditMode(false)}>
<input
type="text"
name={field.name}
value={inputValue}
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => handleChange(e)}
placeholder={t('EDITABLE.MULTI.PLACEHOLDER')}
list="data-list"
/>
{/* Display possible options for values as dropdown */}
<datalist id="data-list">
{collection.map((item, key) => (
<option key={key}>{item.value}</option>
))}
</datalist>
</div>
{/* Render blue label for all values already in field array */}
{field.value instanceof Array &&
field.value.length !== 0 &&
field.value.map((item, key) => (
<span className="ng-multi-value" key={key}>
{item}
<a onClick={() => removeItem(key)}>
<i className="fa fa-times" />
</a>
</span>
))}
</div>
</>
);
};
// Shows the values of the array in non-edit mode
const ShowValue = ({ setEditMode, form: { initialValues }, field }) => {
return (
<div onClick={() => setEditMode(true)}>
{field.value instanceof Array && field.value.length !== 0 ? (
<ul>
{field.value.map((item, key) => (
<li key={key}>
<span>{item}</span>
</li>
))}
</ul>
) : (
<span className="editable preserve-newlines">{''}</span>
)}
<i className="edit fa fa-pencil-square" />
<i className={cn('saved fa fa-check', { active: initialValues[field.name] !== field.value })} />
</div>
);
};
export default RenderMultiField;
This is initialValues before a change and after:
InitialValues before change
InitialValues after change
This is the state of MetadataFields and FetchedValues before and after change:
State before change
State after change