I am implementing some kind of survey, and creating questions and choices based on api response, using map to handle checkbox onChange, it works very well. However, I am unable to make checkbox checked properly.
Here is checkbox
<FormControlLabel
key={choice.choiceID}
value={choice.choiceID}
control={<Radio />}
label={choice.text}
labelPlacement="end"
name={"" + question.questionID}
onChange={handleChange}
checked={
qValues1.get(question.questionID + "") === question.questionID + ""}
/>
I am converting questionID to string, because name prop should be string.
function handleChange(event) {
const { name, value } = event.target;
qValues1.set(name, value); // adding into map, which is state
if (qValues1.get(name) === value) {
return true;
}
}
I tried converting map into array, and trying to find choiceID using .includes(), but no luck.
{qValues1.get(question.questionID + "") === question.questionID + ""}
You're checking if the map qValues contains a key matching its value. So you should do :
const { name, checked } = event.target
qValues1.set(name, checked ? name : undefined);
in handleChange.
It looks like you're using Material-UI, so you might want to mention that. Sometimes they change the props and stuff so it can get a little wonky. As far as your code goes it looks like your handleChange() function may be the issue. Every time I set up an onChange event I make sure to use the syntax event.target.value to capture the inputs. I'm not sure if that works but give it a try! I've never seen a de-structured syntax like that before in your code -- const { name, value } = event.target; so that's throwing me through a loop. What are your errors looking like?
So, it will like below now, works just fine.
<FormControlLabel
key={choice.choiceID}
control={<Radio color="primary" />}
label={choice.text}
labelPlacement="end"
name={question.questionID + ""}
value={choice.choiceID + ""}
/>
and handleChange
const { name, value } = event.target;
let { checked } = event.target;
qValues1.set(name, checked ? value : undefined);
Related
This is a simple representation of my real problem in a project, it seems i am missing something in react or may be the problem is not related to react, this is my code https://codesandbox.io/s/lucid-northcutt-c3fw4?file=/src/App.js
i need to show the item on the list when click on it, and this never happens because that condition never get true and i don't know why?
{id === itemId && el}
When you click the element, you successfully set the itemId value to the id of the clicked element:
const handleItemClick = id => {
setItemId(id);
};
However, upon re-rendering the component you dynamically generate all new id values:
id={v4()}
It's highly unlikely (re: impossible) that the newly generated UUID will be the same as the previously generated one.
Don't generate new UUID id values on every render. Either use static values or perhaps just an incrementing value from .map():
{arr.map((el, i) => {
return <Item
id={i}
itemId={itemId}
item={el}
handleItemClick={handleItemClick}
/>;
))}
Issue :
id={v4()} this will create a new uuid on each render, so when you click handleItemClick it update itemId and which cause re-render and all ids got changed due to id={v4()} so this will never get true {id === itemId && item}
{arr.map(el => {
return <Item
id={v4()} // <------ Issue is hear
itemId={itemId}
item={el}
handleItemClick={handleItemClick}
/>;
})}
Solution :
// instead of creating uuid on render you can create and save it as state, so it won't change on each re-render
const [items, setItems] = useState([1, 2, 3, 4].map(() => v4()));
{items.map(id => {
return (
<Item
id={id} // <----- Then use it like this
itemId={itemId}
item={id}
handleItemClick={handleItemClick}
/>
);
})}
WORKING DEMO :
First you missed the key prop, when using map(). It will just throw the warning, not to worry about much :P.
the main problem in your code is, v4 will generate random keys on each render. so
the id will never be the same as itemId. try to use some static id. if you can like the map comes with index. you may use that
arr.map((el,index) => {
return <Item
id={index}
key={el}
itemId={itemId}
item={el}
handleItemClick={handleItemClick}
/>;
});
I'm assuming v4 generates a random ID.
So id === itemId will never be true.
And I don't see itemId being initialized anywhere.
It would be great if you can tell me what is that condition for?
Or try {id === itemId || item}
Hope this helps
Thank you
I have a problem with saving the state of the search query.
When the popover is brought into focus, the searchString starts with undefined (second undefined value in picture). When the key 'b' is pressed, the event is fired, and it sets the value to "" (initialized value). As shown, when "bart" is in the search query, console only registers "bar". Does anyone know why this behavior occurs? The end goal is that I am trying to retain the search string on selection (it disappears onclick) -> would appreciate any help with this. The main code block where these changes are happening:
<Autocomplete
open
onClose={handleClose}
multiple
classes={{
paper: classes.paper,
option: classes.option,
popperDisablePortal: classes.popperDisablePortal,
}}
value={pendingValue}
onChange={(event, newValue) => {
setPendingValue(newValue);
}}
// inputValue={searchString}
// onInputChange={(event, newValue) => {
// setSearchString(newValue);
// }}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No values"
renderOption={(option, { selected }) => (
<React.Fragment>
<DoneIcon
className={classes.iconSelected}
style={{ visibility: selected ? 'visible' : 'hidden' }}
/>
<div className={classes.text}>
{option.value}
</div>
</React.Fragment>
)}
options={[...suggestions].sort((a, b) => {
// Display the selected labels first.
let ai = selectedValue.indexOf(a);
ai = ai === -1 ? selectedValue.length + suggestions.indexOf(a) : ai;
let bi = selectedValue.indexOf(b);
bi = bi === -1 ? selectedValue.length + suggestions.indexOf(b) : bi;
return ai - bi;
})}
getOptionLabel={option => option.value}
renderInput={params => (
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
className={classes.inputBase}
// onChange={(event) => {
// console.log("event.target: ", event.target);
// console.log("event.currentTarget: ", event.currentTarget);
// setSearchString(event.currentTarget);
// }}
value={searchString}
onChange={handleInputChange}
/>
)}
/>
I have tried to store the value and re-populate it using both through the Autocomplete props and the InputBase (doing it on both causes it to crash). I have added a sandbox for your ref: CodeSandbox
Appreciate all the help!
Material UI autocomplete by design resets the search value every time you select an option. If you want to by pass it, use useAutocomplete hook to fine tune the component according to your need.
As for delayed console log values, you're setting the new value and then you're console logging the old value. So obviously it will print the old value, what else did you expect?
You code should have been like this
const handleInputChange = event => {
// new value => event.currentTarget.value
// old value => searchString
// these values never mutate throughout this function call
setSearchString(event.currentTarget.value);
// searchString still remains the same here and
// won't change even if you call setState
// it remains the same throughout this entire function call
// Since Mutation is not allowed in Functional Programming
// This is perhaps why Functional Programming is
// far better than Object Oriented Programming 😉
console.log('searchString: ', event.currentTarget.value);
}
However this isn't the right way to observe state changes. Better way would be something like this,
// This will be called whenever React
// observes a change in anyState
useEffect(() => {
console.log(anyState)
}, [anyState])
This task is tough one for me. Please don't get mad. I'm making dynamic inputs to able for user add more informartion.Expected output if both field was filled, push it to the array. I have one datalist and one input. For both of them I want to use one onChange function to make validation and push to the array. Right now, in onChange function I'm checking datalist, does value was typed or selected from options. The problem is that when I'm storing in state, it only saves one sigle value or doesn't push to array. I commented where is the problem in the code. I have tried to reproduced my code : https://jsfiddle.net/armakarma/qwg3j2fa/9/
{this.state.arrayOfInput.map((item, idx) => {
return (
<>
<input
type="text"
name="address"
list="data-list"
onChange={e => this.onChooseAddress(e)}
/>
<datalist id="data-list">
{this.state.adresses.map((item, idx) => {
const { id, address } = item
return <option key={idx} data-value={id} value={address} />
})}
</datalist>
<input
name="contact"
className="event_time-inputs event_table_textarea"
placeholder="Contact"
onChange={e => this.onChooseAddress(e)}
/>
</>
)
})}
onChange function:
onChooseAddress(e) {
const { adresses } = this.state
let address_id = ""
let new_address = ""
let whoIsMetting = ""
// checking if value from "contact" input
if (e.target.name === "contact") {
whoIsMetting = e.target.value
}
// checking if value is selected from options in datalist
for (let i = 0; i < adresses.length; i++) {
if (
e.target.value === adresses[i].address &&
e.target.name === "address"
) {
address_id = adresses[i].id
}
}
//if user typed value
if (!address_id && e.target.name === "address") {
new_address = e.target.value
}
// PROBLEM IS HERE!!!! if both fields was filled pushing to array
if ((address_id || new_address) && whoIsMetting) {
let interviewDetails = {
address_id: address_id,
new_address: new_address,
whoIsMetting: whoIsMetting,
}
this.setState(prevState => ({
arrayOfAddresses: [...prevState.arrayOfAddresses, interviewDetails],
}))
}
}
Your problem is, that only one of the three variables address_id, new_address and whoIsMetting can have a value at a call of onChooseAddress.
You can never have a value in whoisMetting and one of the others together, because whoIsMetting only gets a value if onChooseAddress is called from the contact input and the others if it is called from the address input.
I suggest you either stash partly correct values in a different variable (outside of the method ofc) and check for completion before pushing them to the arrayOfAddresses OR simply push partly correct entries directly to arrayOfAddresses and check for completion before using an entry of this array.
This is a really straight forward question. Notice I have the multiple attribute. When I select a value it just pushes a value into an array. Hence, if I have multiple values it will give me an array with multiple values but not the specific option I'm choosing.
Now my question is how do I get that specific value when I remove it from the Dropdown? I've searched google for an hour and haven't found an answer.
<Dropdown
options={options}
onChange={this.onChange.bind(this)}
search fluid multiple selection/>
onChange(e, { value } ) {
e.preventDefault();
// e.target.value DOESN'T WORK???!!!
}
So Dropdown from Semantic UI doesn't seem to provide this functionality out of the box. However, there's a decent way of achieving this. First, create a state like:
this.state = {
selected: [],
}
Then, bind a function to the Dropdown component like so:
<Dropdown
placeholder="Skills"
fluid
multiple
selection
options={options}
onChange={this.handleChange}/>
Once that's done, write the handleChange function to compare array lengths on every change. If state array is longer than whatever array you get from the dropdown, the item has been removed and you can check which one. Otherwise, dump the array into the state.
handleChange = (e, { value }) => {
if (this.state.selected.length > value.length) { // an item has been removed
const difference = this.state.selected.filter(
x => !value.includes(x),
);
console.log(difference); // this is the item
return false;
}
return this.setState({ selected: value });
};
You'll need a polyfill to run in IE8 and below though.
I'm creating a dynamic list component that basically renders a new empty text field every time something is added to the list. The component is as follows (it uses a custom TextFieldGroup but I don't think that code is necessary as nothing out of the ordinary is going on.):
const DynamicInputList = ({ inputs, onChangeArray, label, name, errors }) =>
{
const nonEmptyInputs = inputs.filter(input => {
return input !== "";
});
const lastIndex = nonEmptyInputs.length;
return (
<div>
{nonEmptyInputs.map((input, index) => {
return (
<Grid container spacing={16} key={index}>
<Grid item xs={12}>
<TextFieldGroup
label={label}
id={`${name}${index}`}
name={name}
value={input}
onChange={e => onChangeArray(e, index)}
errors={errors[name]}
/>
</Grid>
</Grid>
);
})}
<Grid container spacing={16} key={lastIndex}>
<Grid item xs={12}>
<TextFieldGroup
label={label}
id={`${name}${lastIndex}`}
name={name}
value={inputs[lastIndex]}
onChange={e => onChangeArray(e, lastIndex)}
errors={errors[name]}
/>
</Grid>
</Grid>
</div>
);
};
The onChangeArray function is as follows:
onChangeArray = (e, index) => {
const state = this.state[e.target.name];
if (e.target.value === "") {
state.splice(index, 1);
this.setState({ [e.target.name]: state });
} else {
state.splice(index, 1, e.target.value);
this.setState({ [e.target.name]: state });
}
};
Everything works fine except when a blank field is changed (begin to type) it immediately removes focus from any of the fields. I'm looking for a way to keep the focus on this field while still adding the new one below.
Thanks in advance.
The problem is that you're encountering the following sequence of events (assuming inputs length of 5):
React renders 1 field (key = 5)
User starts typing 'a'
Your state change is triggered
React renders 2 entirely new fields (key = 1 & value = 'a', key = 5) and trashes the old one (key = 5)
There are at least two solutions
Don't delete / trigger re-render of the original field
This is the cleaner solution.
First, you are directly mutating state in onChangeArray with your use of slice, because that does an in-place modification. Perhaps this isn't causing problems now, but it's a big React anti-pattern and could create unpredictable behavior.
Here's a quick fix:
onChangeArray = (e, index) => {
const state = [...this.state[e.target.name]];
if (e.target.value === "") {
state.splice(index, 1);
this.setState({ [e.target.name]: state });
} else {
state.splice(index, 1, e.target.value);
this.setState({ [e.target.name]: state });
}
};
And other options
I'll leave the reworking of the implementation to you, but the gist would be:
Use consistent key references, which means iterate over all your inputs and just skip the empty ones instead of finding the empty ones first and setting that arbitrary key to the total length. Of course, you should still render the first empty one.
You could consider rendering everything, and use CSS display:none attribute as needed to accomplish your desired UX
Leverage input's autofocus attribute
I assume your TextFieldGroup uses an input element. You could pass the autofocus attribute to the field you want to have focus. This answers your original question.
But this isn't recommended, as you're still needlessly running through those re-render cycles and then putting a patch on top.