I am using react Ref in custom input field but it is not getting input content.
I am creating some ref in React class component and using them in form custom input field refs. then i have a button in the form which has "onClick" event to get that data. I am always getting undefined in console when i am getting "ref.current.value"
class LandingAfterSignIn extends React.Component {
titleTextFieldRef = React.createRef();
shortDescriptionMetaTextFieldRef = React.createRef();
longDescriptionMetaTextFieldRef = React.createRef();
imageFieldRef = React.
createRef();
webFieldRef = React.createRef();
contactTextFieldRef = React.createRef();
constructor(props) {
super(props);
// we use this to make the card to appear after the page has been rendered
this.state = {
cardAnimaton: "cardHidden",
isLoading: false,
error: null
};
// super(props);
this.state = {
errors: []
};
}
render() {
let image= require("assets/img/landing-bg.jpg") ;
const { classes, ...rest } = this.props;
//let titleTextFieldRef= this.titleTextFieldRef ;
//let shortDescriptionMetaTextFieldRef = this.shortDescriptionMetaTextFieldRef ;
return (
<div
className={classes.pageHeader}
style={{
backgroundImage: "url(" + image + ")",
backgroundSize: "cover",
backgroundPosition: "top center",
width: "100%"
}}
>
<Header
color="transparent"
routes={dashboardRoutes}
brand="StockNap"
rightLinks={<HeaderLinks />}
fixed
changeColorOnScroll={{
height: 400,
color: "white"
}}
{...rest}
/>
<div>
<div className={classes.container}>
<div className={classes.container}>
<GridContainer>
<GridItem xs={12} sm={12} md={6}>
<h1 className={classes.title}>Welcome {firebase.auth().currentUser.displayName} </h1>
<FirebaseDatabaseMutation type="push" path="user_bookmarks">
{({ runMutation }) => (
<form>
<GridContainer>
<GridItem xs={12} sm={12} md={6}>
<CustomInput
labelText="Company Name/Title"
id="titleTextField"
formControlProps={{
fullWidth: true
}}
inputRef={this.titleTextFieldRef}
>
</CustomInput>
</GridItem>
<GridItem xs={12} sm={12} md={6}>
<CustomInput
labelText="short Description"
id="shortDescription"
formControlProps={{
fullWidth: true,
className: classes.textArea
}}
inputRef={this.shortDescriptionMetaTextFieldRef}
/>
</GridItem>
<GridItem xs={12} sm={12} md={6}>
<CustomInput
labelText="long Description"
id="longDescription"
formControlProps={{
fullWidth: true,
className: classes.textArea
}}
inputProps={{
multiline: true,
rows: 2
}}
inputRef={this.longDescriptionMetaTextFieldRef}
/>
</GridItem>
<GridItem xs={12} sm={12} md={6}>
<CustomInput
labelText="contact"
id="contactTextField"
formControlProps={{
fullWidth: true,
}}
inputRef={this.contactTextFieldRef}
/>
</GridItem>
<Button
style={{
width: 50,
height: 50,
alignSelf: "center",
background: "#039BE5",
color: "white"
}}
variant="fab"
type="submit"
onClick={async ev => {
console.log("submit") ;
ev.preventDefault();
ev.stopPropagation();
ev.nativeEvent.stopImmediatePropagation();
const titleTextField = get(
this.titleTextFieldRef,
"current.value",
""
);
const shortDescriptionMetaTextField = get(
this.shortDescriptionMetaTextFieldRef,
"current.value",
""
);
const longDescriptionkMetaTextField = get(
this.longDescriptionMetaTextFieldRef,
"current.value",
""
);
const contactTextField = get(
this.contactTextFieldRef,
"current.value",
""
);
console.log(this.titleTextFieldRef);
console.log(this.shortDescriptionMetaTextFieldRef);
await runMutation({
titleTextField: titleTextField,
shortDescriptionMetaTextField: shortDescriptionMetaTextField,
created_at: firebase.database.ServerValue.TIMESTAMP,
updated_at: firebase.database.ServerValue.TIMESTAMP
});
set(this.titleTextFieldRef, "current.value", "");
set(this.shortDescriptionMetaTextFieldRef, "current.value", "");
}}
>
+
</Button>
</GridContainer>
</form>
)}
</FirebaseDatabaseMutation>
</GridItem>
</GridContainer>
</div>
</div>
</div>
<div className={classNames(classes.main, classes.mainRaised)}>
<div className={classes.container}>
</div>
</div>
<br/>
<Footer />
</div>
);
}
}
I want to add one more information , customInput are functional components
i want reference from parents to go into that
function CustomInput({ ...props }) {
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
white,
inputRootCustomClasses,
success
} = props;
const labelClasses = classNames({
[" " + classes.labelRootError]: error,
[" " + classes.labelRootSuccess]: success && !error
});
const underlineClasses = classNames({
[classes.underlineError]: error,
[classes.underlineSuccess]: success && !error,
[classes.underline]: true,
[classes.whiteUnderline]: white
});
const marginTop = classNames({
[inputRootCustomClasses]: inputRootCustomClasses !== undefined
});
const inputClasses = classNames({
[classes.input]: true,
[classes.whiteInput]: white
});
var formControlClasses;
if (formControlProps !== undefined) {
formControlClasses = classNames(
formControlProps.className,
classes.formControl
);
} else {
formControlClasses = classes.formControl;
}
return (
<FormControl {...formControlProps} className={formControlClasses}>
{labelText !== undefined ? (
<InputLabel
className={classes.labelRoot + " " + labelClasses}
htmlFor={id}
{...labelProps}
>
{labelText}
</InputLabel>
) : null}
<Input
classes={{
input: inputClasses,
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
{...inputProps}
/>
</FormControl>
);
}
From the official docs:
In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props.
Your first inclination may be to use refs to “make things happen” in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to “own” that state is at a higher level in the hierarchy. See the Lifting State Up guide for examples of this.
Currently, you're exactly doing what you should not be doing: using refs to access component methods. That is anti-pattern and make your code suceptible to bugs like you're experiencing right now. You should either:
Lift the state up if you need to pass data from one component to another
Use state container libraries like Redux if you need to trigger actions
In either way I strongly advise you to refactor your code in either direction.
Related
Update:
I think the problem is that Child 2 components are not rerendering because there are the same props when handleReset() is triggered in parent component.. but how can I make them rerendering? I tried with useEffect but nothing happens.
I'm struggling for several hours with the following issue:
I have a parent component in which I have some values passed from useState to child 1 (CustomerData) then to child 2 (-AddressForm and -CustomerForm).
My problem is that when the handleReset() from parent component is triggered, the values from child2 (AddressForm and CustomerForm) are not updated and I don't understand why. I Think that Child 2 components are not rerendering when I reset the values and somehow I need to pass in some other way the stated in the set hook from handleReset().
If somebody can help me solve this issue please. Here are my components:
Parent component:
function WidgetCustomerData(props): JSX.Element {
const customerDataTest = {
customerFirstName: 'firstNameTest',
customerLastName: 'lastNameTest',
customerPhone: '1313212',
customerEmail: 'test#test.com',
customerAddress: 'Musterstraße',
customerAddressNo: '12',
customerZip: '80335',
customerCity: 'Berlin',
deviceAddress: 'Straße',
deviceAddressNo: '152',
deviceZip: '32214',
deviceCity: 'Hamburg',
};
const [customerData, setCustomerData] = useState({
addressValue: customerDataTest.customerAddress,
addressValueNr: customerDataTest.customerAddressNo,
addressValueZip: customerDataTest.customerZip,
addressValueCity: customerDataTest.customerCity,
customerFirstName: customerDataTest.customerFirstName,
customerLastName: customerDataTest.customerLastName,
customerPhone: customerDataTest.customerPhone,
customerEmail: customerDataTest.customerEmail,
deviceAddress: customerDataTest.deviceAddress,
deviceAddressNo: customerDataTest.deviceAddressNo,
deviceZip: customerDataTest.deviceZip,
deviceCity: customerDataTest.deviceCity
})
const handleClose = () => {
props.handleClose();
}
const handleSubmit = async (e) => {
e && e.preventDefault();
const formUrl = 'https://fetch.mock/widget-customer-data/addCustomerData';
const formData = new FormData(e.target);
for (const [name, value] of formData) {
console.log(`${name}: ${value}`);
}
const response = await fetch(formUrl.toString(), {
method: 'POST',
body: formData,
credentials: 'same-origin',
});
const data = await response.json();
};
const handleReset = () => {
console.log('handleReset ----');
setCustomerData({
addressValue: customerDataTest.customerAddress,
addressValueNr: customerDataTest.customerAddressNo,
addressValueZip: customerDataTest.customerZip,
addressValueCity: customerDataTest.customerCity,
customerFirstName: customerDataTest.customerFirstName,
customerLastName: customerDataTest.customerLastName,
customerPhone: customerDataTest.customerPhone,
customerEmail: customerDataTest.customerEmail,
deviceAddress: customerDataTest.deviceAddress,
deviceAddressNo: customerDataTest.deviceAddressNo,
deviceZip: customerDataTest.deviceZip,
deviceCity: customerDataTest.deviceCity
})
};
return (
<div className="customer-data">
<Dialog
onClose={handleClose}
open={props.open}
aria-labelledby="customized-dialog-title"
PaperProps={{
style: { borderRadius: 20, minWidth: '80%', maxHeight: 'fit-content' },
}}>
<DialogTitle id="customized-dialog-title" onClose={handleClose} />
<Typography style={{ marginTop: 20, paddingLeft: 48, fontSize: 32, fontWeight: 600 }}>Kundendaten bearbeiten</Typography>
{/* <DialogContent> */}
<form onSubmit={handleSubmit}>
<div style={{ paddingLeft: 48, paddingRight:48 }}>
<CustomerData
customerData={customerData}
/>
</div>
<DialogActions>
<ResetButton onClick={handleReset} variant="contained" color="primary">
Reset
</ResetButton>
<SaveButton type="submit" variant="contained" color="primary">
Save
</SaveButton>
</DialogActions>
</form>
{/* </DialogContent> */}
</Dialog>
</div>
);
}
export default WidgetCustomerData;
Child1
export default ({nextStepAction, customerData }: MoleculeSystemManagementCustomerDataProps): JSX.Element => {
return (
<div className='c-data'>
<CustomerForm customerData={customerData} />
<AddressForm formId='customer' customerData={customerData} />
</div>
);
}
Child2
function AddressForm({formId customerData }): JSX.Element {
const classes = useStyles();
const { addressValue, addressValueNr, addressValueZip, addressValueCity, deviceAddress, deviceAddressNo, deviceZip, deviceCity } = customerData;
return (
<Grid container className={classes.borderedRow}>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<div>
<InputField type='label' id='address' name={`${formId}-address`} value={formId === 'customer' ? addressValue : deviceAddress} label={'Street'} />
</div>
</Grid>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<InputField type='label' id='city' name={`${formId}-city`} value={formId === 'customer' ? addressValueNr : deviceAddressNo} label={'Number'} />
</Grid>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<InputField type='label' id='state' name={`${formId}-state`} value={formId === 'customer' ? addressValueZip : deviceZip} label={'ZIP'} />
</Grid>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<InputField type='label' id='zip' name={`${formId}-zip`} value={formId === 'customer' ? addressValueCity : deviceCity} label={'ORT'} />
</Grid>
</Grid>
);
}
export default AddressForm;
I think the problem is occurring because the state is only being passed to the first child component.
If you look carefully, you'll see that during the passage from the child (1) to child(2) there's no state dealing with it. React is not going to update the value of your props if it's not a state.
Probably, you just need to create a state within the first child. Look the example:
Child 1
export default ({nextStepAction, customerData }: MoleculeSystemManagementCustomerDataProps): JSX.Element => {
const [addressIsDifferent, setAddressIsDifferent] = useState(true);
const [customerDataState, setCustomerDataState] = useState(customerData)
return (
<div className='c-data'>
<CustomerForm customerData={customerData} />
<AddressForm formId='customer' customerData={customerDataState} />
</div>
);
}
Extra tips: I don't what are the use cases of your project, but I highly recommend you to take a look at React Context API
It's a great idea to use it if you need to pass data through components that need the same data and do cohesive functions.
In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.
it looks like you are setting the state to the same data as it was initialised. you initialise it with customerDataTest and then set it to customerDataTest. same thing
I am trying to change a parent's component state from a child component's state.
This is the parent component:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
loggedin: false
}
this.updateLanguage = this.updateLanguage.bind(this)
this.logIn = this.logIn.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
logIn() {
this.setState({
loggedin: true
})
}
render() {
const { selectedLanguage, repos, error, loggedin } = this.state
console.log(loggedin)
return (
<Router>
<div className='container'>
<LanguagesNav
selected={selectedLanguage}
onUpdateLanguage={this.updateLanguage}
/>
<Route
exact path='/'
render={(props) => (
<Login
repos={repos}
selectedLanguage={selectedLanguage}
logIn={this.logIn}
/>
)}
/>
<Route
path='/dashboard'
render={(props) => (
<Dashboard
repos={repos}
selectedLanguage={selectedLanguage}
/>
)}
/>
<Route
path='/profile'
render={(props) => (
<Profile
repos={repos}
selectedLanguage={selectedLanguage}
/>
)}
/>
<Route path='/newdashboard'>
<DrawerPage />
</Route>
</div>
</Router>
)
}
}
As I saw on this answer to a similar question, I am passing a function setting the state from parent to child, and then, I call the function via props from the child component:
function LoginForm ({ repos, selected }) {
const languages = ['EU', 'ES', 'EN']
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://loginsso.ehu.es/login/images/forest.jpg)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src="https://loginsso.ehu.es/login/images/logo_UPV_peq.png"
/>
<br/>
<Box component="form" noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label={language.username}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label={language.password}
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label={language.remember}
/>
<Link
to={{
pathname: '/dashboard',
search: `?lang=${selected}`
}}
>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
// onClick={() => {
// alert('clicked');
// }}
>
{language.login}
</Button>
</Link>
<Grid container>
<Grid item xs>
<Link
to={{
pathname: '/newdashboard',
search: `?lang=${selected}`
}} variant="body2">
{language.forgot}
</Link>
</Grid>
<Grid item>
</Grid>
</Grid>
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
}
this.updateLanguage = this.updateLanguage.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
render() {
const { selectedLanguage, repos, error } = this.props
return (
<React.Fragment>
{this.props.logIn} //calling the parent's function
{error && <p>{error}</p>}
{repos && <LoginForm repos={repos} selected={selectedLanguage}/>}
</React.Fragment>
)
}
}
However, the state won't change.
Okay, I found a solution to my question. I tried this on the child component:
function LoginForm ({ repos, selected, onLogIn }) {
const languages = ['EU', 'ES', 'EN']
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://loginsso.ehu.es/login/images/forest.jpg)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src="https://loginsso.ehu.es/login/images/logo_UPV_peq.png"
/>
<br/>
<Box component="form" noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label={language.username}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label={language.password}
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label={language.remember}
/>
<Link
to={{
pathname: '/dashboard',
search: `?lang=${selected}`
}}
>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
// I call the child component's method which at the same time calls the parent's component's method.
onClick={() => {onLogIn()}}
>
{language.login}
</Button>
</Link>
<Grid container>
<Grid item xs>
<Link
to={{
pathname: '/newdashboard',
search: `?lang=${selected}`
}} variant="body2">
{language.forgot}
</Link>
</Grid>
<Grid item>
</Grid>
</Grid>
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null
}
this.updateLanguage = this.updateLanguage.bind(this)
this.logIn = this.logIn.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
// Created this function that calls the parent's logIn function.
logIn() {
this.props.logIn();
}
render() {
const { selectedLanguage, repos, error, logIn } = this.props
return (
<React.Fragment>
{error && <p>{error}</p>}
{/*I passed child component's method to the function component with the onLogIn attribute.*/}
{repos && <LoginForm repos={repos} selected={selectedLanguage} onLogIn={this.logIn}/>}
</React.Fragment>
)
}
}
Basically, I created a method on the child component that calls the method of the parent component.
I am not sure if I am using the right words to describe the solution, but I have used this solution in other parts of the project and it always works.
Feel free to comment with suggestions to explain my solution more correctly! :)
I am trying to insert a google maps map in a React application. I would rather not use a non-official library (the ones that I have found lack documentation) and I have already managed inserting the map.
My problem is that the map is re-rendered every time the state of the parent component changes; although the values that change are completely irrelevant from what the map needs.
After a bit of research (I am new to React) I came across the React.memo() HOC which is supposed to prevent re-renders of child components when their props are unchanged. For some reason however I cannot get it to work correctly. Event when I insert the map inside a component with no props, any change in the parent state results in a re-render of the map.
Here is the parent component:
const CompanyDepotsPopup = () => {
const classes = useStyles();
const dispatch = useDispatch();
const open = useSelector((state) => selectIsDepotsPopupOpen(state));
const company = useSelector((state) => selectSelectedCompany(state));
const depotsStatus = useSelector((state) => selectDepotsStatus(state));
const {t} = useTranslation();
const [value, setValue] = useState(0);
const [phone, setPhone] = useState("");
const handleChange = (event, newValue) => {
setValue(newValue);
};
const closeModal = () => {
dispatch(toggleDepotsPopup({}));
}
useEffect(() => {
if (company) {
dispatch(depotsListed({companyId: company.id}));
}
}, [company])
if (!company) return <></>;
if (depotsStatus === "loading") {
return <CenteredLoader/>
}
function TabPanel(props) {
const {children, value, index} = props;
return (
<div
hidden={value !== index}
style={{height: "100%"}}
>
{value === index && (
<Box boxShadow={3} mt={1} ml={2} mr={2} height={"100%"}>
{children}
</Box>
)}
</div>
);
}
return (
<Dialog fullWidth={true} open={open}
aria-labelledby="company-dialog-popup">
<DialogTitle >
{company.name}
</DialogTitle>
<DialogContent style={{padding: 0, margin: 0}}>
<Divider/>
<Box mr={0} ml={0} mt={0} p={0} height="95%">
<div >
<AppBar position="static">
<Tabs value={value} onChange={handleChange} aria-label="depots tabs" centered>
<Tab label={t("Company's depots list")}/>
<Tab label={t("Add new depot")}/>
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<DepotsList/>
</TabPanel>
<TabPanel value={value} index={1}>
<Paper>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12}>
<TextField
onChange={(event) => setPhone(event.target.value)}
id="phone"
label={t("Phone")}
type="text"
fullWidth
value={phone}
/>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={12}>
<div style={{height: "250px", display: "flex", "flexDirection": "column"}}>
<MyMap
id="myMap"
/>
</div>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={12} align={"center"}>
<Button variant={"outlined"}>
{t("Save")}
</Button>
</Grid>
</Grid>
</Paper>
</TabPanel>
</div>
</Box>
</DialogContent>
<DialogActions style={{marginTop: "20px"}}>
<Button
variant={"outlined"}
onClick={closeModal}
color="secondary"
>
Done
</Button>
</DialogActions>
</Dialog>
)}
And here is the Map component:
import React, {useEffect} from "react";
const Map = ({id}) => {
const onScriptLoad = () => {
const map = new window.google.maps.Map(
document.getElementById(id),
{
center: {lat: 41.0082, lng: 28.9784},
zoom: 8
}
);
const marker = new window.google.maps.Marker({
position: {lat: 41.0082, lng: 28.9784},
map: map,
title: 'Hello Istanbul!'
});
}
useEffect(() => {
if (!window.google) {
const s = document.createElement("script");
s.type = "text/javascript";
s.src = "https://maps.google.com/maps/api/js?key=''"
const x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
s.addEventListener('load', e => {
onScriptLoad();
})
} else {
onScriptLoad();
}
}, []);
return (
<div style={{width: "100%", height: "100%"}} id={id}/>
);
}
const MyMap = React.memo(Map);
export default MyMap;
Every time setPhone is called when the user types the phone and the state changes, the map is re-rendered. Could someone explain to me why the React.memo does not work and how should I proceed in order to avoid re-rendering the map?
I think my guts feeling is this component
function TabPanel(props) {
const {children, value, index} = props;
return (
<div
hidden={value !== index}
style={{height: "100%"}}
>
{value === index && (
<Box boxShadow={3} mt={1} ml={2} mr={2} height={"100%"}>
{children}
</Box>
)}
</div>
);
}
This is defined inside a component, therefore the instance of this component keeps changing after any state change. In order to prevent it, move it outside of the component, like this
function TabPanel()
function CompanyDepotsPopup()
Instead of
function CompanyDepotsPopup() {
function TabPanel()
}
The reason is also your TabPanel wraps everything else.
I have multiple elements in the state, and when input slider is dragged i want to set the value into state elements, i've tried to write one function that will setState for the dragged elements in state but it isn't working
Here's what i've tried. When slider changes in the browser it shows -- Cannot read property 'name' of undefined.
Can anyone help to solve this problem. I just want to set slider value into state with one function.
import React, {Component} from "react";
import './table.css';
import { Table, Slider, InputNumber, Row, Col } from 'antd';
export default class TableRender extends Component{
state = {
averageCheck: 0,
newSales: 0,
customerChurn:0,
reSales:0,
totalSales:0,
advertisingBudget: 0,
advertisingAgency: 0,
kpi:0,
salesManager:0,
};
handleChange=(event)=>{
let nam = event.target.name;
let val = event.target.value;
this.setState({[nam]: val});
console.log(nam)
};
render(){
const { averageCheck, newSales, customerChurn } = this.state;
return (
<div className="slider1">
<h2>Tovar</h2>
<p>Tovar o'rtacha qiymati</p>
<Row>
<Col span={12}>
<Slider
name="averageCheck"
min={10000}
max={30000000}
step={5000}
onChange={this.handleChange}
value={typeof averageCheck === 'number' ? averageCheck : 0}
/>
</Col>
<Col span={2}>
<InputNumber
min={10000}
max={30000000}
step={5000}
style={{ margin: '0 16px' }}
value={averageCheck}
onChange={this.handleChange}
/>
</Col>
</Row>
<p>Yangi Sotuvlar</p>
<Row>
<Col span={12}>
<Slider
name="newSales"
min={1000000}
max={30000000}
step={5000}
onChange={this.handleChange}
value={typeof newSales === 'number' ? newSales : 0}
/>
</Col>
<Col span={2}>
<InputNumber
min={1000000}
max={30000000}
step={5000}
style={{ margin: '0 16px' }}
value={newSales}
onChange={this.handleChange}
/>
</Col>
</Row>
<p>Xaridor Kamayishi, %</p>
<Row>
<Col span={12}>
<Slider
name="customerChurn"
min={1}
max={100}
step={1}
marks={marks}
style={{ borderColor: 'green' }}
onChange={this.handleChange}
value={typeof customerChurn === 'number' ? customerChurn : 0}
/>
</Col>
<Col span={2}>
<InputNumber
min={1}
max={100}
step={1}
style={{ margin: '0 16px' }}
value={customerChurn}
onChange={this.handleChange}
/>
</Col>
</Row>
</div>
)
}
}
The antd Slider onChange callback return a value not an event object that's why its give you an error of accessing undefined. Try to modify the onChange callback like this:
<Slider
min={10000}
max={30000000}
step={5000}
onChange={(value) => this.handleChange("averageCheck", value)}
value={typeof averageCheck === "number" ? averageCheck : 0}
/>;
and in your handleChange function:
handleChange = (name, value) => {
let nam = name;
let val = value;
this.setState({ [nam]: val });
console.log(nam);
};
It can be also applied on your InputNumber so you are still using one function
I am using 'react-form-validator-core' package and trying to create a custom form validator that implements 'mui-downshift', a Material UI implementation of PayPal's downshift. This question is mostly about 'react-form-validator-core' package itself. The problem is that the form itself does not register the validator component I've created. Here is my full code of the custom component and the form itself. I've exhausted my debugging skills, but what I noticed is that there's something wrong with the this.context in the form...
Validator component:
import React from 'react';
import PropTypes from 'prop-types';
import MuiDownshift from 'mui-downshift';
import { ValidatorComponent } from 'react-form-validator-core';
class AutocompleteValidator extends ValidatorComponent {
constructor(props) {
debugger;
super(props);
this.originalItems = props.items.map(({key, name}) => ({ text: name, value: key }));
this.handleStateChange = this.handleStateChange.bind(this);
this.errorText = this.errorText.bind(this);
}
componentWillMount() {
if (!this.filteredItems) {
this.setState({filteredItems: this.originalItems});
}
if (!!this.props.value) {
const selectedItem = this.originalItems.filter(
item => item.value.toLowerCase().includes(this.props.value.toLowerCase())
)[0];
this.setState({ selectedItem })
} else {
this.setState({ selectedItem: null})
}
}
componentWillReceiveProps(nextProps) {
// If no filteredItems in sate, get the whole list:
if (!nextProps.value) {
this.setState({ isValid: false })
}
}
handleStateChange(changes) {
// If searching
if (changes.hasOwnProperty('inputValue')) {
const filteredItems = this.originalItems.filter(
item => item.text.toLowerCase().includes(changes.inputValue.toLowerCase())
);
this.setState({ filteredItems })
}
// If something is selected
if (changes.hasOwnProperty('selectedItem')) {
!!changes.selectedItem ? this.setState({isValid: true}) : this.setState({isValid: false})
// If we get undefined, change to '' as a fallback to default state
changes.selectedItem = changes.selectedItem ? changes.selectedItem : '';
this.props.onStateChange(changes);
}
}
errorText() {
const { isValid } = this.state;
if (isValid) {
return null;
}
return (
<div style={{ color: 'red' }}>
{this.getErrorMessage()}
</div>
);
}
render() {
return (
<div>
<MuiDownshift
{...this.props}
items={this.state.filteredItems}
onStateChange={this.handleStateChange}
ref={(r) => { this.input = r; }}
defaultSelectedItem={this.state.selectedItem}
/>
{this.errorText()}
</div>
);
}
}
AutocompleteValidator.childContextTypes = {
form: PropTypes.object
};
export default AutocompleteValidator;
A component where it's used:
render() {
return (
<ValidatorForm
ref='form'
onSubmit={() => {
this.context.router.history.push(this.props.config.urls['step5']);
}}
onError={errors => console.log(errors)}
>
<Row>
<Col md={12}>
<AutocompleteValidator
validators={['required']}
errorMessages={['Cette information doit être renseignée']}
isRequired={true}
name='bankId'
items={this.props.config.list.bank}
onStateChange={(changes) => {
this.props.loansUpdate('bankId', changes.selectedItem.value);
}}
value={!!this.props.loans.bankId ? this.props.loans.bankId : false}
/>
</Col>
</Row>
<Row>
<Col md={12} style={{ marginTop: '15px' }}>
<Checkbox
label={<Translate value='step4.insuranceProvidedByBank' />}
labelStyle={{ 'top': '0px' }}
name='insuranceProvidedByBank'
value={this.props.loans.insuranceProvidedByBank}
checked={this.props.loans.insuranceProvidedByBank}
onCheck={(event, value) => {
this.props.loansUpdate('insuranceProvidedByBank', value);
}}
/>
</Col>
</Row>
<Row>
<Col sm={6} md={5}>
<Button
fullWidth
style={{ marginTop: '50px', marginBottom: '20px' }}
type='submit'
icon={<i className='fa fa-angle-right' style={{ marginLeft: '10px', display: 'inline-block' }} />}
><Translate value='iContinue' /></Button>
</Col>
</Row>
</ValidatorForm >
)
};
};
you get this problem because you override default componentWillMount and componentWillReceiveProps methods from ValidatorComponent. So, to solve this case you should do something like this
componentWillMount() {
// should call parent method before
super.componentWillMount();
// your code
}