How to call child component method from parent - javascript

In my Reactjs app, I need to have a parent component (a wizard) named Wizard.js and a number of child components (steps of the wizard) named PrimaryForm.js, SecondaryForm.js etc. They all are Class based components with some local validation functions.
Previous and Next buttons to advance the steps, reside in the Wizard.js.
To advance the next step of the wizard, I'm trying to call a method from PrimaryForm. I checked similar questions in Stackoverflow; tried using ref or forwardRef, but I could not make it work. I currently receive "TypeError: Cannot read property 'handleCheckServer' of null" error.
Below are my parent and child classes. Any help about what I would be doing wrong is appreciated.
Wizard.js:
import React, { Component } from 'react';
...
const getSteps = () => {
return [
'Info',
'Source Details',
'Target Details',
'Configuration'
];
}
class Wizard extends Component {
constructor(props) {
super(props);
this.firstRef = React.createRef();
this.handleNext = this.handleNext.bind(this);
this.state = {
activeStep: 1,
}
}
componentDidMount() {}
handleNext = () => {
if (this.state.activeStep === 1) {
this.firstRef.current.handleCheckServer(); <<<<<<<<<<<<<<<<< This is where I try to call child method
}
this.setState(state => ({
activeStep: state.activeStep + 1,
}));
};
handleBack = () => {
this.setState(state => ({
activeStep: state.activeStep - 1,
}));
};
handleReset = () => {
this.setState({
activeStep: 0,
});
};
render() {
const steps = getSteps();
const currentPath = this.props.location.pathname;
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<Topbar currentPath={currentPath} />
<div className={classes.root}>
<Grid container spacing={2} justify="center" direction="row">
<Grid container spacing={2} className={classes.grid} justify="center" direction="row">
<Grid item xs={12}>
<div className={classes.topBar}>
<div className={classes.block}>
<Typography variant="h6" gutterBottom>Wizard</Typography>
<Typography variant="body1">Follow the wizard steps to create a configuration.</Typography>
</div>
</div>
</Grid>
</Grid>
<Grid container spacing={2} alignItems="center" justify="center" className={classes.grid}>
<Grid item xs={12}>
<div className={classes.stepContainer}>
<div className={classes.bigContainer}>
<Stepper classes={{ root: classes.stepper }} activeStep={this.state.activeStep} alternativeLabel>
{steps.map(label => {
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
);
})}
</Stepper>
</div>
<PrimaryForm ref={this.firstRef} />
</div>
</Grid>
</Grid>
<Grid container spacing={2} className={classes.grid}>
<Grid item xs={12}>
<div className={classes.flexBar}>
<Tooltip title="Back to previous step">
<div>
<Button variant="contained"
disabled={(this.state.activeStep === 0)}
className={classes.actionButton}
onClick={this.handleBack}
size='large'>
<BackIcon className={classes.rightIcon} />Back
</Button>
</div>
</Tooltip>
<Tooltip title="Proceed the next step">
<div>
<Button
variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!(!this.state.isFormValid || this.state.isTestWaiting)}
onClick={this.handleNext}>
<ForwardIcon className={this.props.classes.rightIcon}/>Next</Button>
</div>
</Tooltip>
<Tooltip title="Cancel creating new configuration">
<Button variant="contained" color="default" className={classes.actionButton}
component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
<CancelIcon className={classes.rightIcon} />Cancel
</Button>
</Tooltip>
</div>
</Grid>
</Grid>
</Grid>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(Wizard));
PrimaryForm.js:
import React, { Component } from 'react';
...
class PrimaryForm extends Component {
constructor(props) {
super(props);
this.handleCheckServer = this.handleCheckServer.bind(this);
this.state = {
hostname: {
value: "localhost",
isError: false,
errorText: "",
},
serverIp: {
value: "127.0.0.1",
isError: false,
errorText: "",
},
isFormValid: true,
isTestValid: true,
testErrorMessage: "",
isTestWaiting: false,
};
}
componentDidMount() { }
handleCheckServer() {
alert('Alert from Child. Server check will be done here');
}
evaluateFormValid = (prevState) => {
return ((prevState.hostname.value !== "" && !prevState.hostname.isError) &&
(prevState.serverIp.value !== "" && !prevState.serverIp.isError));
};
handleChange = event => {
var valResult;
switch (event.target.id) {
case 'hostname':
valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
hostname:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
});
break;
case 'serverIp':
valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
serverIp:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
default:
}
this.setState(prevState => ({
...prevState,
isFormValid: this.evaluateFormValid(prevState),
}));
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<div className={classes.bigContainer}>
<Paper className={classes.paper}>
<div>
<div>
<Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
Primary System
</Typography>
<Typography variant="body1" gutterBottom>
Information related with the primary system.
</Typography>
</div>
<div className={classes.bigContainer}>
<form className={classes.formArea}>
<TextField className={classes.formControl}
id="hostname"
label="FQDN Hostname *"
onChange={this.handleChange}
value={this.state.hostname.value}
error={this.state.hostname.isError}
helperText={this.state.hostname.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="serverIp"
label="Server Ip Address *"
onChange={this.handleChange}
value={this.state.serverIp.value}
error={this.state.serverIp.isError}
helperText={this.state.serverIp.errorText}
variant="outlined" autoComplete="off" />
</form>
</div>
</div>
</Paper>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(PrimaryForm));
(ps: I would like to solve this without another framework like Redux, etc if possible)

