why the createRef current always null in react - javascript

I am developing a simple edit app page, because the form.item initial value did not update by data, so I want to setFieldsValue in the antd 4.x, this is my code looks like:
import React from 'react'
import { Modal, Input, Form } from 'antd'
export default function EditApp(props) {
const { visible, rowData: data = {}, onVisibleChange, onEdit, dispatch } = props
const [form] = Form.useForm()
let formRef = React.createRef()
if(formRef.current){
formRef.current.setFieldsValue({
remark: data?data.remark:''
})
}
function onConfirm() {
form.validateFields()
.then(values => {
let localValues = {
...values,
appId: data.app_id
}
onEdit(localValues)
})
.catch(info => {
console.log('Validate Failed:', info)
})
}
function onCancel() {
onVisibleChange()
}
return (
<>
<Modal title='Edit App' visible={visible} onOk={onConfirm} onCancel={onCancel}>
<Form form={form} ref={formRef}>
<Form.Item
label='remark'
name='remark'
value={data?data.remark:''}
>
<Input placeholder='Please input remark' />
</Form.Item>
</Form>
</Modal>
</>
)
}
To my surprise, the formRef.current is always null. Am I missing something? what should I do to make the Form.Item value update by data which passed from other component?

CreateRef work only with class components , you can use the hooks useRef if your react versions support it
import React, {useRef} from 'react'
import { Modal, Input, Form } from 'antd'
export default function EditApp(props) {
const { visible, rowData: data = {}, onVisibleChange, onEdit, dispatch } = props
const [form] = Form.useForm()
const formRef = useRef();
if(formRef.current){
formRef.current.setFieldsValue({
remark: data?data.remark:''
})
}
function onConfirm() {
form.validateFields()
.then(values => {
let localValues = {
...values,
appId: data.app_id
}
onEdit(localValues)
})
.catch(info => {
console.log('Validate Failed:', info)
})
}
function onCancel() {
onVisibleChange()
}
return (
<>
<Modal title='Edit App' visible={visible} onOk={onConfirm} onCancel={onCancel}>
<Form form={form} ref={formRef}>
<Form.Item
label='remark'
name='remark'
value={data?data.remark:''}
>
<Input placeholder='Please input remark' />
</Form.Item>
</Form>
</Modal>
</>
)
}

this could fix it too:
React.useEffect(() => {
form.setFieldsValue({
remark:data?data.remark:''
});
});
when using useEffect, ref code should be removed.

Related

react-hook-form: Update form data

In my project using react-hook-form to update and create details. There is an issue in the update form, the values are not updating properly, and the code
countryupdate.tsx
import React from 'react'
import { useQuery } from 'react-query'
import { useParams } from 'react-router-dom'
import { useCountryUpdate } from '../api/useCountryUpdate'
import { getDetails, useDetails } from '../api/useDetails'
import { CountryCreateUpdateForm } from '../forms/createupdateForm'
interface data{
id: string,
name: string
}
export const CountryUpdatepage = () => {
const { dataId }: any = useParams()
const { data, isLoading, isError } = useQuery(['details', dataId], () => getDetails(dataId), {
enabled: !!dataId,
});
const { mutateAsync } = useCountryUpdate();
const onFormSubmit = async() =>{
console.log("mutate", {...data})
await mutateAsync({...data, dataId})
}
return (
<div>
<h3>Update Details</h3>
<CountryCreateUpdateForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={undefined}/>
</div>
)
}
Here, when console the value inside onFormSubmit, it shows the same data in the previous state
createupdateform.tsx
import { useState } from "react"
import { useCountryCreate } from "../api/usecountrycreate"
import { useForm } from "react-hook-form"
export const CountryCreateUpdateForm = ({ defaultValues, onFormSubmit, isLoading }: any) => {
// console.log("name", defaultValues.data.name)
const { register, handleSubmit } = useForm({ defaultValues:defaultValues?.data });
const onSubmit = handleSubmit((data) => {
onFormSubmit(data)
})
return (
<form onSubmit={onSubmit}>
<div>
<label>Name</label>
<input {...register('name')} type="text" name="name" />
</div>
<button type="submit" >submit</button>
</form>
)
}
I am a beginner in react typescript, Please give me suggestions to solve this problem.
in countryupdate.tsx
the data is undefined at the beggining, so defaultValue of form is not updated after that;
it should help:
return (
<div>
<h3>Update Details</h3>
{data?.data && <CountryCreateUpdateForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={undefined}/>
}
</div>
)

