Populate CheckboxList Based on URL Parameters - reactjs

I am trying to retrieve value from URL parameters and populate the value to a checkboxlist. Next step, select the checkbox value, update the URL parameter. My sandbox URL is https://oh2p2e.csb.app/?checkedCampusItems=A,B. Right now, it only works with one value in the URL. What is my problem? How do I synchronize the URL parameter value when clicking the checkboxlist? Many thanks.

https://oh2p2e.csb.app/?checkedCampusItems=A,B
The A and B should be selected.
That means you should grab the 'checkedCampusItems', that will return you a string 'A,B'.
For example:
// valuesToCheck is the array that contains
// the values from the url parrameter
const valuesToCheck = checkedCampusItems.split(,);
// Now you can see which one should be checked.
// The three checkboxes will be rendered but
// the ones you want to will be checked.
<input type="checkbox" checked={valuesToCheck.includes('A')} onCheck={() => history.push(`/checkedCampusItems?=${currentValue},A`)} />
<input type="checkbox" checked={valuesToCheck.includes('B')} onCheck={() => history.push(`/checkedCampusItems?=${currentValue},A`)} />
<input type="checkbox" checked={valuesToCheck.includes('C')} onCheck={() => history.push(`/checkedCampusItems?=${currentValue},A`)} />
)
This is not the exact code, but you get the main point.

You are getting TypeError this.history.push is not a function beacause you can't use this reference in functional component.
Second thing, useHistory() is now deprecated, you can use useNavigation() insted.
To summarize, first add this line:
Import {useNavigation} from "react-router-dom";
Then, change input element like this :
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(value, index)}
/>
Change handleOnChange function like this:
const handleOnChange = (value, position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
// update URL params
console.log(searchParams);
navigation(/?checkedCampusItems=${value});
};
Here is sandbox : https://codesandbox.io/s/checkboxlist-forked-c1lt1x?file=/src/App.js:1513-1824

Related

Trying to add multiple refs to dynamically generated element in ReactJs