Example in Typescript.
The idea is that the parent passes its callback to the child. The child calls the parent's callback supplying its own e.g. child callback as the argument. The parent stores what it got (child callback) in a class member variable and calls it later.
import * as React from 'react'
interface ICallback {
(num: number): string
}
type ChildProps = {
parent_callback: (f: ICallback) => void;
}
class Child extends React.Component {
constructor(props: ChildProps) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback: ICallback = (num: number) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
readonly state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun: ICallback) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback: ICallback | undefined = undefined;
}
P.S.
The same code in Javascript. The only difference is that interface, type, readonly and type annotations are taken out. Pasting into here confirms it's a valid ES2015 stage-2 code.
class Child extends React.Component {
constructor(props) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback = (num) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback = undefined;
}

Related

Context API not displaying array of data

I'm receiving the error 'expected an assignment of function call and instead saw an expression.
*The above error is resolved, it's now giving me an Unhandled Rejection (TypeError): render is not a function. It functions properly until I go to the products page and then gives me the error.
I console logged it and it was pulling the information but then it breaks when I import the ProductBrowse component to display the information.
ProductPage:
class ProductPage extends React.Component {
state = {
shoppingCartOpen: false,
};
drawerToggleClickHandler = () => {
this.setState((prevState) => {
return { shoppingCartOpen: !prevState.shoppingCartOpen };
});
};
render() {
let shoppingCartDrawer;
if (this.state.shoppingCartOpen) {
shoppingCartDrawer = <ShoppingCartDrawer />;
}
return (
<ProductStyling>
<ButtonAppBar drawerClickHandler={this.drawerToggleClickHandler} />
<H1>Products</H1>
{shoppingCartDrawer}
<ProductConsumer>
<Grid container spacing={3}>
{(value) => {
return value.products.map((prod, idx) => {
return (
<Grid item xs={3} key={`${prod.name}-${prod.store}-${idx}`}>
<ProductBrowse
productName={prod.name}
productDesc={prod.desc}
productImg={prod.img}
productPrice={prod.price}
/>
</Grid>
);
});
}}
</Grid>
</ProductConsumer>
;
</ProductStyling>
);
}
}
Structure of value:
const ProductContext = React.createContext();
class ProductProvider extends React.Component {
state = {
products: productData,
};
addToCart = () => {
console.log('Hello from add to cart'); };
render() {
return (
<ProductContext.Provider
value={{ ...this.state, addToCart: this.addToCart }}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
ProductBrowse:
const ProductBrowse = ({
productName,
productDesc,
productImg,
productPrice,
}) => {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card className={classes.root}>
<CardActionArea onClick={() => openModal()}>
<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={productName}
productDesc={productDesc}
/>
<CardHeader
title={productName}
subheader={formatCurrency(productPrice)}
/>
<CardMedia
className={classes.media}
image={productImg}
alt={productDesc}
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='p'>
{productDesc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size='small' /*To Checkout*/>BUY NOW</Button>
<Button size='small'>ADD TO CART</Button>
<Button size='small'>REVIEW</Button>
</CardActions>
</Card>
</Box>
);
};
ProductData:
import desk from '../../../assets/img/desk.webp';
export const productData = [
{
img: desk,
name: 'Desk',
store: 'Local Furniture Shop 1',
price: 9.99,
desc: "This sturdy desk is built to outlast years of coffee and hard work. You get a generous work surface and a clever solution to keep cords in place underneath."
},
Index.js:
ReactDOM.render(
<React.StrictMode>
<Auth0Provider
domain={auth0Domain}
client_id={auth0ClientID}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
audience={auth0Audience}
scope={"read:current_user"}
>
<ProductProvider>
<Provider store={store}>
<App />
</Provider>
</ProductProvider>
</Auth0Provider>
</React.StrictMode>,
document.getElementById('root')
);
You are not returning anything from ProductConsumer You need to do like this:
<ProductConsumer>
<Grid container spacing={3}>
{(value) => {
return value.products.map((prod, idx) => {
return (
<Grid item xs={3} key={`${prod.name}-${prod.store}-${idx}`}>
<ProductBrowse
productName={prod.name}
productDesc={prod.desc}
productImg={prod.img}
productPrice={prod.price}
/>
</Grid>
);
});
}}
</Grid>
</ProductConsumer>;

onClick gets fired immediately on component mount using firestore and React, redux

I've got a component, that displays a project. For each project, there is a delete button, but for some reason, the delete buttons of all my projects get "pressed" immediately.
Why would this happen? I'm using redux and firestore. I think this has maybe something to do with the realtime listener, but don't have any problems with adding data to the database.
Projects.js
componentDidMount = () => {
this.props.projectActions.registerProjectListener();
};
renderProjects = () => {
const { projects } = this.props.projects;
const { classes, projectActions } = this.props;
return (
<Paper elevation={0} square={true} className={classes.projectPaper}>
<Box fontSize="h5.fontSize" textAlign="center">
Your Projects:
</Box>
{projects &&
projects.map(project => {
return <Project {...{ key: project.id, project, projectActions }}></Project>;
})}
<Grid container className={classes.grid} direction="row" justify="flex-end" alignItems="center">
<AddProject {...{ projectActions }}></AddProject>
</Grid>
</Paper>
);
};
Project.js
export class Project extends Component {
render() {
const { classes, project, projectActions } = this.props;
console.log(project.id);
return (
<Paper elevation={3} variant="outlined" className={classes.paper}>
<Grid container justify="space-between">
<Box fontSize="h6.fontSize">{project.name}</Box>
<IconButton onClick={projectActions.deleteProject(project.id)}> //This gets fired immediately for every project
<ClearIcon></ClearIcon>
</IconButton>
</Grid>
</Paper>
);
}
}
actions
export const deleteProject = id => {
return dispatch => {
console.log(db.collection("projects").doc(id));
// db.collection("projects")
// .doc(id)
// .delete();
dispatch({ type: ActionTypes.DELETE_PROJECT });
};
};
export const registerProjectListener = () => {
let projects = [];
return dispatch => {
db.collection("projects").onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") {
console.log(change.doc.data());
projects.push({ ...change.doc.data(), ...{ id: change.doc.id } });
} else if (change.type === "removed") {
// projects.filter(()=> )
}
});
dispatch({ type: ActionTypes.REGISTER_LISTENER, projects: projects });
});
};
};
You need to pass reference to a function. Update the IconButton component with the following.
<IconButton
onClick={() => projectActions.deleteProject(project.id)}>
<ClearIcon></ClearIcon>
</IconButton>

