I have a Checkbox component looking like this:
...
{values.map((value) => (
<div key={value}>
<div
className={`checkbox ${
checked && checked.includes(value) && "checked"
}`}
>
<div className="checkbox__box">
<input
{...register(name, {
required: required,
validate: CustomValidation,
})}
id={name + value}
type="checkbox"
name={name}
value={value}
defaultChecked={defaultData === value}
/>
</div>
</div>
<label htmlFor={name + value}>
<FormattedMessage id={value} />
</label>
</div>
))}
As you can see, the component relays on a checked prop array to add the checked class to the single checkbox element.
The component is being called like so:
<Checkbox
values={SourceOfFundsList}
name="source_of_funds__list"
md={12}
register={register}
errors={errors}
defaultData={
props.data.dataTree.customer.source_of_funds.source_of_funds__list
}
checked={source_of_funds__list__checked}
/>
The checked props is populated by watching the form state, like so:
const source_of_funds__list__checked = watch("source_of_funds__list");
This works as expected, adding the class to every item of the Checkbox once it is clicked. It also logs in the console correctly, by using useEffect, like so:
useEffect(() => {
console.log(source_of_funds__list__checked);
}, [source_of_funds__list__checked]);
The problem arises when I move to another route, and then I come back.
When I come back to the page, 2 things happen:
The state is persistent, and when inspecting the component, the defaultData contains the correct array with the checked items
The checked array is undefined, and therefore the UI doesn't display the correct state (all checkboxes looked unchecked, as class is not applied)
I have tried to add a second useEffect to populate the checked array on load, if the defaultData is there to start with, and it consoles correctly, but still the classes are not applied.
useEffect(() => {
if (props.data.dataTree.customer.source_of_funds.source_of_funds__list) {
source_of_funds__list__checked =
props.data.dataTree.customer.source_of_funds.source_of_funds__list;
}
console.log("source_of_funds__list__checked", source_of_funds__list__checked);
}, []);
I'm pretty sure it has to do with the render cycle and some mistake on my use of useEffect.
Any pointers on what could I be missing?
I think you misuse the react hook form. inside the useffect, use the 'setValue' to update the initial array, and instead of using 'watch', replace it with 'useWatch'. Try it out, hope it helps you!
Related
currently I have a signup form with 5 options but I'm trying to find a way to limit so the user can only select 2 options and in case the user selects a third option the first one would be unchecked, I had found a way of doing this in plain js but I haven't found a react way of doing it. This is what I have so far, would it be better to handle with plain js instead of react?
{iconsPool.map((src, index) => (
<Box className="test">
<input type="checkbox" className="iconsCheckbox" id={iconsPool.id} />
<label for={iconsPool.id}>
<img className="signupIcons" src={iconsPool[index].src} key={index} />
</label>
{console.log(iconsPool)}
</Box>
))}
This can be implemented with a state as an array with 2 elements.
Two items of the state Array will represent the index of selected items.
If an checkbox is clicked, that checkbox and the one clicked right before will be checked. (Therefore unchecking the one that was clicked even before that)
This can be done by pushing the index of newly clicked checkbox into the head of array, and removing the last item of the array.
When an checked checkbox is clicked again, (therefore it should be unchecked,) the index of the checkbox is searched from the state array, and removed by replacing that value with undefined
Below is code, as an example
...
const [checkedItems, setCheckedItems] = useState([undefined,undefined])
// When an Item is clicked
const onClickItem = (itemIndex:number, isSelected: boolean) =>{
if(isSelected){
setCheckedItems(c=>([itemIndex,c[0]]))
} else {
if(itemIndex === checkedItems[0]){
setCheckedItems(c=>([undefined,c[1]]))
} else if(itemIndex === checkedItems[1]){
setCheckedItems(c=>([c[0],undefined]))
}
}
}
I have tried finding the answer to this on StackOverflow and there are some related posts (e.g. React Child Component Not Updating After Parent State Change) but I want to understand why this is not working...
I have a React application that will display a layout of character cards (that is, each card displays a different character). It uses a child component, CharacterBoard, that lays out the CharacterCards, which would be a grandchild component. I pass the characters down from the App to the CharacterBoard as props, and CharacterBoard in turn maps these out the CharacterCards.
The problem is that I want the state of the character to change when I click on one of them. Specifically, I want the revealed field to change. However, even though the state change is reflected in the array of characters in the App (that is, the revealed field changes correctly), and the change is reflected in the array of characters in CharacterBoard, but not in CharacterCard. In fact, my mapping does not seem to be called at all in CharacterBoard when the props change.
Do I need to use something like getDerivedStateFromProps in CharacterBoard and set the state of that component and then use the state to map the values down to CharacterCard? If so, why?
In short (tl;dr), can you pass props on down through the component chain and map them out along the way and still have all changes reflected automatically?
Thanks for any guidance.
If it helps, the render method of my App is
render() {
const {state: {characters}} = this
return (
<div>
<header>
</header>
<main>
<CharacterBoard
onCardSelected={this.onCardSelected}
rowSize={logic.ROW_SIZE}
characters={characters}
cardSize={this.CARD_SIZE}/>
</main>
</div>
);
}
that of CharacterBoard is
render() {
const {props: {characters, rowSize, cardSize,onCardSelected}} = this
const rowUnit = 12 / rowSize
const cardLayout = characters
.map((character, i) => (
<Col xs={6} sm={rowUnit} key={character.name}>
<CharacterCard
onCardSelected = {onCardSelected}
key={i + Math.random()}
character={character}
cardSize={cardSize}
/>
</Col>
)
)
return (
<div>
<Container>
<Row>
{cardLayout}
</Row>
</Container>
</div>
)
}
and finally CharacterCard has this render method
render() {
const {props: {character, cardSize}} = this
const {thumbnail, revealed} = character
const imgURL = `${thumbnail.path}/${cardSize}.${thumbnail.extension}`
const topCardClass = classNames('characterCard__card-back', {'characterCard__card-back--hidden': revealed})
console.log(revealed)
return < a href="/#" onClick={this.onCardSelected}>
<div className='characterCard__card'>
<div className={topCardClass}>
<img src="/images/card_back.png" alt=""/>
</div>
< div className='characterCard__card-front'>< img alt=''
src={imgURL}/>
</div>
</div>
</a>
}
Doh! A simple forgetting to setState in App. Knowing that it should work made me go back through the code one more time and see that, indeed, it was a stupid error on my part.
I'm trying to implement the Google Tasks Add Task feature (See pic for reference). Basically, when you click on Add Task button, it opens an input field which autosaves as you type. I'm trying to implement this in React.js.
class App extends Component {
state = {
showInput: false,
addTaskInput: ''
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
render() {
const {showInput, addTaskInput} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
/> }
<TodoList addItem={}/>
</div>
);
}
}
Here is my code. App is my main component. TodoList is the component which has the whole list of Todos. What I am going to do is:
When I type something in the input, it fires onChange and sets the state. Inside onChange I would also change the addItem prop which would re-render TaskList. This doesn't seem very optimal because of unnecessary re-renders. I'm thinking of changing addItem prop when focus on input is removed.
How do I go about doing the latter? Alternative approaches are welcome.
I would say it depends on how much your app is doing; if it's just a simple app saving onChange is not bad, but if a lot else is going on, yeah you should optimise.
onBlur
mix keystroke and debounce (my favorite)
when input is more than a set length
Trying to build a react component where I need to control checked status of checboxes and select options when change event occurs. But I don't know how it is possible to get value of the checked checkbox(es) and set the state.
We're using custom data-binding. On page load, we're assigning selected value of the select, with jQuery.
Programmatically changing value of the select must update matching check-boxes.
When user checks/unchecks a checkbox, corresponding value must be toggled on the select.
With jQuery I would loop trough check-boxes and build array with checked values then assign this value to the select on checkbox change. And when select change event is triggered, I would uncheck all check-boxes and check the ones matching selected items.
This is my simplified code.
state = {
items: [
{Key: 1, Value: "A"},
{Key: 29, Value: "Z"}
],
selected: [1, 29]
}
function onSelectChange(){
// Update checked checkboxes
}
function onCheckboxChange(){
// Update selected options
}
<div>
<select multiple onChange={onSelectChange} className="hidden">
{this.state.items.map((item, i) =>
<option value={item.Key}>{item.Value}</option>
)}
</select>
<div className="checkboxes">
{this.state.items.map((item, i) =>
<input
type="checkbox"
key={i}
checked={this.state.selected.indexOf(item.Key) >= 0}
onChange={onCheckboxChange} />
)}
</div>
</div>
You would use this.setState({}) inside the event handler to update the state of a component in React. This triggers a rerender in React which allows you to query the the updated state (this.state.selected).
Be advised that this.setState() expects an immutable object, so you should never change the previous, but always set a new state object!
Answer to comment:
For selectItem:
onSelectChange = event => this.setState({selected:event.target.value})
and for checkboxes (note the prevState):
onCheckboxChange = item => event => this.setState(({selected,...prevState})=> ({
...prevState,
selected: event.target.checked? selected.concat(item): selected.filter(it=> it!== item)
}))
and usage:
{this.state.items.map((item, i) =>
<input
type="checkbox"
key={i}
checked={this.state.selected.indexOf(item.Key) >= 0}
onChange={onCheckboxChange(item)} />
)}
This has the downside that it will create a new function on each rerender, so it's better to create a custom CheckboxItem and pass the item to it and use a handleClick.
onChange function give event where you could check whether the select box is being checked or not using this you can update your state accordingly.
function onCheckboxChange(e){
console.log("checked", e.target.checked);
// Then, on the basis of boolean you can update your state
}
This is my Creatable component:
function optionsForSelect(field) {
return field
.values
.map((fieldOption) => {
return {value: fieldOption, label: fieldOption};
});
}
function PatientSelectInput({field, options, value, onChange, disabled}) {
const className = field.id + '-select';
return (
<label className={cx('input-label', className)}>
<div className="label-text">{field.displayName}</div>
<Creatable
value={value}
onChange={(selectedValue) => onChange(selectedValue ? selectedValue.value : null)}
disabled={disabled}
onBlurResetsInput={false}
onCloseResetsInput={false}
options={options} />
</label>
);
}
It is a functional component. When it renders, I can create a new option but when I hit tab or enter or click on the automatically generated "Create Option..." the newly created option disappears. I just want the default behavior.
What am I missing?
Unfortunately this version of react-select mutates it's options prop.
This is very bad coding and is fixed in version 2: https://github.com/JedWatson/react-select/issues/2484
The issue you're experiencing is coming from the way you construct the options. Your function creates a new object on each render, replacing the array containing the option you just created.
If you can't update your version I suggest you save the output of optionsForSelect to this.state.options, then pass the state variable into react-select. The upshot of doing this is you still have the ability to mutate the state of your element and limits the impact of the mutate.