I am trying to add multiple refs to a set of dynamically generated checkboxes. At this point, the checkboxes work, but when I am trying to change the checked state of a particular checkbox, it seems the last one is the only one to have the ref and the state updates just for that particular checkbox.
So far I have this:
// Setting 6 as a number of states
new Array(states.length).fill(false)
);
// Adding refs to check values
const inputsRef = useRef([]);
const handleStateChecked = (position) => {
const updateCheckedState = isStateChecked.map((isChecked, index) =>
index === position ? !isChecked : isChecked
);
setStateIsChecked((prevState) => updateCheckedState);
};
return (
{ ... form ...}
{/* Generating state checkboxes dynamically */}
{states.map((state, index) => {
return (
{...div with styles}
<input
type="checkbox"
checked={isStateChecked[index]}
onChange={ () => handleStateChecked(index)}
value={state}
ref={inputsRef}
/>
{state}
</div>
);
})}
</div>
</div>
Could someone guide me in the right direction? Thanks!

React input field mirrors the data from previous input

I am trying to build a small recipe app. One feature of the app is saving user recipes, including ingredient/qty/measurement.
I need to wrap up the inputted ingredients into an array of objects to send to server but right now my setIngredientList only works for the first two ingredients a user inputs.
When a user tries to add a third ingredient it just mirrors the data from the second input (and fills the third input's fields with the same data as the second input). It is like the second inputs and any subsequent input mirror each other.
I believe the problem is init is not clearing properly (it seems to clear after the first ingredient is added allowing the second one to be added, but then it does not clear for the next ingredients.
I'm not sure the proper way to make sure this happens so multiple ingredients can be added.
Here is my code:
const init = {
ingredient_name: '',
quantity: '',
measure: '',
}
export default function Recipe() {
const [name, setName] = useState('')
const [ingredientList, setIngredientList] = useState([
{
ingredient_name: '',
quantity: '',
measure: '',
},
])
const handleChange = (e, i) => {
const { name, value } = e.target
setIngredientList((prevState) => {
const newIngredientList = [...prevState]
newIngredientList[i][name] = value
return [...newIngredientList]
})
}
return (
<div>
<div className="recipe-form-container">
<form className="recipe-form">
[...]
</div>
{ingredientList.map((list, i) => (
<div key={i} className="ingredient-triad">
<input
className="ingredient"
name="ingredient_name"
type="text"
value={list.ingredient_name}
onChange={(e) => handleChange(e, i)}
></input>
<input
className="quantity"
name="quantity"
type="text"
value={list.quantity}
onChange={(e) => handleChange(e, i)}
></input>
<select
className="dropdown"
name="measure"
id="measure"
value={list.measure}
onChange={(e) => handleChange(e, i)}
>
<option value="" disabled>
--none--
</option>
<option value="cup">cup</option>
</select>
<button
onClick={(e) => {
console.log(init)
setIngredientList((prev) => [...prev, init])
e.preventDefault()
}}
>
Add
</button>
</div>
))}
</form>
</div>
</div>
)
}
Classical object reference issue.
Use the below code it will work fine.
Previously, you pass the same init object for multiple rows,
which is why you got that result. Instead of doing that, when the user clicks 'add' button then add a new Object to your state which is derived from your init object. Here I just clone the init object and then set the state.
<button
onClick={(e) => {
console.log(init);
setIngredientList((prev) => [...prev, { ...init }]);
e.preventDefault();
}}
>
Sounds like you wanted some advice and can prob find the solution yourself, but what i would do to this code:
Move e.preventDefault() above setIngredientList.
Create an Ingredient class with the state / logic within and pass state to Ingredient, the list should not not be concerned with an Ingredient, it just lists.
Then what is onClick doing - init - its the initial value isn't it, so I think that with your onClick you are overwriting prev with the value that is outside the component - should it be outside or is it some state - it holds a value - so it should be an initial state of an ingredient? Checkout react tools profiler so see exactly what is happening on your events.
So what is prev we are over writing it, but what is it? Isn't the first arg on onClick the click event? So I think you are overwriting the click event with the init state - which is outside the component - a few things to tweak for you (if you like), hope it helps.

Set checkbox checked state of checkbox group programmatically in Formik

I have a checkbox group (checkboxes with the same name), that I'd like to toggle them through an external button.
The parent component will map over the data and create multiple checkboxes given the data, like so:
(options.map(
option => (
<Field
name={field.name}
value={option.value}
component={CheckBoxButton}
/>
)
)}
And the CheckBoxButton is
const CheckBoxButton = ({
value,
field,
...props
}) => {
const [activeState, setActiveState] = useState(false)
return (
<div>
<Field
type="checkbox"
{...field}
{...props}
value={value}
checked={activeState}
/>
<Button
active={activeState}
onClick={() => {
setActiveState(!activeState)
}}
>
Toggle Checkbox
</Button>
</div>
)
}
Now on submission, it seems the checked={activeState} value passed to the field seemed to be ignored, nor that it triggers onChange when changed. I have tried to use setFieldValue() but since we have multiple checkboxes with the same name, it only captures the last value. To use it means I'd have to manually structure the value array and pop/add values, which seems like an overkill.
I also tried using useRef and setting ref.current.checked manually, but at no use.
Any insights on what I might be doing wrong, or how to properly set the value programmatically?
So, I managed to get a workaround that solves the problem by wrapping the button (now a div) and checkbox within the same Label, and overriding the state within the onChange event of the field. Each field would have a unique identifier id which the label binds to using htmlFor.
While a valid workaround, it would still be nice to have a programmatic way to do so.
const CheckBoxButton = ({value,field,...props}) => {
const [activeState, setActiveState] = useState(active)
const { handleChange } = useFormikContext()
const fieldId = `${field.name}-${value}`
return (
<label htmlFor={fieldId}>
<Field
id={fieldId}
type="checkbox"
{...field}
{...props}
value={value}
onChange={(event) => {
setActiveState(!activeState)
handleChange(event)
}}
className={style.checkbox}
/>
<span active={activeState} >
Toggle Checkbox
</span>
</label>
)
}

Radio ref on react

I have radio input use want to use ref to show the result on console.
I always get second value even when I have choose the first one.
This is the constructor
this.inputKelamin = React.createRef();
and rendered like this
<div>
Jenis Kelamin :
<input name="kelamin" type="radio" value="laki - laki" ref={this.inputKelamin}/>
Laki - laki
<input name="kelamin" type="radio" value="perempuan" ref={this.inputKelamin}/>
Perempuan
</div>
onSubmit, I put it like this via console :
alamat : ${this.inputAlamat.current.value}
the result is always "perempuan"
This is not for production, just a learning purpose, thank you
You're using same ref for both the element, second ref={this.inputKelamin} overrides the first one and it always points to the second radio button.
From official docs
Refs provide a way to access DOM nodes or React elements created in the render method.
You should create 2 different refs for both inputs.
And you're checking the wrong property here
this.inputAlamat.current.value
value will always be the attribute value you gave value="perempuan".
In case of radio you should look at the checked property, which tells you whether it was selected
this.inputKelamin.current.checked
Also, you might want to look at controlled and un-conntrolled components
In order to get just one data from multiple radio you cam simply do this :
Example with typescript :
import React, { useRef } from "react";
function Form() {
const inputKelamin = useRef() as React.MutableRefObject<HTMLInputElement>;
const inputLaki = useRef() as React.MutableRefObject<HTMLInputElement>;
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const selectedRadio = inputKelamin.current.checked ? inputKelamin.current.value : inputLaki.current.value
console.log(selectedRadio);
}
return (
<form className="form" onSubmit={handleSubmit}>
Jenis Kelamin :
Laki - laki <input type="radio" value="laki - laki" ref={inputKelamin} /> <br />
Perempuan <input type="radio" value="perempuan" ref={inputLaki} /> <br />
<button type="submit">Submit</button>
</form>
);
}
export default Form;

ReactJS: add a new input field on an option select

I'm using react-select with isMulti. I am trying to add a new input field when user selects an option from dropdown. I have somehow achieved it. I am facing a problem, let's say if I select 1st option from dropdown then it adds the input field for that particular option and when I try to select second option then 2 new input fields are added (1 for the previously selected option and the other for current selected option). In this way, I get 3 new input fields but I should be getting 2 input fields because I have selected only 2 options. Below is my code.
onChange = (e) => {
if(e){
e.map((item) => {
this.state.selectValue.push(item.value);
})
}
}
this.state.selectValue.map((item) => {
return(
<div className="row m-3">
<label className="col-md-4">{item}</label>
<div className="col-md-7">
<input type="text" name={item} className="form-control" />
</div>
</div>
)
})
<Select
isMulti
options={this.state.options}
onChange = {(e) => this.onChange(e)}
classNamePrefix="select"
/>
Note: I am storing the option's value as a "name" of input field to keep track of the quantity.
In the screenshot when I selected first then an input field with "first" label was displayed but when I selected "second" then 2 input fields showed up as highlighed in the image.
Updating state within a loop causes sideffects with the way the component re-renders. Try using a temporary array in your handler and then override state after your map function.
onChange = (e) => {
if(e){
let arr = []
e.map((item) => {
arr.push(item.value);
})
this.setState({selectValue: arr})
}
}
Updating state within a loop causes sideffects with the way the component re-renders. Try using a temporary array in your handler and then override state after your map function.

Resources