How to change an array element in state in a React

I am trying to change an element of an array using the handleToggle method, but an error occurs, what am I doing wrong, and why should I always return a new array in the React?
Child component:
function Todolist(props) {
const todoItem = props.todos.map((todo, index) =>
<ListItem key={todo.id} dense button>
<ListItemIcon>
<Checkbox checked={todo.completed} onChange={props.onChange(todo.id)} edge="start"/>
</ListItemIcon>
<ListItemText primary={todo.title} />
<ListItemSecondaryAction>
<IconButton edge="end" aria-label="comments"></IconButton>
</ListItemSecondaryAction>
</ListItem>
)
return (
<div>
{todoItem}
</div>
)
}
Parent component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
todos: [
{
title: 'Learn React',
id: Math.random(),
completed: true
}
]
}
};
handleChange = (evt) => {
this.setState({
value: evt.target.value
})
};
handleSubmit = (evt) => {
evt.preventDefault();
const todos = [...this.state.todos];
todos.push({
title: this.state.value,
id: Math.random(),
completed: false
});
this.setState(state => ({
todos,
value: ''
}))
};
handleToggle = (id) => {
const todos = [...this.state.todos];
todos.map(todo => {
if (todo.id === id) {
return todo.completed = !todo.completed
} else {
return todo
}
});
this.setState(state => ({
todos
}))
};
render() {
return (
<div className="App">
<Grid className="Header" justify="center" container>
<Grid item xs={11}>
<h1 className="Title">My to-do react app</h1>
<FormBox value={this.state.value} onSubmit={this.handleSubmit} onChange={this.handleChange}/>
</Grid>
</Grid>
<TodoList todos={this.state.todos} onChange={this.handleToggle} />
</div>
);
}
}
Text of error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
You are executing onChange immediately
onChange={props.onChange(todo.id)}
Wrap the callback in an arrow function
onChange={() => props.onChange(todo.id)}
Also your handleToggle isn't changing the values correctly (mutating), try like this
handleToggle = (id) => {
const todos = [...this.state.todos];
this.setState({ todos : todos.map(todo => ({
...todo,
completed : todo.id === id ? !todo.completed : todo.completed
}) }))
}

