I'm learning react-redux from the docs and don't see what the below means. What is the ref part referring to? And node? This ref isn't used anywhere from I see. Does the ref refer to the child component's node (the input) on the DOM after it gets rendered? If so, why not just refer to the input directly?
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
This is a ref callback attribute, and its purpose is to gain "direct access" to the DOM element/class components. Using a ref you may focus an input box, get it's value directly or access a method of class component.
In this case it's purpose is to get/change the input's value, by assigning a reference to the input variable (the let input) - see comments in code.
let AddTodo = ({ dispatch }) => {
let input // the input variable which will hold reference to the input element
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) { // using the input variable
return
}
dispatch(addTodo(input.value)) // using the input variable
input.value = ''
}}>
<input ref={node => {
input = node // assign the node reference to the input variable
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
Related
So I created a component which is responsible with opening a form to add data. Here it is:
import Button from "#mui/material/Button";
import TextField from "#mui/material/TextField";
import { useState } from "react";
import "./CreateRow.css";
type Props = {
fields: string[]
}
export default function CreateRow(props: Props) {
function sendData(event: any) {
event.preventDefault();
console.log(event);
}
const [creatingRow, setCreatingRow] = useState(false);
return (
<>
<Button variant="contained"
onClick={() => setCreatingRow(!creatingRow)}
className="adauga-button"
>Adauga</Button>
{
creatingRow && (
<form onSubmit={sendData} className="create-form">
{
props.fields.map((field, index) =>
{
return (
<TextField
key={index}
required
id="outlined"
name={field}
label="Nume"
placeholder="Scrie"
/>
)
}
)
}
<Button variant="contained"
className="trimite-button"
>Finalizeaza</Button>
</form>
)
}
</>
);
}
the fields that will be rendered for the form, will be passed as props, in a string array (as you can see)
So how will I create my hooks for controlling the form data?
You need to add a state variable to be passed to the material-UI text field and attach an onchange method to update the value.
But since it seems there might be multiple text fields there needs to be an individual state for each of those text fields. Create a function component as below in your file.
function TextFieldWithState(props) {
const [value, setValue] = useState(props?.value ? props.value : ""); //checking if value is there in props if so assigning it else
const updateValue = (e) => setValue(e.target.value);//function to update the state value.
return <TextField {...props} value={value} onChange={updateValue} />;
}
After creating the component call it inside the props.fields.map as below,
props.fields.map((field, index) => {
return (
<TextFieldWithState
key={index}
required
id="outlined"
name={field}
label="Nume"
placeholder="Scrie"
/>
)
})
I am missing how to bring the input value out of the child component.
I have an Input component that I want to re-use, there I can see what happens onChange, and I see every change on the input field.
Then on the parent component, Form, where I use <Input />, I have the rest of the form with the Submit button. At this level, I want to handle onSubmit, but I cannot see/console the value of the input here. I can only see it from the child.
Any ideas about what I am doing wrong?
Input.js - here I can see the input value onChange
function Input(props) {
const { label, name, value } = props;
const handleChange = (event) => {
const updateForm = {...Form};
console.log("change:", updateForm)
updateForm[label] = event.target.value;
}
return (
<label>
{label}
<input name={name} value={value} onChange={handleChange}></input>
</label>
)
}
export { Input }
Forms.js - here I cannot get access to the input value and submit/handle it
function Form(props) {
const handleSubmit = (event) => {
event.preventDefault();
console.log(Input.value);
console.log(props.label.value)
alert(`form is: ${event.target.input}`);
}
return (
<>
<form onSubmit={handleSubmit}>
<Input label={props.label} />
<input type="submit" value="Submit"></input>
</form>
</>
)
}
I have that structure because I am defining what I want in my Form on the main HomePage component:
function Home() {
return (
<>
.......
<Section withForm label={["Name"]} {...homeObjFive}/>
<Section withForm label={"Phone"} {...homeObjOne}/>
.......
</>
)
}
This is the perfect case to use the useRef function from react.
In Form.js
import React, { useRef } from 'react'
create a reference constant and pass it as a prop into the input component. and change the input value that is handled in the onSubmit function to the reference
Also in Form.js (changes are made to the submit function)
function Form(props) {
const { inputValue } = useRef(); // added
const handleSubmit = (event) => {
event.preventDefault();
console.log(inputValue); // changed
console.log(props.label.value)
alert(`form is: ${event.target.input}`);
}
return (
<>
<form onSubmit={handleSubmit}>
{/* added the inputValue prop to the input component */}
<Input label={props.label} inputValue={inputValue} />
<input type="submit" value="Submit"></input>
</form>
</>
)
}
and now inside of the Input component set the input elements reference to the inputValue prop. you will no longer need a onChange function as reacts useRef function is updated automatically
In Input.js
function Input(props) {
return (
<label>
{props.label}
<input name={props.name} value={props.value} ref={props.inputValue}></input>
</label>
)
}
export { Input }
Suppose you have a form with two inputs, name and email (these are the id props of the inputs). You can extract the form values like this:
const handleSubmit = (event) =>
{
event.preventDefault()
const data = new FormData(event.currentTarget)
const name = data.get('name')
const email = data.get('email')
// do something with the data
}
You can read more about FormData here.
I'd like to react rerender component after every state edit.
App component:
let [cur1, setCur1] = useState('USD')
let [cur2, setCur2] = useState('EUR')
let [result, setResult] = useState(0)
let currenciesArr = [cur1, cur2]
async function getRate(e) {
e.preventDefault()
setCur1(cur1 = e.target.cur1.value)
setCur2(cur2 = e.target.cur2.value)
let amount = e.target.amount.value
const api_url = await fetch(`https://free.currconv.com/api/v7/convert?q=${cur1}_${cur2}&compact=ultra&apiKey=${API_KEY}`)
const data = await api_url.json()
await setResult(convert(amount, data))
}
I have used Context.Provider for rerender, but it doesn't work.
return (
<Context.Provider value={{currenciesArr}}>
<div>
<Choose getRate={getRate} chooseCur={chooseCur} chooseCur2={chooseCur2}/>
<ShowRate currencies={currenciesArr} result={result}/>
</div>
</Context.Provider>
)
Component that need to rerender
function Choose(props) {
const cProps = useContext(Context)
console.log(cProps.currenciesArr);
return(
<div>
<div>
<button onClick={ props.chooseCur } name='RUB'>RUB</button>
<button onClick={ props.chooseCur } name='AUD'>AUD</button>
</div>
<div>
<button onClick={ props.chooseCur2 } name='EUR'>EUR</button>
<button onClick={ props.chooseCur2 } name='GBP'>GBP</button>
</div>
<form onSubmit={props.getRate}>
{cProps.currenciesArr.map((item,i) => {
return(
<input type='text' key={i} name={'cur'+(i+1)} defaultValue={item}></input>
)
})
}
<input type='text' name='amount' defaultValue='1'></input>
<button onClick={(e)=>{console.log(e.target)}} ></button>
</form>
</div>
)
}
Button with prop props.chooseCur setting state in App component
function chooseCur(e) {
e.preventDefault()
setCur1(e.target.name)
}
function chooseCur2(e) {
e.preventDefault()
setCur2(e.target.name)
}
and i'd like to "choose" component will rerender after setState.
First currenciesArr should be part of the state as const [currenciesArr, setCurrenciesArr] = useState([cur1, cur2])
Next, you need to call setCurrenciesArr in your chooseCur2 functions. I used a restructuring assignment to get the value of name inside the function. Hooks are called when the event loop is complete. See Capbase Medium post for more information on hooks and the event loop.
In choose.js
You need to use value in your input instead of defaultValue and set it as readonly to prevent receiving a warning about setting the value.
Default value provides the value if none is present.
See the following codesandbox for a working version.
https://codesandbox.io/s/long-rain-8vyuh
I'm not yet a React master, hence my question. Why there is still invoking a parent function if in child component I'm writing new characters in input fields? I want to call parent method only when I clicked Search button in my child component.
Parent component:
class MainPage extends Component {
render() {
let searchOffersBar = (
<MuiThemeProvider>
<SearchOffer
offersFound={this.props.onOffersFound}
/>
</MuiThemeProvider>
);
let searchResults = (
<SearchResults
offers={this.props.offers}
/>
);
return (
<Aux>
<div className={classes.container}>
<Intro/>
<div className={classes.contentSection}>
{searchOffersBar}
{searchResults}
</div>
</div>
</Aux>
)
}
}
const mapStateToProps = state => {
return {
offers: state.offers.offers
}
}
const mapDispatchToProps = dispatch => {
return {
onOffersFound: (searchParams) => dispatch(actions.fetchOffersByCriteria(searchParams))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
<SearchOffer> is my child component with a search section (input fields and button "Search offers"). I want to fill some data in my inputs and then click the button. I though that clicking the button will invoke a method in child component: onOffersFound:
const searchOffer = props => {
let currentDate = new Date();
const [searchCriteria, setSearchCriteria] = useState({
brand: 'xxx',
capacity: 100
})
const [drawerIsOpen, setDrawerIsOpen] = useState(false);
const handleToggle = () => setDrawerIsOpen(!drawerIsOpen);
const handleBrand = (event) => {
let mergedState = updateObject(searchCriteria, {brand: event.target.value})
setSearchCriteria(mergedState);
}
const handleCapacity = (event) => {
let mergedState = updateObject(searchCriteria, {capacity: event.target.value});
setSearchCriteria(mergedState);
}
const handleBookingFrom = (bookingFromValue) => {
let mergedState = updateObject(searchCriteria, {bookingFrom: bookingFromValue});
setSearchCriteria(mergedState);
}
const handleBookingTo = (bookingToValue) => {
let mergedState = updateObject(searchCriteria, {bookingTo: bookingToValue});
setSearchCriteria(mergedState);
}
return (
<div className={classes.sideNav}>
<Button variant={"outlined"} onClick={handleToggle} className={classes.sideNavBtn}>Search</Button>
<Drawer
className={classes.drawer}
containerStyle={{top: 55}}
docked={false}
width={200}
open={drawerIsOpen}
onRequestChange={handleToggle}
>
<AppBar title="Search"/>
<form noValidate autoComplete="off" onSubmit={props.offersFound(searchCriteria)}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<Grid container justify="space-around">
<TextField
id="brand"
label="Brand"
margin="normal"
onChange={handleBrand}
/>
<TextField
id="capacity"
label="Capacity"
margin="normal"
onChange={handleCapacity}
/>
<Button variant="contained" color="primary">
Search
</Button>
</Grid>
</MuiPickersUtilsProvider>
</form>
</Drawer>
</div>
);
}
export default searchOffer;
onOffersFound in my action creator looks like:
export const fetchOffersByCriteria = (searchParams) => {
return dispatch => {
let queryParams = '?brand='+searchParams.brand + '&capacity='+searchParams.capacity;
axios.get('/getFilteredOffers' + queryParams)
.then(response => {
dispatch(saveFoundOffers(response.data)); --> saves the state
})
.catch(error => {
console.log(error);
})
}
}
My question is why the above method fetchOffersByCriteria is invoked every time I enter new character in my child component? I want to invoke this method only when I click the Search button in child component. Maybe my approach is bad?
Thanks for all tips!
The issue is that props.offersFound(searchCriteria) is being invoked every render. The onSubmit prop should be a function to be invoked when submitted. Currently, it's being invoked immediately.
This line:
onSubmit={props.offersFound(searchCriteria)}
Should be (or something similar):
onSubmit={() => props.offersFound(searchCriteria)}
Currently, when typing in the brand (or capacity) field, the handleBrand change callback is invoked. This invokes setSearchCriteria (a state update) which triggers a re-render of the component. While this component is re-rendering, it's immediately invoking props.offersFound(searchCriteria) and passing the return value to the onSubmit prop. You likely want the onSubmit prop to be a function to be invoked at the time of submitting.
See the documentation for controlled components for more de3tails.
<form
noValidate
autoComplete="off"
onSubmit={props.offersFound(searchCriteria)}>
You are immediately invoking prop and trying to use result returned as event listener. It should be
<form
noValidate
autoComplete="off"
onSubmit={() => props.offersFound(searchCriteria)}>
instead
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 } />
}