React Redux way of handling enable-disable of DOM elements - reactjs

I have a form where i have to enable/disable certain DOM elements based on the state of other DOM elements. For e.g. I have a radio button, on the click of which a drop down should be enabled.
Now for implementing this, should I again follow the redux way of disposing an action when the radio is clicked and then within the reducer change the state and then enable/disable the dropdown?
Does redux-form in any way simplify this process? What is the best practice to implement this in a react-redux setup?

I use redux-form for conditional inputs. For example, I have a checkbox that when checked, should display a text area to explain the true input. That looks like this:
<div className="checkbox">
<label for="trueInput">
<input type="checkbox" {...trueInput} />
Is this input true?</label>
</div>
<div className={!trueInput.value ? 'conditional-input' : ''}>
<label for="trueInputExplanation">Why is this input true?</label>
<input className="form-control" {...trueInputExplanation} />
</div>
The class .conditional-input has styling to hide the element. I'd imagine you could do this the same way for disabled, by way of using a ternary function that returns true or false, depending on the conditions you need.

Redux Form keeps track of everything in the store. (It's easy to see what's going on with the Redux Chrome dev tool.) Say I have a master checkbox whose enablement allows me to toggle a slave checkbox. So I want to put the master state read from the form into props:
const mapStateToProps = (state) => {
const isMasterChecked = state.mySetting.isMasterChecked;
const form_mySetting = state.form.mySetting;
const form_isMasterChecked = form_mySetting ? form_mySetting.values.isMasterChecked : null;
return {
isMasterChecked,
form_isMasterChecked
}
};
and then for the form you have
const {isMasterChecked, form_isMasterChecked} = props;
const shouldDisable_slaveCheckbox= () => {
if (form_isMasterChecked == null) return isMasterChecked; // the form is not fully built yet, so use "real" store value instead of reading the form via store
return form_isMasterChecked;
};
<Field name="isSlaveChecked" component="input" type="checkbox" disabled={shouldDisable_slaveCheckbox() ? "" : "disabled"}/>
Use sparingly, as this approach may cause entire form redraw.

Related

How should I show editable data after it has been submitted with a form so that I can reuse a form components?

If I was making a website where the user submits some data using a form and then the user is redirected to a page with a unique URL where they can view this data, what is the best way to display this data to user in a way in which they can make updates to it?
To give some context I am using react and right now I am showing the same form again on the view data page, but as the forms have slightly different display options, such as some fields being disabled and some extra buttons I am repeating the same form in two seperate components which seems to go against the react way of doing things.
The way I would personally do it is have all your form elements in a single component and use it for the create and edit page, then just pass a "isCreating" or "isEditing" prop, or something similar. In that component, you can then conditionally disable your inputs.
Component
const SomeForm = ({ isEditing }) => {
const onSubmit = () => {
if (isEditing) {
// Make request to update endpoint
} else {
// Make request to create endpoint
}
};
return (
<>
<form onSubmit={onSubmit}>
<input disabled={isEditing} />
<input />
</form>
</>
);
};
Create Page Usage
<MyForm />
Edit Page Usage
<MyForm isEditing />
Note that if you're doing a ton of conditional logic/rendering based on whether you're creating or editing a form, it might be better to split it into two separate form components. If you're simply disabling some inputs on the edit page and doing different submit logic, then I think this is fine.

How to conditionally render extra form options on checking dynamically generated check boxes in React?