Search functionality is not working for the frontend

I am getting data from the backend using postman. But when i am using frontend for the same, it is not working. I am getting errors like
1.TypeError: Cannot read property 'map' of null
2.Unhandled Rejection (TypeError): Cannot read property 'map' of null.
I think, I am getting this error because cards are not able to render when I am searching.The backend data is coming as a array.
const styles = theme => ({
appBar: {
position: 'relative',
},
icon: {
marginRight: theme.spacing.unit * 2,
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
[theme.breakpoints.up(1100 + theme.spacing.unit * 3 * 2)]: {
width: 1100,
marginLeft: 'auto',
marginRight: 'auto',
},
},
cardGrid: {
padding: `${theme.spacing.unit * 8}px 0`,
},
card: {
height: '100%',
display: 'flex',
flexDirection: 'column',
},
cardContent: {
flexGrow: 1,
},
});
class Products extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
searchString: ''
};
this.onSearchInputChange = this.onSearchInputChange.bind(this);
this.getProducts = this.getProducts.bind(this);
}
componentDidMount() {
this.getProducts();
}
// delete = id => {
// axios.post('http://localhost:9022/products/delete/' + id)
// .then(res => {
// let updatedProducts = [...this.state.products].filter(i => i.id !== id);
// this.setState({ products: updatedProducts });
// });
// }
delete = id => {
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
this.setState((prevState, prevProps) => {
let updatedProducts = [...prevState.products].filter(i => i.id !== id);
return ({
products: updatedProducts
});
});
});
}
getProducts() {
axios.get('http://localhost:9022/products/getAll')
.then(res => {
this.setState({ products: res.data }, () => {
console.log(this.state.products);
});
});
}
onSearchInputChange = (event) => {
let newSearchString = '';
if (event.target.value) {
newSearchString = event.target.value;
}
axios.get('http://localhost:9022/products/getproducts' + newSearchString)
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
this.getProducts();
}
// onSearchInputChange(event) {
// let newSearchString = '';
// if (event.target.value) {
// newSearchString = event.target.value;
// }
// // call getProducts once React has finished updating the state using the callback (second argument)
// this.setState({ searchString: newSearchString }, () => {
// this.getProducts();
// });
// }
render() {
const { classes } = this.props;
return (
<React.Fragment>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<CssBaseline />
<main>
<div className={classNames(classes.layout, classes.cardGrid)}>
<Grid container spacing={40}>
{this.state.products.map(currentProduct => (
<Grid item key={currentProduct.id} sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{currentProduct.title}
</Typography>
<Typography>
{currentProduct.price}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" component={Link} to={"/products/" + currentProduct.id}>
Edit
</Button>
<Button size="small" color="primary" onClick={() => this.delete(currentProduct.id)}>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</main>
</React.Fragment>
)
}
}
Products.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Products);
It has been observed that, you have added wrong URL of getproducts, slash is missing in the URL. Please find details below:
If search-string is r, then you are using this URL of getproducts: http://localhost:9022/products/getproductsr
which is wrong and it should be http://localhost:9022/products/getproducts/r
Hence you have to change your code of retrieving products as follows:
axios.get('http://localhost:9022/products/getproducts/' + newSearchString)
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
Also it will be good to provide a check for undefined/null for this.state.products and then render the components because it is possible that products might be null if one provide wrong URL and undefined as axios request is async. Hence by adding 'this.state.products && ' in existing render code will be good to avoid such issues. I have updated your render function, please find it below:
render() {
const { classes } = this.props;
return (
<React.Fragment>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<CssBaseline />
<main>
<div className={classNames(classes.layout, classes.cardGrid)}>
<Grid container spacing={40}>
{this.state.products && this.state.products.map(currentProduct => (
<Grid item key={currentProduct.id} sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{currentProduct.title}
</Typography>
<Typography>
{currentProduct.price}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" component={Link} to={"/products/" + currentProduct.id}>
Edit
</Button>
<Button size="small" color="primary" onClick={() => this.delete(currentProduct.id)}>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</main>
</React.Fragment>
)
}
Hope it will help..
You may try this one:
You just missed the slash in the api end Point as below : Use this
axios.get('http://localhost:9022/products/getproducts/' + newSearchString)
instead of :
axios.get('http://localhost:9022/products/getproducts' + newSearchString)

react-form-validator not registering children components?

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
}

Categories