I have the following functions which renders a set of fields:
const renderFields = (data: CustomerDetails) => {
return Object.keys(data).map((s: string) => {
const key = s as keyof CustomerDetails
return Object.keys(data[key]).map(fieldKey => {
const name = `${key}.${fieldKey}`
const id = `customer-details-form-${fieldKey}`
return (
<FormItem key={name}>
<Label htmlFor={id}>{camelCaseToTitleCase(fieldKey)}</Label>
<Field name={`${key}.${fieldKey}.value`} validate={validate(fieldKey)}>
{props =>
<TextField
disabled={
data.contact[fieldKey] !== undefined
? data.contact[fieldKey].disabled
: true
}
// disabled={
// data.contact[fieldKey]?.disabled ?? true
// }
{...props}
data-bdd={`customer_details_field_${fieldKey}`}
id={id}
/>
}
</Field>
</FormItem>
)
})
})
}
however the disabled status is dependent on the redux structure at the moment. Is there a way to make fields disabled/enabled on click without having to dispatch an action saying which fields should be enabled or not?
Using local state here would be appropriate. Here's a simplified example:
function WrappedField(props){
const [disabled, setDisabled] = useState(false);
return <Field {...props} disabled={disabled} onClick={() => setDisabled(!disabled)} />
}
You can use the wrapped version wherever you would have used the Field component.
Related
I'm using React to build a form and I'm trying to filter a list with the SearchInput (which works the same as TextInput) located in the child component Header. But everytime I type a character the SearchInput gets unfocused
function index() {
const list = [//data\\]
const [search, setSearch] = useState("");
const [filteredResults, setFilteredResults] = useState([]);
const searchItems = (searchValue) => {
setSearch(searchValue);
if (search !== "") {
const filteredData = partners.filter((item) => {
return Object.values(item)
.join("")
.toLowerCase()
.includes(search.toLowerCase());
});
setFilteredResults(filteredData);
} else {
setFilteredResults(partners);
}
};
const Header = () => (
<Box>
<SearchInput
placeholder="Search"
value={search}
onChange={(e) => searchItems(e.target.value)}
/>
</Box>
);
return (
<Parent
headerContent={<Header />}
>
<Box>
<Table data={search.length > 1 ? filteredResults : list} />
</Box>
</Parent>
);
}
export default index;
Oh, I think I can see the problem now - it's the way you're rendering the <SearchInput /> component. You're inadvertantly creating a new functional component on every render. Either inline the Header directly into the Parent control's headerContent property, or create an entirely separate component:
const Header = ({ search, onSearchChange }) => {
const handleChange = (e) => onSearchChange(e.target.value);
return (
<Box>
<SearchInput
placeholder="Search"
value={search}
onChange={handleChange}
/>
</Box>
);
}
function index() {
// ----- 8< -----
return (
<Parent
headerContent={<Header search={search} onSearchChange={searchItems} />}
>
{/* ... */}
</Parent>
);
}
While you're there, you have a subtle bug with your comparison - it looks like you're searching your partners effectively as a list of strings; but, since you're joining them, if you had partners with the names:
'one'
'two'
You're creating a search string as 'onetwo' - so searching for 'et' would match, even though you don't actually have a partner matching that. You can fix that by just checking each partner individually... something like:
const searchItems = (searchValue) => {
setSearch(searchValue);
if (search !== "") {
const searchValueLower = searchValue.toLowerCase();
const filteredData = partners.filter((item) => {
return Object.values(item)
.some(item => item.toLowerCase().includes(searchValueLower);
});
setFilteredResults(filteredData);
} else {
setFilteredResults(partners);
}
};
I'm using react and material ui. I need to get all checkboxes in a list checked by checking one checkbox in a parent component (i.e. I need to select all). I pass down the correct value of the parent checkbox via props, but that doesn't trigger visual changes in its children, even though their values do change to 'true'. I'm sure that the values are correct, because I tried logging them to the console.
Here's the parent:
const [checkboxValue, setCheckboxValue] = useState(false)
<Checkbox value={checkboxValue} onChange={e => setCheckboxValue(e.target.checked)}/>
{elements.map(element => (<Element selectAll={checkboxValue}/>))}
And here's the child:
function Element(props) {
const [checkboxValue, setCheckboxValue] = useState(false)
useEffect(() => {
setCheckboxValue(props.selectAll)
}, [props.selectAll])
return (
<Checkbox value={checkboxValue} onChange={e => setCheckboxValue(e.target.checked)}/>)
}
you are using value instead of checked
function SingleCheckBox({ selectAll }) {
const [checkboxValue, setCheckboxValue] = useState(selectAll);
useEffect(() => {
setCheckboxValue(selectAll);
}, [selectAll]);
return (
<Checkbox
checked={checkboxValue}
onChange={(e) => setCheckboxValue(e.target.checked)}
/>
);
}
export default function App() {
const [checkboxValue, setCheckboxValue] = useState(false);
return (
<div className="App">
<Checkbox
checked={checkboxValue}
onChange={(e) => {
setCheckboxValue(e.target.checked);
}}
/>
<div>
<h2>Other checkboxes</h2>
<SingleCheckBox selectAll={checkboxValue} />
</div>
</div>
);
}
https://codesandbox.io/s/angry-buck-yp7f3z?file=/src/App.js
I want to use search filter in sidebar so i created search filter and implemented but its now working shown only input field. and i stuck with how to map the items to search filter.
here i attached some of my working code:
state = {
search : ""
}
onchange = e =>{
this.setState({search : e.target.val })
}
const Menu = ({ resources, onMenuTap, translate }) => {
const {search}=this.state;
if (search !== "" && resources.name.toLowerCase().indexof(search.toLowerCase()) === -1 ){
return null
}
onchange = e =>{
this.setState({search : e.target.val })
}
return (
<Card className={classes.sidebarCard}>
{/* Search */}
<input placeholder="Search" onChange={this.onchange} />
//
....
//
);
};
for your onchange, try to use that :
onchange = e =>{
this.setState({search : e.target.value })
}
and in your search input :
{/* Search */}
<input placeholder="Search" value={this.state.search} onChange={(e) => this.onchange(e)} />
Because you're using functional component. You need to use useState hook:
const [search, setState] = useState("");
if (
search !== "" &&
resources.name.toLowerCase().indexof(search.toLowerCase()) === -1
) {
return null;
}
onchange = (e) => {
setState(e.target.val);
};
The attached sanbox code is based on your code. But it seems it doesn't render:
You are using functional component. So you need to change your code in this way:
const Menu = ({ resources, onMenuTap, translate }) => {
const [search, setSearch] = useState(""); //<-- this is your state
const onchange = e =>{
setSearch(e.target.val); //set the state
}
if (search !== "" && resources.name.toLowerCase().indexof(search.toLowerCase()) === -1 ){
return null
}
return (
<Card className={classes.sidebarCard}>
{/* Search */}
<input placeholder="Search" onChange={onchange} value={search} /> //<-- set value equal to search state
{permissions === "Administrator" && (
<>
<MenuItemLink
className={classes.MenuItemLink}
activeClassName={classes.active}
to="/_apps"
primaryText={translate("easyadmin.apps")}
leftIcon={
<AppsOutlined fontSize="small" className={classes.iconColor} />
}
onClick={onMenuTap}
/>
<MenuItemLink
className={classes.MenuItemLink}
activeClassName={classes.active}
to="/_entitys"
primaryText={translate("easyadmin.customobjects")}
leftIcon={
<SettingsOutlinedIcon
fontSize="small"
className={classes.iconColor}
/>
}
onClick={onMenuTap}
/>
)}
);
};
I'm passing antd's component (FormFieldInput) to redux-form's Field component. Everything works well with "text" and "telephone" input types. Normalize number function stops working as soon as I change Field's type to "number".
I can see that FormFieldInput component is triggered only when I input numbers. When I'm typing alphabetic characters into the input FormFieldInput console log at the top of the function is not returning new values.
Normalizer:
const normalizeNumber = (value /* , previousValue */) => {
console.log('---input', value);
if (!value) {
return value;
}
const onlyNums = value.replace(/[^\d]/g, '');
console.log('---output', onlyNums);
return onlyNums;
};
Usage:
<Field
name="size"
type="number"
component={FormFieldInput}
label="Size"
placeholder="Size"
required
validate={[required, maxLength255]}
normalize={normalizeNumber}
/>
FormFieldInput:
const FormFieldInput = ({
input,
label,
type,
placeholder,
required,
meta: { touched, error, warning }
}) => {
const [hasError, setHasError] = useState(false);
const [hasWarning, setHasWarning] = useState(false);
console.log('---input', input);
useEffect(() => {
setHasError(!!error);
}, [error]);
useEffect(() => {
setHasWarning(!!warning);
}, [warning]);
const ref = createRef();
return (
<div className="form-item">
<div className="form-item__label">
{`${label}:`}
{required && <span style={{ color: 'red' }}> *</span>}
</div>
<div className={`form-item__input`}>
<AntInput
{...input}
ref={ref}
placeholder={placeholder}
type={type}
/>
</div>
</div>
);
};
export const AntInput = React.forwardRef((props, ref) => {
const { placeholder, type, suffix } = props;
console.log('---type', type);
return <Input {...props} ref={ref} placeholder={placeholder} type={type} suffix={suffix} />;
});
I expect all input data to go through normalize function, but somehow alphabetic characters are going through it.
Novice.
I have a class Address which I ultimately want to split into a presentational component and container. It all works as is but when I move this particular function outside the render function from initially within the actual async.select form field -
onSuburbChange = (value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
...I find I am getting hit with a number of errors based on the the fact that they are unreferenced.
The error I get is
address.jsx:56 Uncaught ReferenceError: input is not defined
If I comment this line out I get the same type of error on updatePostcodeValue.
Here is the entire address file. As you can see it would be good to move the presentational section in render off to another file but I need to move all the functions to the outside of the render function.
NOTE: I have commented out where the function orginal sat so anybody who has a crack at this question knows where it was and also where I intended to move it...
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Field, change } from 'redux-form'
import { Col, Panel, Row } from 'react-bootstrap'
import Select from 'react-select'
import { getSuburbs } from './actions'
import FormField from '../formComponents/formField'
import TextField from '../formComponents/textField'
import StaticText from '../formComponents/staticText'
import { CLIENT_FORM_NAME } from '../clients/client/client'
export class Address extends Component {
static contextTypes = {
_reduxForm: PropTypes.object.isRequired,
}
constructor(props, context) {
super(props, context)
this.state = {
selectedSuburb: null,
}
}
// Manage Select for new data request - for suburbs.
handleSuburbSearch = (query) => {
console.group('handleSuburbSearch')
console.log('query', query)
const { addressData } = this.props
console.log('addressData', addressData)
const companyStateId = addressData.companyStateId
console.log('companyStateId', companyStateId)
if (!query || query.trim().length < 2) {
console.log('no query; bailing!')
console.groupEnd()
return Promise.resolve({ options: [] })
}
const queryString = {
query: query,
companyStateId: companyStateId,
}
console.log('queryString', queryString)
return getSuburbs(queryString)
.then(data => {
console.log('Suburbs returned!', data)
console.groupEnd()
return { options: data }
})
}
//I HAVE MOVED IT TO HERE....
onSuburbChange = (value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
render() {
const { addressData, updatePostcodeValue } = this.props
const { value } = this.state
const sectionPrefix = this.context._reduxForm.sectionPrefix
return (
<Panel header={<h3>Client - Address Details</h3>}>
<Row>
<Field component={TextField}
name="address1"
id="address1"
type="text"
label="Address Line 1"
placeholder="Enter street 1st line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
<Field component={TextField}
name="address2"
id="address2"
type="text"
label="Address Line 2"
placeholder="Enter street 2nd line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
// just the props we want the inner Typeahead textbox to have
const { name, onChange } = input
const onStateChange = (state) => {
console.log('onStateChange', state)
onChange(state)
}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select
name={name}
onChange={onStateChange}
placeholder="Select state"
valueKey="id"
options={addressData.states}
labelKey="stateLabel"
optionRenderer={option => `${option.stateShortName} (${option.stateName})`}
value={input.value}
selectValue={Array.isArray(input.value) ? input.value : undefined}
/>
</FormField>
)
}}
name="state"
id="state"
label="State."
fieldCols={6}
labelCols={3}
controlCols={6}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
const { name, value, onChange, onBlur, onFocus } = input
const inputProps = {
name,
value,
onChange,
onBlur,
onFocus,
}
{/*onSuburbChange = (value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}*/}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select.Async
{...inputProps}
onChange={this.onSuburbChange}
valueKey="id"
labelKey="suburbName"
loadOptions={this.handleSuburbSearch}
backspaceRemoves={true}
/>
</FormField>
)
}}
name="suburb"
id="AddressLocation"
label="Suburb."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field component={StaticText}
name="postcode"
id="postcode"
label="Postcode."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
</Panel>
)
}
}
const AddressContainer = connect(state => ({
addressData: state.addressData,
}), dispatch => ({
updatePostcodeValue: (postcode, sectionPrefix) => dispatch(change(CLIENT_FORM_NAME, `${sectionPrefix ? (sectionPrefix + '.') : ''}postcode`, postcode))
}))(Address)
export default AddressContainer
How do I structure the onSuburbChange so that it can sit outside the render function, update the onChange value and also update the Postcode etc?
well, if you look at the method, you'll see that... well, input is undefined in that scope.
onSuburbChange = (value) => { // <-- scope starts here
this.setState({ selectedSuburb: value }, () => {
input.onChange(value) // <-- input used here
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
assuming Select.Async is a "magic" blackbox Component that you don't have access to/are able to change, and the only parameter you get back from it in the callback is the new value, your best bet is a ref on the input.
<Field ref={(input) => this.input = input } ... />
and then change it to this.input instead of just input
I think you could also partially apply it (it's late any I'm not thinking straight) - it would look like
onSuburbChange = (input, value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
--
const mOnChange = onSuburbChange.bind(null, input) while input is in scope.
updatePostcodeValue can be referenced from props in the callback - and you've already taken care of ensuring it has the correct scope by using ES6 arrow function notation. Just destructure it out of props just like you did in render at the top of the callback.
also, unrelated, but you REALLY oughta break out those component props into another file or function...