React not loading form input field. No errors, just nothing being rendered

I'm very new to this, so I'm assuming it's a stupid little problem that I'm unware of.
I have form that renders input lines based off the amount of setpoints it gets from the backend. I have put in several console.log, and they all function fine until I get to the input.jsx. My console.log() in input never gets called, and the code just seems to.. forget about the input.
main code:
import React from "react";
import Joi from "joi-browser";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/button";
import Form from "./common/form";
import axios from "axios";
class SetpointsModal extends Form {
state = {
data: {},
errors: {},
};
schema = {};
doSubmit = async () => {
const { data } = await axios.put(
`/api/${this.props.id}_setpoints`,
this.state.data
);
console.log(data);
};
async componentDidMount() {
const { data } = await axios.get(`/api/${this.props.id}_setpoints`);
Object.keys(data).forEach((key) => {
this.schema[key] = Joi.number().label(data[key].label);
});
this.setState({ data });
}
render() {
const { title, show, handleClose } = this.props;
return (
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>{title} Setpoints</Modal.Title>
</Modal.Header>
<form onSubmit={this.handleSubmit}>
<Modal.Body>
{Object.keys(this.state.data).forEach((field) => {
this.renderInput(field);
})}
</Modal.Body>
<Modal.Footer>
{this.renderBtn("Update")}
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</form>
</Modal>
);
}
}
export default SetpointsModal;
Handle submit + render Input:
handleSubmit = (e) => {
e.preventDefault();
const errors = this.validate();
console.log(errors);
this.setState({ errors: errors || {} });
if (errors) return;
this.doSubmit();
};
renderInput(name) {
const { errors } = this.state;
const { label, data } = this.state.data[name];
console.log(name);
return (
<Input
name={name}
label={label}
value={data}
error={errors[name]}
onChange={this.handleChange}
/>
);
Input:
import React from "react";
const Input = ({ name, label, error, ...rest }) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<input {...rest} id={name} name={name} className="form-control" />
{error && <div className="alert alert-danger">{error}</div>}
</div>
);
};
export default Input;
forEach here won't be doing what you want it to do; it will 'do' something on each iteration but won't 'return' anything.
Using map might be more in line with what you were hoping for (docs here); this will return an array of components which will render out.
I'd suggest the following change in your render (you will need to pass a key down to each Input from your function):
{Object.keys(this.state.data).map((field, key) => (
this.renderInput(field, key);
))}

React Too many re-renders

