I’m using react-bootstrap, but with bootstrap v3, because that’s the bootstrap version my project currently uses.
Now, I just need to have a ref from an input. In the react-bootstrap docs, it says you should use formControl’s inputRef property like this:
<FormControl inputRef={ref => { this.input = ref; }} />
But currently, I’m using a function and react hooks to build my component, instead of classes.
So I just wrote my code this way:
let inputReferencia = useRef(null);
In onFocus event, I use this statement to select the content of the input:
inputReferencia.current.select();
And, finally, this is my input, as per react-bootstrap syntax:
<FormGroup>
<FieldGroup
id="referencia"
name="referencia"
value={formValues.referencia}
type="text"
label="Referencia"
onFocus={(e) => onDescripcionReferenciaInputFocus(e)}
onChange={(e) => onInputChange(e)}
inputRef={ref => { inputReferencia = ref; }} />
</FormGroup>
As React-bootstrap suggests, this is FieldGroup:
const FieldGroup = ({ id, label, help, ...props }) => {
return (
<FormGroup controlId={id} bsSize="small">
<ControlLabel>{label}</ControlLabel>
<FormControl {...props} />
{help && <HelpBlock>{help}</HelpBlock>}
</FormGroup>
);
}
But when I try to access the ref, as in
inputReferencia.current.select()
the current property is undefined.
Of course, if I check out the ref in Chrome debugger, it was definitely initialized with something:
Can somebody help me to solve this issue?
Many thanks for your help …
According to the docs you ought to use ref instead of inputRef. This is regardless of whether you are using Bootstrap V3 or Bootstrap V4 or any other version as React-bootstrap only uses their stylesheet.
In dealing with functional components and trying to access the child element's ref on the parent when the ref variable is declared on the parent, you can opt to use forwardRef
function App() {
let inputReferencia = useRef(null);
function onDescripcionReferenciaInputFocus(e) {
console.log(`inputReferencia`, inputReferencia);
}
return (
<Container>
<FormGroup>
<FieldGroup
id="referencia"
name="referencia"
value={formValues.referencia}
type="text"
label="Referencia"
onFocus={(e) => onDescripcionReferenciaInputFocus(e)}
onChange={(e) => onInputChange(e)}
ref={inputReferencia}
/>
</FormGroup>
</Container>
);
}
const FieldGroup = React.forwardRef(({ id, label, help, ...props }, ref) => {
return (
<FormGroup controlId={id} bsSize="small">
<ControlLabel>{label}</ControlLabel>
<FormControl {...props} ref={ref} />
{help && <HelpBlock>{help}</HelpBlock>}
</FormGroup>
);
});
Related
Currently I have a useLazyQuery hook which is fired on a button press (part of a search form).
The hook behaves normally, and is only fired when the button is pressed. However, once I've fired it once, it's then fired every time the component re-renders (usually due to state changes).
So if I search once, then edit the search fields, the results appear immediately, and I don't have to click on the search button again.
Not the UI I want, and it causes an error if you delete the search text entirely (as it's trying to search with null as the variable), is there any way to prevent the useLazyQuery from being refetched on re-render?
This can be worked around using useQuery dependent on a 'searching' state which gets toggled on when I click on the button. However I'd rather see if I can avoid adding complexity to the component.
const AddCardSidebar = props => {
const [searching, toggleSearching] = useState(false);
const [searchParams, setSearchParams] = useState({
name: ''
});
const [searchResults, setSearchResults] = useState([]);
const [selectedCard, setSelectedCard] = useState();
const [searchCardsQuery, searchCardsQueryResponse] = useLazyQuery(SEARCH_CARDS, {
variables: { searchParams },
onCompleted() {
setSearchResults(searchCardsQueryResponse.data.searchCards.cards);
}
});
...
return (
<div>
<h1>AddCardSidebar</h1>
<div>
{searchResults.length !== 0 &&
searchResults.map(result => {
return (
<img
key={result.scryfall_id}
src={result.image_uris.small}
alt={result.name}
onClick={() => setSelectedCard(result.scryfall_id)}
/>
);
})}
</div>
<form>
...
<button type='button' onClick={() => searchCardsQuery()}>
Search
</button>
</form>
...
</div>
);
};
You don't have to use async with the apollo client (you can, it works). But if you want to use useLazyQuery you just have to pass variables on the onClick and not directly on the useLazyQuery call.
With the above example, the solution would be:
function DelayedQuery() {
const [dog, setDog] = useState(null);
const [getDogPhoto] = useLazyQuery(GET_DOG_PHOTO, {
onCompleted: data => setDog(data.dog)
})
return (
<div>
{dog && <img src={dog.displayImage} />}
<button
onClick={() => getDogPhoto({ variables: { breed: 'bulldog' }})}
>
Click me!
</button>
</div>
);
}
The react-apollo documentation doesn't mention whether useLazyQuery should continue to fire the query when variables change, however they do suggest using the useApolloClient hook when you want to manually fire a query. They have an example which matches this use case (clicking a button fires the query).
function DelayedQuery() {
const [dog, setDog] = useState(null);
const client = useApolloClient();
return (
<div>
{dog && <img src={dog.displayImage} />}
<button
onClick={async () => {
const { data } = await client.query({
query: GET_DOG_PHOTO,
variables: { breed: 'bulldog' },
});
setDog(data.dog);
}}
>
Click me!
</button>
</div>
);
}
The Apollo Client documentation isn't explicit about this, but useLazyQuery, like useQuery, fetches from the cache first. If there is no change between queries, it will not refetch using a network call. In order to make a network call each time, you can change the fetchPolicy to network-only or cache-and-network depending on your use case (documentation link to the fetchPolicy options). So with a fetchPolicy change of network-only in your example, it'd look like this:
const AddCardSidebar = props => {
const [searching, toggleSearching] = useState(false);
const [searchParams, setSearchParams] = useState({
name: ''
});
const [searchResults, setSearchResults] = useState([]);
const [selectedCard, setSelectedCard] = useState();
const [searchCardsQuery, searchCardsQueryResponse] =
useLazyQuery(SEARCH_CARDS, {
variables: { searchParams },
fetchPolicy: 'network-only', //<-- only makes network requests
onCompleted() {
setSearchResults(searchCardsQueryResponse.data.searchCards.cards);
}
});
...
return (
<div>
<h1>AddCardSidebar</h1>
<div>
{searchResults.length !== 0 &&
searchResults.map(result => {
return (
<img
key={result.scryfall_id}
src={result.image_uris.small}
alt={result.name}
onClick={() => setSelectedCard(result.scryfall_id)}
/>
);
})}
</div>
<form>
...
<button type='button' onClick={() => searchCardsQuery()}>
Search
</button>
</form>
...
</div>
);
};
When using useLazyQuery, you can set nextFetchPolicy to "standby". This will prevent the query from firing again after the first fetch. For example, I'm using the hook inside of a Cart Context to fetch the cart from an E-Commerce Backend on initial load. After that, all the updates come from mutations of the cart.
Can we dynamically show/hide the columns of a List Datagrid based on the filters values?
I do not see how we can do this. Thanks for any help on this.
This is not possible with the default ra-ui-materialui List component. You'll have to implement your own, using it as a starting point.
Feel free to open a feature request issue on the https://github.com/marmelab/react-admin repository describing the use case.
You can refer to this link for customizing your datagrid columns : https://github.com/fizix-io/ra-customizable-datagrid
OR,
you can make your list component as a stateful component, and implement your own Actions in the List component like a toggle button.
For Example:
class MoreDetails extends Component {
constructor() {
super();
this.state = {
showDetails: false
};
}
toggleDetails = () => {
const toggle = this.state.showDetails;
this.setState((prevState, props) => {
return {
showDetails: !toggle,
}
});
}
render() {
const { classes, ...props } = this.props;
const MyActions = ({ basePath, data, resource }) => (
<CardActions style={cardActionStyle}>
<Button
color="primary"
onClick={this.toggleDetails}
>Toggle Details</Button>
</CardActions>
);
return <List
actions={<MyActions />}
{...props} >
<Datagrid>
<TextField source="c1" label="Column1" />
<TextField source="c2" label="Column2" />
{this.state.showDetails ?
<TextField source="c3" label="Column3" /> : null }
<TextField source="c4" label="Column4" />
{this.state.showDetails ?
<TextField source="c5" label="Column5" /> : null }
</Datagrid>
</List>
}
}
In my project we are building a form with React and Redux-Form. We have a single information that is composed by the value of two inputs. But the values of each input is combined and validated together.
The first implementation try was by connecting each component with Field. It let us update the state properly but we couldn't validate all the values together with the validate prop.
The second Try was using Fieldscomponent but it does not have a validate prop. We thought in create a Pull Request for it but the API for it isn't clear yet, since what we want to validate the combination of the two values and the behavior of the Fields props (such as parse and format) is different, executing the function for each input inside Fields component separately.
I know it is possible to create a component and use Field to connect with the application state, but I didn't want to manage things as the touched prop, or the callbacks to update the state, or other things that I even have noticed, since Redux-Form has all of it done.
The fact is that I end up with an implementation but it didn't looked very elegant. I'd like you to take a look at the implementation and give your opinion, sugest other solutions and even if this solution is not implemented in Redux-Form yet we could maybe open a pull request for that.
Here is an example implementation
Simple form container
import SimpleForm from 'app/simpleForm/components/simpleForm'
import { reduxForm } from 'redux-form'
export default reduxForm({
form: 'simpleForm'
})(SimpleForm)
Simple form component
import React from 'react'
import { Field } from 'redux-form'
import MultiInputText from 'app/simpleForm/components/multiInputText'
const onSubmit = values => alert(JSON.stringify(values))
const validateAddress = (value) => {
if (!value) return 'Address is empty'
if (!value.street) return 'Street is empty'
if (!value.number) return 'Number is empty'
return null
}
const SimpleForm = ({ handleSubmit }) => {
return (
<form onSubmit={ handleSubmit(onSubmit) }>
<Field label="Home Address" name="home" component={MultiInputText} type="text" validate={validateAddress}/>
<Field label="Work Address" name="work" component={MultiInputText} type="text" validate={validateAddress}/>
<button type="submit">Submit</button>
</form>
)
}
export default SimpleForm
MultiInputText component
import React from 'react'
import { Fields, FormSection } from 'redux-form'
const renderAddreessInputs = ({ street, number, error }) => (<div>
<input {...street.input} type="text" />
<input {...number.input} type="text" />
{ street.meta.touched && number.meta.touched && error && <span className="error">{error}</span> }
</div>)
const MultiInputText = (props) => {
const { input: { name }, label, meta: { error }} = props
const names = [
'street',
'number'
]
return (<div>
<label htmlFor={name}>{label}</label>
<FormSection name={name}>
<Fields names={names} component={renderAddreessInputs} error={error}/>
</FormSection>
</div>)
}
export default MultiInputText
I see two options:
1) Use record-level validation.
reduxForm({
form: 'addressForm',
validate: values => {
const errors = {}
if(!home) {
errors.home = 'Address is empty'
}
// etc, etc. Could reuse same code for home and work
return errors
}
})
2) Create a single input that handles a complex value.
<Field name="home" component="AddressInput"/>
...
const AddressInput = ({ input, meta }) =>
<div>
<input
name={`${input.name}.street`}
value={(input.value && input.value.street) || ''}
onChange={event => input.onChange({
...input.value,
street: event.target.value
})}/>
...other inputs here...
</div>
That's total pseudocode, but I hope it gets the point across: a single input can edit a whole object structure.
Personally, I'd choose Option 1, but I prefer record-level validation over field-level validation in general. The nice thing about Option 2 is that a single AddressInput could be reused across the application. The downside is that you don't get specific field-level focus/blur/dirty/pristine state.
Hope that helps...?
In using redux-form with React, I'm having an issue where the error messages are not displaying for field-level input validation.
Here is the relevant code from the component:
const renderField = ({input, placeholder, type, meta: {touched, error, warning}}) => (
<div>
<input {...input} placeholder={placeholder} type={type} />
{touched &&
((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>)
)
}
</div>
)
const required = value => {
console.log("required");
return value ? undefined : 'Required';
};
const Question = props => {
const { handleSubmit, onBlur, question, handleClick } = props;
return (
<div className={`question question-${question.name}`}>
<form className={props.className} onSubmit={handleSubmit}>
<div className='question-wrapper'>
<label className={`single-question-label question-label-${question.name}`}>{question.text}</label>
<Field
component={renderField}
type={question.type}
name={question.name}
placeholder={question.placeholder}
onBlur={onBlur}
validate={required}
/>
</div>
</form>
</div>
)
}
export default reduxForm({
form: 'quiz',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
})(Question);
When I test it, I see that in the console the UPDATE_SYNC_ERRORS action is being called, and the console.log("required"); is also showing up. But when I navigate to the next question, neither on the screen do I see the error message, nor do I see any evidence of it when I inspect the component with DevTools.
I've been following the example on Field-Level Validation shown in the redux-form docs here: http://redux-form.com/6.7.0/examples/fieldLevelValidation/
Any idea what could be causing this? Thanks in advance!
Well, you have to write a validate function, and pass it to the reduxForm helper or wrapper like this. Redux-form will pass all the form values to this function before the form is submitted.
function validate(values) {
const errors = {};
// Validate the inputs from 'values'
if (!values.name) {
errors.name = "Enter a name!";
}
...
return errors;
}
export default reduxForm({
validate,
form: 'QuestionForm'
})(
connect(null, { someAction })(Question)
);
Hope this helps. Happy Coding !
you can also provide validate like this
const formOptions = {
form: 'yourformname',
validate: validatefunctionname,redux-form
};
I am trying to use "redux-form": "^6.7.0" with "react-bootstrap": "^0.31.0"
My Component renders nicely, but when I press Submit, what I see is an empty object.
note: I have tried wrapping the Config with connect first, and as show below, first wraping it with redux-form and then with the from react-redux connect()
Configuration.js
class Config extends Component {
render() {
const { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath, pickFile, pickFolder, handleSubmit } = this.props;
return (
<Form horizontal onSubmit={handleSubmit}>
<FormGroup controlId="serverPortBox">
<Col componentClass={ControlLabel} sm={2}>Watson Port:</Col>
<Col sm={10}>
<OverlayTrigger placement="left" overlay={(<Tooltip id="tt1">TCP port for Watson to use</Tooltip>)}>
<Field name="WatsonPort" component={FormControl}
type="number" min="1024" max="65535" placeholder={ServerPort} />
</OverlayTrigger>
</Col>
</FormGroup>
......
const CForm = reduxForm({
form: 'configuration' // a unique name for this form
})(Config);
const Configuration = connect(mapStateToProps, mapDispatchToProps)(CForm)
export default Configuration
reducers.js
import { combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form
......
const reducerList = {
GLVersion,
ServerConfig,
ServerStats,
form: formReducer
}
export default combineReducers(reducerList)
Main Package Dashboard.js
what i see in the debugger is that config is an empty object
<Panel className='configPanel'
collapsible header="Configuration"
eventKey="1"
defaultExpanded={true}>
<Configuration onSubmit={(config) => writeConfig(config)} />
</Panel>
See: https://github.com/erikras/redux-form/issues/2917
Oh, this was a great mystery. I followed the advice in https://github.com/react-bootstrap/react-bootstrap/issues/2210 and both the warning about additional props and the empty submit stopped.
It seems you have to wrap the Bootstrap in your custom component (why?, I don't know). Also make sure you custom component is a stateless funcitonal component, or after the first key press, you field will blur and lose focus.
There are some warnings in the documentation of redux-form about this.
my custom field component FieldInput
const FieldInput = ({ input, meta, type, placeholder, min, max }) => {
return (
<FormControl
type={type}
placeholder={placeholder}
min={min}
max={max}
value={input.value}
onChange={input.onChange} />
)
}
and I invoke it like this:
<Field name="ServerPort"
type='number'
component={FieldInput}
placeholder={ServerPort}
min="1024" max="65535"
/>
see also: https://github.com/erikras/redux-form/issues/1750
So now, the definition of FieldInput and Config look like this:
import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import { Form, FormControl, FormGroup, ControlLabel, Col, Button, Tooltip, OverlayTrigger } from 'react-bootstrap'
import * as Act from '../dash/actions.js'
import FaFolderOpen from 'react-icons/lib/fa/folder-open'
import FaFileCodeO from 'react-icons/lib/fa/file-code-o'
const FieldInput = ({ input, meta, type, placeholder, min, max }) => {
return (
<FormControl
type={type}
placeholder={placeholder}
min={min}
max={max}
value={input.value}
onChange={input.onChange} />
)
}
const Config = ({ ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath, pickFile, pickFolder, handleSubmit }) => {
return (
<Form horizontal onSubmit={handleSubmit}>
<FormGroup controlId="serverPortBox">
<Col componentClass={ControlLabel} sm={2}>Watson Port:</Col>
<Col sm={10}>
<OverlayTrigger placement="left" overlay={(<Tooltip id="tt1">TCP port for Watson to use</Tooltip>)}>
<Field name="ServerPort" type='number' min="1024" max="65535" component={FieldInput} placeholder={ServerPort} />
</OverlayTrigger>
</Col>
</FormGroup>
Some props required by <FormControl> are passed inside props.input from <Field>, see http://redux-form.com/6.6.3/docs/api/Field.md/#props
To pass all those props in a generic way, instead of doing it explicitly, you can use the following function:
const ReduxFormControl = ({input, meta, ...props}) => {
return <FormControl {...props} {...input} />
};
and then inside the form:
<Field component={ReduxFormControl} ... />
This way, value, onChange, etc. are all passed as expected to <FormControl>.