can anyone help, i'm trying this in React but i'm really stuck.
I have 3 checkboxes on a child component that i have rendered dynamically using an array of options in state (in the parent component):
What i need to be able to do is, as per the below image: click on each checkbox and get further options.
https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/d/3/d385407399b0fa130741e653c9650ff9953282df.png
The checked options and the sub options (not just dropdowns, the third checkbox has input fields) all need to be available in state so i can collect them up and post them to a database
What i have so far is the below:
Array of options in state:
sowType: [
"ProductSow",
"Teradata Customer SOW",
"Custom Professional Services SOW"
]
Final rendered checkboxes:
https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/4/5/45702836b84abe764917f4bdf5172258f4d3e39c.png
My problem is, i don't know what to do next. I have dynamically rendered the initial 3 check boxes but i don't know how to add the conditional rendering to get the sub boxes to appear on clicking the check boxes) and add the info selected from them to state.
i.e. can i only add the conditional rendering on checkboxes that have NOT been dynamically rendered from map or is there a way to do it, in which case how is it done?
My code so far is as below, it may not be the best setup for what i am trying to do:
Can anyone help??
This is the reference to the component in the parent with the props passed down:
<SowType
title={"SOW Type"}
setName={"SOW Type"}
subtitle={"What type of SOW do you want to generate?"}
type={"checkbox"}
controlFunc={this.handleSOWTypeCheckbox}
options={this.state.sowType}
selectedOptions={this.state.sowTypeSelectedOption}
/>
This is the child component dynamically rendering the array of items from state:
class SOWType extends React.Component {
render() {
// console.log(this.props);
return (
<div className="form-group">
<label htmlFor={this.props.name} className="form-label">
{this.props.title}
<h6>{this.props.subtitle}</h6>
</label>
<div className="checkbox-group">
{this.props.options.map(option => {
return (
<label key={option}>
<input
className="form-checkbox"
name={this.props.setName}
onChange={this.props.controlFunc}
value={option}
checked={this.props.selectedOptions.indexOf(option) > -1}
type={this.props.type}
/>
{option}
</label>
);
})}
</div>
</div>
);
}
}
This is the method i am currently using in the parent when a checkbox is clicked in the child component (this basically takes the selected option from the array in state and puts the checked options into another array in state called 'sowTypeSelectedOption: [ ]',
handleSOWTypeCheckbox(e) {
const newSelection = e.target.value;
let newSelectionArray;
if (this.state.sowTypeSelectedOption.indexOf(newSelection) > -1) {
newSelectionArray = this.state.sowTypeSelectedOption.filter(
item => item !== newSelection
);
} else {
newSelectionArray = [...this.state.sowTypeSelectedOption, newSelection];
}
this.setState(
{
sowTypeSelectedOption: newSelectionArray
} /*() =>
console.log("sow Type Selection: ", this.state.sowTypeSelectedOption) */
);
}
If I understand your question correct, you want the additional sub menu to show once the checkbox is selected, then you can use conditional rendering. Just show/hide the menu on select or deselect of checkbox like so.
{ this.props.selectedOptions.indexOf(option) > -1 ? (
<h5>submenu options component renders here</h5>
) : " "
}

Setting a checkbox "check" property in React

I am having a very annoying issue with React and checkboxes. The application I am working with requires a list of checkboxes that represent settings that are persisted in the back-end. There is an option to restore the settings to their original state.
At first, I created a component that has an object like a map of settings. Each setting has a key and a boolean value. Hence:
{
bubbles: true,
gregory: false
}
Is to be represented as:
<input type="checkbox" value="bubbles" checked="checked" />
<input type="checkbox" value="gregory" />
Now, it seems React is ignorant about how a checkbox is supposed to work. I don't want to set the checkboxes' values, I want the "checked" property.
Yet, if I try something like this:
<input
type="checkbox"
value={setting}
checked={this.settings[setting]}
onChange={this.onChangeAction.bind(this)}
/>
I get this warning:
Warning: AwesomeComponent is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: [some useless docs page I read several times to no avail]
So I decided to create another component to wrap each individual checkbox and I got this:
<input
type="checkbox"
checked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Now the checked is a property present directly in my state.
This yields the same warning, so I tried using defaultChecked:
<input
type="checkbox"
defaultChecked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Which makes the warning disappear, but now it is unable to reset the checked value to the default one. So I tried playing with the method componentWillReceiveProps, this way I am quite sure my state is correct, this.state.checked is correct and the component renders again.
And it does. But the checkbox remains as it was originally.
For now I left that ugly warning and I am using checked.
How do I fix this thing so the warning goes away?
I was thinking that perhaps there is a way to force-re-render the component, so it captures the new defaultChecked value and uses it. But I don't know how to do that. Perhaps suppress the warning only for this component? Is that possible? Perhaps there is something else that can be done?
The problem arises if you set the checked property of your checkbox to null or undefined.
Those are "falsy" values in JS. However, React treats a value of null as if the property was not set at all. Since the default state of a checkbox is unchecked, everything will work fine though. If you then set checked to true, React thinks the property suddenly comes into existence! This is when React figures you switched from uncontrolled to controlled, since now the prop checked exists.
In your example, you can get rid of this warning by changing checked={this.settings[setting]} to checked={!!this.settings[setting]}. Notice the double bang (!!). They will convert null or undefined to false (and leave true alone), so React will register your checked property with a value of false and start off with a controlled form component.
I had this problem too and I, too, read the docs about controlled-components several times to no avail, but I finally figured it out, so I thought I'd share. Also, since version 15.2.0, normal inputs are triggered to be controlled by setting value, while checkboxes are initialized as controlled by setting checked, regardless of their value property, which added a bit to the confusion.
Amoebe's answer is correct, but I think there's a cleaner solution than the double bank (!!). Simply add a defaultProps property with value false for checked prop of your Checkbox component:
import React from 'react';
const Checkbox = ({checked}) => (
<div>
<input type="checkbox" checked={checked} />
</div>
);
Checkbox.defaultProps = {
checked: false
};
export default Checkbox;
Basically, the defaultChecked means you don't want to control the input – it just renders with this value and then there is no way to control it. Also, value shouldn't be used, but checked instead, so your second code should be correct. And you shouldn't use them both simultaneously.
<input
type="checkbox"
checked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Can you create a small fiddle with this behaviour?
Here is an answer using hooks should you choose to convert the class component to a functional one...
export default Checklist = () => {
const [listOfItems, setListOfItems] = useState([
{name: 'bubbles', isChecked: true},
{name: 'gregory', isChecked: false}
]);
const updateListOfItems = (itemIndex, isChecked) => {
const updatedListOfItems = [...listOfItems];
updatedListOfItems[itemIndex].isChecked = isChecked;
setListOfItems(updatedListOfItems);
}
return (
{listOfitems.map((item, index) =>
<index key={index} type="checkbox" checked={item.isChecked} onChange={() => updateListOfItems(index, !item.isChecked)} />
)}
)
}
You can assign your data to the state and then make use of the checked property associated with the individual checkbox to set the state as
{ this.state.data.map(function(item, index) {
return (
<input type="checkbox" checked={item.value} onChange={this.handleChange.bind(this, index)}/>
);
}.bind(this))
}
Sample Data in state is as
data: [{name:'bubbles', value: true}, {name:'gregory', value: false}]
JSFIDDLE
What finally worked, combining other answers:
const [categories, setCategories] = useState(
[
{
field: 'Label 1',
checked: true
},
{
field: 'Label 2',
checked: false
},
{
field: 'Label 3',
checked: false
}
]
)
const updateList = (item, isChecked) => {
const updatedList = [...categories];
updatedList[item].checked = isChecked;
setCategories(updatedList);
}
... in your markup:
{
categories.map((item, index) =>
<div key = {item.field}>
<label>
<span>{item.field}</span>
<input key={index}
type="checkbox"
checked={item.checked}
onChange={() => updateList(index, !item.checked)}
/>
</label>
</div>
)
}

How to clear the text field in formsy material ui React

Hi I am using React 14 and writing it in ES6. I am using formsy-material-ui for form validations. There is a scenario where I want to clear the value of text field on click of a button.
I tried the following code
<FormsyText
name="email"
ref="email"
validations="isEmail"
validationError="Invalid Email"
hintText="Email"
value={this.state.emailValue}
/>
And on click of the button, I am executing the following line of code
this.setState({emailValue : ''});
But the text field is not getting cleared.
How to clear it.
Pls help.
So, if you were using a controlled input (maybe using directly the TextField from Material-ui) - your code would be right, however the FormsyText component handle it's value internally.
If you pass a value or defaultValue it'll just be used when it's rendered, you can check it here.
I only see one way to clear the value now, in an imperative style.
this.refs.email.setState({ value: "" })
Note: I suggest you to change the way you're using the ref. Using refs with a string is deprecated and probably will be removed in the future. Instead you should pass a function that's gonna receive that component. https://facebook.github.io/react/docs/more-about-refs.html
Example:
<FormsyText
name="email"
ref={(node) => this._emailText = node}
validations="isEmail"
validationError="Invalid Email"
hintText="Email"
value={this.state.emailValue}
/>
//And to clear it
this._emailText.setState({ value: "" })
Try reset field after setState:
this.setState({emailValue : ''});
this.refs.email.reset()
Also you can reset all form.
this.refs.form.reset()
const formRefdeposit = useRef(null);
<Formsy
onValidSubmit={handleSubmit}
onValid={enableButton}
onInvalid={disableButton}
ref={formRef}
>
....Form Fields
</Formsy>
if you have class in your js file then use
this.formRef.current.reset();
if without class then use
formRef.current.reset();

I am using Redux. Should I manage controlled input state in the Redux store or use setState at the component level?

I have been trying to figure out the best way to manage my react forms. I have tried to use the onChange to fire an action and update my redux store with my form data. I have also tried creating local state and when my form gets submitted I trigger and action and update the redux store.
How should i manage my controlled input state?
I like this answer from one of the Redux co-authors:
https://github.com/reactjs/redux/issues/1287
Use React for ephemeral state that doesn't matter to the app globally
and doesn't mutate in complex ways. For example, a toggle in some UI
element, a form input state. Use Redux for state that matters globally
or is mutated in complex ways. For example, cached users, or a post
draft.
Sometimes you'll want to move from Redux state to React state (when
storing something in Redux gets awkward) or the other way around (when
more components need to have access to some state that used to be
local).
The rule of thumb is: do whatever is less awkward.
That is, if you're sure that your form won't affect global state or need to be kept after your component is unmounted, then keep in the react state.
You can use the component's own state. And then take that state and give it as an argument to the action. That's pretty much the "React way" as described in the React Docs.
You can also check out Redux Form. It does basically what you described and links the form inputs with Redux State.
The first way basically implies that you're doing everything manually - maximum control and maximum boilerplate. The second way means that you're letting the higher order component do all the work for you. And then there is everything in between. There are multiple packages that I've seen that simplify a specific aspect of form management:
React Forms -
It provides a bunch of helper components to make form rendering and validation more simple.
React JSON schema -
Allows one to build an HTML form from a JSON schema.
Formsy React -
As the description says: "This extension to React JS aims to be that "sweet spot" between flexibility and reusability."
Update: seems these days Redux Form is being replaced with:
React Final Form
And one more important contender in the space that's worth checking out is:
Formik
Tried out React Hook Form in my last project - very simple, small footprint and just works:
React Hook Form
TL;DR
It's fine to use whatever as it seems fit to your app (Source: Redux docs)
Some common rules of thumb for determing what kind of data should be
put into Redux:
Do other parts of the application care about this data?
Do you need to be able to create further derived data based on this original data?
Is the same data being used to drive multiple components?
Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
These questions can easily help you identify the approach that would be a better fit for your app. Here are my views and approaches I use in my apps (for forms):
Local state
Useful when my form has no relation to other components of the UI. Just capture data from input(s) and submits. I use this most of the time for simple forms.
I don't see much use case in time-travel debugging the input flow of my form (unless some other UI component is dependent on this).
Redux state
Useful when the form has to update some other UI component in my app (much like two-way binding).
I use this when my form input(s) causes some other components to render depending on what is being input by the user.
Personally, I highly recommend keeping everything in the Redux state and going away from local component state. This is essentially because if you start looking at ui as a function of state you can do complete browserless testing and you can take advantage of keeping a reference of full state history (as in, what was in their inputs, what dialogs were open, etc, when a bug hit - not what was their state from the beginning of time) for the user for debugging purposes. Related tweet from the realm of clojure
edited to add: this is where we and our sister company are moving in terms of our production applications and how we handle redux/state/ui
Using the helper libraries is just more quick and avoid us all the boilerplate. They may be optimized, feature rich ...etc. As they make all different aspects more of a breeze. Testing them and making your arsenal as knowing what's useful and better for the different needs, is just the thing to do.
But if you already implemented everything yourself. Going the controlled way. And for a reason you need redux. In one of my projects. I needed to maintain the form states. So if i go to another page and come back it will stay in the same state. You only need redux, if it's a mean for communicating the change to multiple components. Or if it's a mean to store the state, that you need to restore.
You need redux, if the state need to be global. Otherwise you don't need it. For that matter you can check this great article here for a deep dive.
One of the problems that you may encounter! When using the controlled inputs. Is that you may just dispatch the changements at every keystroke. And your form will just start freezing. It became a snail.
You should never directly dispatch and use the redux flux, at every changement.
What you can do, is to have the inputs state stored on the component local state. And update using setState(). Once the state change, you set a timer with a delay. It get canceled at every keystroke. the last keystroke will be followed by the dispatching action after the specified delay. (a good delay may be 500ms).
(Know that setState, by default handle the multiple successive keystroke effectively. Otherwise we would have used the same technique as mentioned above (as we do in vanilla js) [but here we will rely on setState])
Here an example bellow:
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
// here how you implement the delay timer
clearTimeout(this.onInputsChangeTimeoutHandler); // we clear at ever keystroke
// this handler is declared in the constructor
this.onInputsChangeTimeoutHandler = setTimeout(() => {
// this will be executed only after the last keystroke (following the delay)
if (typeof onInputsChange === "function")
onInputsChange(this.state.inputs, roomId, evt);
}, 500);
})
}
You can use the anti-pattern for initializing the component using the props as follow:
constructor(props) {
super(props);
const {
name,
description
} = this.props;
this.state = {
inputs: {
name,
description
}
}
In the constructor or in the componentDidMount hook like bellow:
componentDidMount () {
const {
name,
description
} = this.props;
this.setState({
...this.state,
inputs: {
name,
description
}
});
}
The later allow us to restore the state from the store, at every component mounting.
Also if you need to change the form from a parent component, you can expose a function to that parent. By setting for setInputs() method that is binded. And in the construction, you execute the props (that is a getter method) getSetInputs(). (A useful case is when you want to reset the forms at some conditions or states).
constructor(props) {
super(props);
const {
getSetInputs
} = this.props;
// .....
if (typeof getSetInputs === 'function') getSetInputs(this.setInputs);
}
To understand better what i did above, here how i'm updating the inputs:
// inputs change handlers
onNameChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
name: value
},
evt
);
}
onDescriptionChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
description: value
},
evt
);
}
/**
* change = {
* name: value
* }
*/
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
clearTimeout(this.onInputsChangeTimeoutHandler);
this.onInputsChangeTimeoutHandler = setTimeout(() => {
if (typeof onInputsChange === "function")
onInputsChange(change, roomId, evt);
}, 500);
})
}
and here is my form:
const {
name='',
description=''
} = this.state.inputs;
// ....
<Form className="form">
<Row form>
<Col md={6}>
<FormGroup>
<Label>{t("Name")}</Label>
<Input
type="text"
value={name}
disabled={state === "view"}
onChange={this.onNameChange}
/>
{state !== "view" && (
<Fragment>
<FormFeedback
invalid={
errors.name
? "true"
: "false"
}
>
{errors.name !== true
? errors.name
: t(
"You need to enter a no existing name"
)}
</FormFeedback>
<FormText>
{t(
"Enter a unique name"
)}
</FormText>
</Fragment>
)}
</FormGroup>
</Col>
{/* <Col md={6}>
<div className="image">Image go here (one day)</div>
</Col> */}
</Row>
<FormGroup>
<Label>{t("Description")}</Label>
<Input
type="textarea"
value={description}
disabled={state === "view"}
onChange={this.onDescriptionChange}
/>
{state !== "view" && (
<FormFeedback
invalid={
errors.description
? "true"
: "false"
}
>
{errors.description}
</FormFeedback>
)}
</FormGroup>
</Form>

Resources