I am following the serverless-stack.com tutorial. But I am stuck after creating the login button.
I keep getting the error:
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
I don't know what is causing the render so many times.
I combined my LoaderButton instead of importing to make it simpler.
import React, { useState } from "react";
import { Auth } from "aws-amplify";
import { useHistory } from "react-router-dom";
import { FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import { useFormFields } from "../libs/hooksLib";
import { onError } from "../libs/errorLib";
import "../css/index.css";
const LoaderButton = (
isLoading,
className = "",
disabled = false,
...props ) => {
return(
<Button
className={`LoaderButton ${className}`}
disabled={disabled || isLoading}
{...props}
>
{isLoading && <Glyphicon glyph="refresh" className="spinning" />}
{props.children}
</Button>
)
};
export default function Login() {
let history = useHistory();
const [isLoading, setIsLoading] = useState(false);
const [fields, handleFieldChange] = useFormFields({
email: "",
password: ""
});
function validateForm() {
return fields.email.length > 0 && fields.password.length > 0;
}
async function handleSubmit(event) {
event.preventDefault();
setIsLoading(true);
try {
await Auth.signIn(fields.email, fields.password);
userHasAuthenticated(true);
console.log(history);
//history.push("/");
} catch (e) {
onError(e);
setIsLoading(false);
}
}
return (
<div className="Login">
<form onSubmit={ () => { handleSubmit() } }>
<FormGroup controlId="email" bsSize="large">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={fields.email}
onChange={ () => { handleFieldChange() } }
/>
</FormGroup>
<FormGroup controlId="password" bsSize="large">
<ControlLabel>Password</ControlLabel>
<FormControl
type="password"
value={fields.password}
onChange={ () => { handleFieldChange() } }
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={ () => { isLoading() } }
disabled={() => { !validateForm() }}
>
Login
</LoaderButton>
</form>
</div>
);
}
hooksLib.js / useFormFields
import { useState } from 'react'
const useFormFields = (initalState) => {
const [fields, setValues] = useState(initalState)
return [
fields,
setValues({
...fields,
[event.target.id]: event.target.value
})
]
}
export { useFormFields }
Your custom hook should look like this if you want to accept the event value:
const useFormFields = (initalState) => {
const [fields, setValues] = useState(initalState)
return [
fields,
(event) => setValues({
...fields,
[event.target.id]: event.target.value
})
]
}
Since that parameter is actually a callback that should occur.
Also, your LoadingButton implementation needs to change to this:
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={isLoading} // This is a boolean value, not a function
disabled={() => !validateForm()}
>...</LoaderButton>

Generating Search suggestions in React?

I am looking to generate search suggestions that match data collected, like so:
As you type in you get suggestions:
I am referencing some of the tutorial work from WesBos:
https://github.com/wesbos/JavaScript30/blob/master/06%20-%20Type%20Ahead/index-FINISHED.html
I've got the data logging in the console but now I am unsure how to get it to render. Below are my components (My thoughts were to generate the divs as a loop in App.js and pass the props to Match.js which I would eventually import but I am not sure if I am approaching this wrong):
App.js
import React, { Component } from 'react';
import { Form, Button } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
const my_data = require('./data/test.json')
class App extends Component {
constructor(props) {
super(props);
this.state = {
links: [],
selectedLink:null,
userLocation: {},
searchInput: "",
showMatches: false,
matches: []
};
}
componentDidMount() {
fetch('https://data.cityofnewyork.us/resource/s4kf-3yrf.json')
.then(res=> res.json())
.then(res=>
//console.log(json)
this.setState({links:res})
);
}
render() {
const handleInputChange = (event) => {
event.preventDefault()
this.setState({searchInput: event.target.value })
//console.log(event.target.value)
}
const handleSubmit = (event) => {
event.preventDefault()
const data = this.state
displayMatches();
}
const findMatches = (wordToMatch, my_obj) => {
return my_obj.filter(place => {
// here we need to figure out the matches
const regex = new RegExp(wordToMatch, 'gi');
//console.log(place.street_address.match(regex))
return place.street_address.match(regex)
});
}
const displayMatches =() => {
const matchArray = findMatches(this.state.searchInput, this.state.links);
matchArray.map(place => {
console.log(place.street_address);
this.setState({matches:place})
this.setState({showMatches:true})
});
}
return (
<div>
<Form style = {{width: "75%"}} onSubmit = {handleSubmit}>
<Form.Group controlId="formSearch">
<Form.Control
type="text"
name = "my_search"
placeholder="Search for a Link Near you..."
onChange = {handleInputChange} />
</Form.Group>
<Button variant="primary" type="submit">
Search
</Button>
</Form>
<div>
{`How can I generate the console logged values as dynammic suggestions?`}
</div>
</div>
);
}
}
export default App;
Match.js
import React from 'react';
const match = ( props ) => {
return (
<div className="Matches">
<p>{`data is passed: ${props.address}`}</p>
</div>
)
};
export default match;
Appreciate the help.
Answers - Using Suggestions below
App.js
import React, { Component } from 'react';
import { Form, Button, ListGroup } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import Match from './Match'
const my_data = require('./data/test.json')
class App extends Component {
state = {
links: [],
selectedLink:null,
userLocation: {},
searchInput: "",
showMatches: false,
matches: [],
searchLink:[]
}
componentDidMount() {
fetch('https://data.cityofnewyork.us/resource/s4kf-3yrf.json')
.then(res=> res.json())
.then(res=>
//console.log(json)
this.setState({links:res})
);
}
handleInputChange = (event) => {
event.preventDefault()
this.setState({searchInput: event.target.value })
console.log(event.target.value)
}
handleSubmit = (event) => {
event.preventDefault()
this.displayMatches();
}
findMatches = (wordToMatch, my_obj) => {
return my_obj.filter(place => {
// here we need to figure out the matches
const regex = new RegExp(wordToMatch, 'gi');
//console.log(place.street_address.match(regex))
return place.street_address.match(regex)
});
}
displayMatches =() => {
const matchArray = this.findMatches(this.state.searchInput, this.state.links);
const newStateMatches = matchArray.map(place => {
console.log(place.street_address);
return place
});
this.setState({matches:newStateMatches})
this.setState({showMatches:true})
}
alertClicked =(event) => {
//alert('you clicked an item in the group')
const data = event.target
console.log('clicked this data:', data)
this.setState({searchLink: event.target})
console.log(this.state.searchLink)
}
render() {
return (
<div>
<input
placeholder="Search for a Link Near you..."
onChange = {this.handleInputChange}
value = {this.state.searchInput}
/>
<Button onClick={this.handleSubmit}>
Search
</Button>
<ListGroup defaultActiveKey="#link1">
{
this.state.matches.map(match => {
return <Match
address={match.street_address}
alertClicked={this.alertClicked}/>
})
}
</ListGroup>
</div>
);
}
}
export default App;
Match.js
import React from 'react';
import { ListGroup } from 'react-bootstrap';
const match = ( props ) => {
return (
<ListGroup.Item
className="Matches"
action onClick={props.alertClicked}>
<p>{`${props.address}`}</p>
</ListGroup.Item>
)
};
export default match;
I think your initial instinct as to how to do this is correct :
get the matches
store them in state
map over the state and render one component per match, passing the relevant data as props
To answer your question exactly, mapping over state to render component usually looks something like this :
<div>
{
matches.map(match => {
return <Match address={match.address} name={match.name} />
})
}
</div>
You can also destructure properties like this :
<div>
{
matches.map(({address, name}) => {
return <Match address={address} name={name} />
})
}
</div>
Also, another minor observation: you notice I called the component Match with a capital M. It is a convention in React and other component based libraries that components' names are always capitalized, not only in the file name but also in the code.
First move all your method definitions outside of your render function (you'll need to update const and add this.
in your display matches you should be building a newstate array then setState with the new array once built
i do not use react bootstrap but it did not appear that your submit button was within the form therefor was not submitting the form.
Make sure react components are capitalized (match component should be Match)
I passed the whole 'place' down to the Match component via place prop:
<Match place={place} />
if you want to access the address like you did you would need to pass each individual value from the place down to the Match component like:
<Match address={place.address} />
(also if you are only initializing state before first render you can do so outside of the constructor)
I simplified the return statement to just use a plain input and button tag for simplicity but you can probably get going from here
Working Snippet:
const Match = ( props ) => {
return (
<div className="Matches">
<p>{`data is passed: ${props.place.street_address}`}</p>
</div>
)
};
class SomeComponent extends React.Component{
state = {
links: [],
selectedLink:null,
userLocation: {},
searchInput: "",
showMatches: false,
matches: []
}
componentDidMount() {
fetch('https://data.cityofnewyork.us/resource/s4kf-3yrf.json')
.then(res=> res.json())
.then(res=>
//console.log(json)
this.setState({links:res})
);
}
handleInputChange = (event) => {
event.preventDefault()
this.setState({searchInput: event.target.value })
//console.log(event.target.value)
}
handleSubmit = (event) => {
event.preventDefault()
this.displayMatches();
}
findMatches = (wordToMatch, my_obj) => {
return my_obj.filter(place => {
// here we need to figure out the matches
const regex = new RegExp(wordToMatch, 'gi');
//console.log(place.street_address.match(regex))
return place.street_address.match(regex)
});
}
displayMatches =() => {
const matchArray = this.findMatches(this.state.searchInput, this.state.links);
const newStateMatches = matchArray.map(place => {
console.log(place.street_address);
return place
});
this.setState({matches:newStateMatches})
this.setState({showMatches:true})
}
render() {
return (
<div>
<input
placeholder="Search for a Link Near you..."
onChange = {this.handleInputChange}
value = {this.state.searchInput}
/>
<button onClick={this.handleSubmit}>
Search
</button>
{this.state.matches.map((place)=>{
return <Match place={place} />
})}
</div>
);
}
}
ReactDOM.render(
<SomeComponent />,
document.getElementById("react")
);
<div id='react'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

How to Update This Reactjs Select

I have this wrapper class that is used because I am using Formik and the FieldArray
import React, { Component } from "react";
import { ReactDOM } from "react-dom";
import Select from "react-select";
import { observer } from "mobx-react";
import { axiosInstance } from "../stores/AxiosInstance";
#observer
export default class CountryStateSelectComponent extends React.Component {
constructor(props) {
super(props);
this.state = { stateOptions: [] };
}
handleCountryChange = value => {
const that = this;
axiosInstance
.get(`/States?countryId=${value.value}`)
.then(function(response) {
that.props.onChange(that.props.countryName, value);
that.props.onChange(that.props.stateName, null);
const states = response.data.map(state => {
return { label: state.name, value: state.id };
});
// if I move out state select code then won't need to update state here but don't know how to call something like updateState(record)
that.setState({
stateOptions: states
});
});
};
handleStateChange = value => {
console.log(this.props.stateName, value)
this.props.onChange(this.props.stateName, value);
};
handleCountryBlur = () => {
this.props.onBlur(this.props.countryName, true);
};
handleStateBlur = () => {
this.props.onChange(this.props.stateName, true);
};
render() {
const props = this.props;
return (
<React.Fragment>
<div className="field">
<label className="label">Country</label>
<div className="control">
<Select
options={props.options}
isMulti={props.isMulti}
onChange={this.handleCountryChange}
onBlur={this.handleCountryBlur}
closeMenuOnSelect={props.closeMenuOnSelect}
/>
{this.props.CountryError}
</div>
</div>
<div className="field">
<label className="label">State/Province</label>
<div className="control">
<Select
options={this.state.stateOptions}
onChange={this.handleStateChange}
onBlur={this.handleStateBlur}
/>
{this.props.StateError}
</div>
</div>
</React.Fragment>
);
}
}
However what I found is that when the State gets selected the value does not get stored in Formik(it gets stored as undefined and sometimes true).
So now I am thinking maybe moving out the State Zip out and making it's own wrapper or something but I don't know how to get the "states" that came back and populate the correct state box as they can generate many.
#inject("AccountSetupStore")
#observer
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = { records: [this.generateRecord(1, true, true)] };
}
componentDidMount() {
const accountSetupStore = this.props.AccountSetupStore;
accountSetupStore.getCountries();
}
updateState(record) {
// would like to call this method that can somehow update the record
// propblem is I don't seem to have access to props when this function is being called from the CountryStateSelectComponent
}
render() {
const props = this.props;
const accountSetupStore = props.AccountSetupStore;
const countries = [];
for (const country of accountSetupStore.countries) {
countries.push({ value: country.id, label: country.name });
}
return (
<section className="accordions">
<Formik
initialValues={{
records: this.state.records
}}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
console.log(values,"values");
}}
validationSchema={Yup.object().shape({
branches: Yup.array()
.of(
Yup.object().shape({
})
)
})}
render={({
values,
setFieldValue,
setFieldTouched,
}) => (
<FieldArray
name="records"
render={arrayHelpers => (
<Form>
{values.records.map((record, index) => {
return (<article}>
<CountryStateSelectComponent options={countries}
onChange={setFieldValue}
countryName={`records[${index}].selectedCountry`}
stateName={`records[0].selectedState`}
onBlur={setFieldTouched}
isMulti={false}
index = {index}
closeMenuOnSelect={true}
CountryError = {<ErrorMessage name={`records[${index}].selectedCountry`}/>}
StateError= {<ErrorMessage name={`records[${index}].selectedState`}/>}
/>
</article>)
})}
</Form>
)}
/>
)}
/>
</section>
);
}
}
React Select onChange sends the value to the method supplied
const onStateChange = (selectedOption, {action}) => {
//do something with the selectedOption according to the action
}
<Select onChange={onStateChange} />
See the documentation for the onChange in the Props documentation.

Categories

Resources