This code example is minimal representation of what the structure is
Main component where we group all other components:
<Form>
<FormGroup>
<RadioGroup>
<RadioButton/>
<RadioButton/>
</RadioGroup>
</FormGroup>
<FormGroup>
<TextInput />
</FormGroup>
<Button>Submit Form</Button>
</Form>
The goal is to create a reference to every TextInput in the FormGroup or to every RadioButton in RadioGroup or even FormGroup, so lets go further down the components, for now Form and FormGroup are empty components rendering children:
const Form: React.FunctionComponent<Props> = ({ children }) => {
return (
<form>
{children}
</form>
);
};
const FormGroup: React.FunctionComponent<Props> = ({ children }) => {
// WE WANT TO ACCESS REF HERE, with React.Children.map every child's ref is always null
return (
{children}
);
};
To keep it simple the RadioGroup also just rendering children
const RadioGroup: React.FunctionComponent<Props> = ({ children }) => {
// WE WANT TO ACCESS REF HERE, with React.Children.map every child's ref is always null
return (
{children}
);
};
And we are getting to the main point, the Child we want to create a reference to, in this example the RadioButton component
class RadioButton extends Component<{props}, state> {
this.state = {
inputRef: React.createRef()
};
handleClick() {
WE CAN ACCESS THE REF HERE
// this.state.inputRef.current
}
render() {
return (
<div> // putting the ref here also doesnt work in parent components
<label>
<input
ref={this.state.inputRef}
onChange={() => this.handleClick()}
/>
</label>
</div>
);
}
};
I would suggest you to use react-hook-form if you are working with form.
react-hook-form
Related
I have a children component as a shared form using with different parent components, those parent components have different buttons and their actions.
My parent component is like:
const form = useForm({
defaultValues: {
name: '',
}
})
const { handleSubmit } = form
const onSubmit = (data) => { ... }
return (
<>
<Child form={form} />
<button onClick={handleSubmit(onsubmit)} />
</>
)
The child component is:
const {register, control } = props.form
return (
<form>
<Controller control={control} name="name" render={(field, fieldState) => <input .../>} />
</form>
)
How can I test the children component input behavior?
I used a jest.mock to react-hook-form:
jest.mock('react-hook-form', () => {
...jest.requireActural('react-hook-form')
Controller: ()=><></>
useForm:()=>({
Control: ()=>({}),
handleSubmit: ()=>jest.fn()
})
})
But this mock cannot get the input on the DOM. On the screen.debug, I can only see a tag renders, but not the content.
https://codesandbox.io/s/react-hook-form-v7-controller-forked-fwjqxg?file=/src/index.js
I am attempting to use the react-select "Select" component in my app as a convenient way to generate a series of drop-down lists as part of my UI.
From the react-select documentation, I have been able to get these components to render with the desired options; However, the value stored from the Select components is stored as a state variable via the useState hook.
I need to pass the value (state) of these menus to parent components. I am at a loss of how to get the state value generated in these functions to be able to be read in higher parent components. Below are some snippets of what I currently have.
NOTE: the arrays which populate the options props for these child functions are generated at a higher parent component and passed down as props. There seems to be no issue with a portion of the code
function ButtonA(props){
const options=props.buttonAOptions;
const [selectedOption, setSelectedOption]=useState(null);
return(
<div>
<p>Button A
<Select
value={selectedOption}
onChange={newValue =>setSelectedOption(newValue)}
options={options}
/>
</p>
</div>
)
}
function ButtonB(props){
const options=props.buttonBOptions;
const [selectedOption, setSelectedOption]=useState(null);
return(
<div>
<p>Button B
<Select
value={selectedOption}
onChange={newValue =>setSelectedOption(newValue)}
options={options}
/>
</p>
</div>
)
}
class Window extends React.Component {
constructor(props){
super(props);
}
// there are some other methods in here that are not related to this problem
render(){
return (
<div>
<ButtonA
buttonAOptions={this.props.buttonAOptions}
/>
<ButtonB
buttonBOptions={this.props.buttonBOption}
/>
</div>
)
}
}
You can communicate to a parent component through callbacks. You can lift the state and pass a callback to change the state on the parent, that is also passed as props to your component, or you can maintain the same state on both parent and child and just communicate changes from the children to the parent.
Example:
function Parent() {
const [counters, setCounters] = useState({ a: 0, b: 0 });
return (
<>
A: {counters.a}
<Child onChange={(value) => setCounters((s) => ({ ...s, a: value }))} />
B: {counters.b}
<Child onChange={(value) => setCounters((s) => ({ ...s, b: value }))} />
</>
)
}
function Child({ onChange }) {
const [counter, setCounter] = useState(0);
function handleClick() {
setCounter((s) => {
const newValue = s + 1;
onChange && onChange(newValue);
return newValue;
})
}
return <button onClick={handleClick}>Add 1</button>;
}
I am trying to use the register() method in my child component. But I am getting a Typescript Error saying: Expected 1-2 arguments, but got 0.
I am assuming I am passing the register method incorrectly?
Parent Component
type FormValues = {
projectType: string;
};
const Parent = () => {
const {
register,
} = useForm<FormValues>({ mode: 'all' });
return (
<Container>
<FormBlock id="form">
<fieldset>
<ChildComponent props={register()}/>
</fieldset>
</FormBlock>
</Container>
);
};
Child Component
const ChildComponent = ({ props }) => {
return (
<InputField {...props.register('projectType')}></InputField
);
};
You've to update the following line to:
<ChildComponent props={register} />
You shouldn't call register, you've to remove the parenthesis
EDIT: thanks to Calvin
You've to edit the component:
<InputField {...props('projectType')}></InputField>
It's cleaner to rename props to register
<ChildComponent register={register} />
// Field
<InputField {...register('projectType')}></InputField>
i found a gist about how to pass state between two components.
Here the jsbin
But how about the multi state?
I want two input fields and show the entered text in other components when i edit it.
i tried edited like this
this.state = {
fieldVal: "" //first input state
otherFieldVal: "" //second
}
and
//input onChange
onUpdate = name => (event) => {
this.setState({ [name]: event.target.value });
};
with no luck.
How can i made it work on multi state for multi input fields ?
Don't need to keep state in both Child and parent. You can write your child component like below, and you can access tow states dynamically by using data-attirb or you can folloe #Isaac 's answer.Keep the state in Child and pass state to Parent or keep the event to Parent from Child.
export class Child extends React.Component {
update = (e) => {
this.props.onUpdate(e.target)
};
render() {
return (
<div>
<h4>Child</h4>
<input
type="text"
placeholder="type here"
onChange={this.update}
data-state = "fieldVal"
value={this.props.fieldVal}
/><br/><br/>
<input
type="text"
placeholder="type here"
onChange={this.update}
data-state = "otherFieldVal"
value={this.props.otherFieldVal}
/>
</div>
)
}
}
export class OtherChild extends React.Component {
render() {
return (
<div>
<h4>OtherChild</h4>
Value in OtherChild Props passedVal1: {this.props.passedVal1} <br/>
Value in OtherChild Props passedVal2: {this.props.passedVal2}
</div>
)
}
}
and in parent :
class App extends Component {
onUpdate = (data) => {
this.setState({
[data.dataset.state]: data.value
})
};
render() {
return (
<div>
<h2>Parent</h2>
Value in Parent Component State fieldVal: {this.state.fieldVal} <br/>
Value in Parent Component State otherFieldVal: {this.state.otherFieldVal}
<br/>
<Child onUpdate={this.onUpdate} fieldVal= {this.state.fieldVal} otherFieldVal ={this.state.otherFieldVal}/>
<br />
<OtherChild passedVal1={this.state.fieldVal} passedVal2={this.state.otherFieldVal}/>
</div>
);
}
}
demo
renderInput = (prop) => {
return (
<Input
onChange={(event) => {
this.setState({ [prop]: event.target.value });
}}
/>
)
}
render() {
<div>
{this.renderInput('name')}
{this.renderInput('age')}
</div>
}
We can set a renderInput method and render different input using parameter to achieve your objective
I am looking to create a stateless component who's input element can be validated by the parent component.
In my example below, I am running into a problem where the input ref is never being assigned to the parent's private _emailAddress property.
When handleSubmit is called, this._emailAddress is undefined. Is there something I'm missing, or is there a better way to do this?
interface FormTestState {
errors: string;
}
class FormTest extends React.Component<void, FormTestState> {
componentWillMount() {
this.setState({ errors: '' });
}
render(): JSX.Element {
return (
<main role='main' className='about_us'>
<form onSubmit={this._handleSubmit.bind(this)}>
<TextInput
label='email'
inputName='txtInput'
ariaLabel='email'
validation={this.state.errors}
ref={r => this._emailAddress = r}
/>
<button type='submit'>submit</button>
</form>
</main>
);
}
private _emailAddress: HTMLInputElement;
private _handleSubmit(event: Event): void {
event.preventDefault();
// this._emailAddress is undefined
if (!Validators.isEmail(this._emailAddress.value)) {
this.setState({ errors: 'Please enter an email address.' });
} else {
this.setState({ errors: 'All Good.' });
}
}
}
const TextInput = ({ label, inputName, ariaLabel, validation, ref }: { label: string; inputName: string; ariaLabel: string; validation?: string; ref: (ref: HTMLInputElement) => void }) => (
<div>
<label htmlFor='txt_register_first_name'>
{ label }
</label>
<input type='text' id={inputName} name={inputName} className='input ' aria-label={ariaLabel} ref={ref} />
<div className='input_validation'>
<span>{validation}</span>
</div>
</div>
);
You can useuseRef hook which is available since v16.7.0-alpha.
EDIT: You're encouraged to use Hooks in production as of 16.8.0 release!
Hooks enable you to maintain state and handle side effects in functional components.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Read more in Hooks API documentation
EDIT: You now can with React Hooks. See the answer by Ante Gulin.
You can't access React like methods (like componentDidMount, componentWillReceiveProps, etc) on stateless components, including refs. Checkout this discussion on GH for the full convo.
The idea of stateless is that there isn't an instance created for it (state). As such, you can't attach a ref, since there's no state to attach the ref to.
Your best bet would be to pass in a callback for when the component changes and then assign that text to the parent's state.
Or, you can forego the stateless component altogether and use an normal class component.
From the docs...
You may not use the ref attribute on functional components because they don't have instances. You can, however, use the ref attribute inside the render function of a functional component.
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
This is late but I found this solution much better.
Pay attention to how it uses useRef & how properties are available under current property.
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
For more reference check react docs
The value of your TextInput is nothing more than a state of your component. So instead of fetching the current value with a reference (bad idea in general, as far as I know) you could fetch the current state.
In a reduced version (without typing):
class Form extends React.Component {
constructor() {
this.state = { _emailAddress: '' };
this.updateEmailAddress = this.updateEmailAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
updateEmailAddress(e) {
this.setState({ _emailAddress: e.target.value });
}
handleSubmit() {
console.log(this.state._emailAddress);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
value={this.state._emailAddress}
onChange={this.updateEmailAddress}
/>
</form>
);
}
}
You can also get refs into functional components with a little plumbing
import React, { useEffect, useRef } from 'react';
// Main functional, complex component
const Canvas = (props) => {
const canvasRef = useRef(null);
// Canvas State
const [canvasState, setCanvasState] = useState({
stage: null,
layer: null,
context: null,
canvas: null,
image: null
});
useEffect(() => {
canvasRef.current = canvasState;
props.getRef(canvasRef);
}, [canvasState]);
// Initialize canvas
useEffect(() => {
setupCanvas();
}, []);
// ... I'm using this for a Konva canvas with external controls ...
return (<div>...</div>);
}
// Toolbar which can do things to the canvas
const Toolbar = (props) => {
console.log("Toolbar", props.canvasRef)
// ...
}
// Parent which collects the ref from Canvas and passes to Toolbar
const CanvasView = (props) => {
const canvasRef = useRef(null);
return (
<Toolbar canvasRef={canvasRef} />
<Canvas getRef={ ref => canvasRef.current = ref.current } />
}