I have LIVR in a project i'm working now and is quite unclear to me how this work. I can't understand how to create new rules for custom validation.
Here's the code:
LIVR.Validator.defaultAutoTrim(true);
let validator = new LIVR.Validator({});
LIVR.Validator.registerDefaultRules({
nested_object_value() {
return function (value) {
if (!value || (value && !value.value || value === [])) {
return 'REQUIRED';
}
return '';
};
},
max_number_advancement() {
return function (value) {
if (value > 100) {
return 'MAX_NUMBER';
}
return '';
};
},
required_if_activity_present() {
return function (value, allValue) {
if (allValue.activitycycletype && !value || allValue.requestpeople === []) {
console.log(first)
return 'REQUIRED_IF_CYCLETYPE';
}
return '';
};
},
});
And this is how its used:
validationForm = () => {
const { formValue, updateErrors } = this.props;
const validData = validator.validate(formValue);
console.log(formValue)
if (!validData) {
const errorsValidator = validator.getErrors();
if (errorsValidator && Object.keys(errorsValidator).length > 0) {
const newErrors = {};
Object.keys(errorsValidator).forEach((error) => {
newErrors[error] = errorsValidator[error];
});
updateErrors(newErrors);
}
blame(t('validation-error'));
return false;
}
updateErrors({});
return true;
}
Opening the form with this validation in the app, seems to call only the last method required_if_activity_present().
What i expect here is that i can create a new method inside registerDefaultRules(), that is a LIVR method, like this:
LIVR.Validator.registerDefaultRules({
re quired_not_empty() {
return function (value) {
if (!value) {
return 'REQUIRED';
}
return '';
};
},
... //other methods
}
but seems not working, the newer method is not being called at all by validator.validate()
Anyone know how to create a new rules where i can check if an element inside the object that has to be validate is an empty array?
Because seems that LIVR doesn't return a validation error in this case, but only on empty string and null values.
Thanks in advance
I have created line chart using Fushionchart in Angular with API data. I have method to add custom color to the chart but it doesn't work. Here I have provide full code to figure the flow. Function pass two parameters and need to set to the label of chart.FYI: *getColorCode() is located in dashboard.service.ts(in below). Here's the full code.
dashboard.component.html
<div *ngIf="multipleConsump" class="chart-card-body p-3">
<app-consumption-summery-chart></app-consumption-summery-chart>
</div>
dashboard.component.ts
getConsumptionData(): void {
this.typeChart.reset();
if (this.trendLogTabIndex == 0 || this.trendLogTabIndex == 1) {
this.tempIntervalid = 2;
} else if (this.trendLogTabIndex == 2) {
this.tempIntervalid = 3;
} else if (this.trendLogTabIndex == 3) {
this.tempIntervalid = 4;
}
this.ltDashboardService
.getCategoryTrends(
this.buildingId,
this.tempIntervalid,
this.intervalTime.from,
this.intervalTime.to,
)
.subscribe(
(res: Object[]) => {
if (res.length) {
const labels = res[0]["data"].map((x) => x.label);
const values = res
.filter((x: any) => this.selectedTypes.indexOf(x.itemId) > -1)
.map((x: any) => {
return {
seriesname: x.name,
data: x.data.map((d) => {
return {
value: d.value,
};
}),
};
});
this.typeChart.setData(labels, values);
}
},
(err) => console.log("Err")
);
}
dashboard.service.ts
getCategoryTrends(building: number, interval: intervalGroup, from:Date, to:Date): Observable<any> {
return this.dataService.get(this.config.endPoints['building-type-consumption-group'], {
startDate: from,
endDate: to,
groupId: GroupBy.MeterType,
serviceTypeId: ServiceType.ELECTRICAL,
buildingId: building,
interval: interval,
siteId: this.config.siteConfigurations.siteId,
dataMode: DataMode.CategorySum,
})
}
Get color code function (meter type is ChartGroupType.METER_TYPES for above function )
Get color code
private getColorCode(chartGroupType: ChartGroupType, itemId: number) {
let item;
let color;
switch(chartGroupType) {
case ChartGroupType.METER:
return '';
case ChartGroupType.BUILDING_CATEGORY:
item = find(this.initialService.navigationStore.buildingCategories, {buildingCategoryID: itemId});
return this.getDefaultColorCode(item);
case ChartGroupType.METER_TYPES:
item = find(this.initialService.navigationStore.meterTypes, {meterTypeID: itemId});
return this.getDefaultColorCode(item);
case ChartGroupType.HT_LOOPS:
return '';
}
}
private getDefaultColorCode(item) {
let color;
if(item === undefined || item.attributes.length === 0) {
return this.config.config.defaultColorCode;
} else {
color = find(
item.attributes,
{
textId: this.config.config.attributes.COLOR_CODE
});
console.log(item + 'chart colors are: ')
return color===null || color === undefined?this.config.config.defaultColorCode:color.value;
}
}
My problem is that I want to insert values that are not repeated when doing a push
This is my code :
addAddress: function() {
this.insertAddresses.Adress = this.address_address
this.insertAddresses.State = this.selectedStateAddress
this.insertAddresses.City = this.selectedCityAddress
if(this.insertAddresses.Adress !== "" && this.insertAddresses.State !== null && this.insertAddresses.City !== null) {
let copia = Object.assign({}, this.insertAddresses);
this.addresses.push(copia)
}
else
{
this.$message.error('Not enough data to add');
return
}
},
When adding a new element to my object, it returns the following.
When I press the add button again, it adds the same values again, I want to perform a validation so that the data is not the same. How could I perform this validation in the correct way?
Verify that the item doesn't already exist in the array before inserting.
You can search the array using Array.prototype.find:
export default {
methods: {
addAddress() {
const newItem = {
Address: this.address_address,
State: this.selectedStateAddress,
City: this.selectedCityAddress
}
this.insertItem(newItem)
},
insertItem(item) {
const existingItem = this.addresses.find(a => {
return
a.State === item.State
&& a.City === item.City
&& a.Address === item.Address
})
if (!existingItem) {
this.addresses.push(item)
}
}
}
}
On the other hand, if your app requires better performance (e.g., there are many addresses), you could save a separate dictonary to track whether the address already exists:
export default {
data() {
return {
seenAddresses: {}
}
},
methods: {
insertItem(item) {
const { Address, State, City } = item
const key = JSON.stringify({ Address, State, City })
const seen = this.seenAddresses[key]
if (!seen) {
this.seenAddresses[key] = item
this.addresses.push(item)
}
}
}
}
demo
check it:
let filter= this.addresses.find(x=> this.insertAddresses.State==x.State)
if (filter==null) {
this.$message.error('your message');
}
OR FILTER ALL
let filter= this.addresses.find(x=> this.insertAddresses.Adress==x.Adress && this.insertAddresses.State==x.State && this.insertAddresses.City==x.City)
if (filter==null) {
this.$message.error('your message');
}
``
On initial render, the input components acts as normal. However after it is unfocused and refocused again, it will only accept one character. This also happens if I reset the form.
I created a FormValidationStateManager class to reuse Joi validation logic that is needed.
Form.jsx
class Form extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.validation = new FormValidationStateManager();
}
handleSubmit(e) {
e.preventDefault();
var result = this.validation.validateAll();
this.setState({
// need to do this, trigger render after validations
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
}, function() {
if (result.valid) {
this.props.onSubmit(result.data);
}
}.bind(this));
}
resetForm(e) {
e.preventDefault();
this.validation.clearForm();
this.setState({
// need to do this, trigger render after validations,
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
});
}
render() {
// TODO implement logic for rendering name from API call
let name = "John Doe";
return (
<form class='info-form' onSubmit={this.handleSubmit} autocomplete='off'>
<div class='info-block-section-title'>
<h2>Welcome {name}</h2>
<h4>Some nicely worded few sentences that let's the user know they just need to fill out a few bits of information to start the process of getting their student loans paid down!</h4>
</div>
<br />
<div class='info-block-section-body'>
<InfoFieldset validation={this.validation} />
</div>
<div class='info-form-button'>
<FormButton label="Continue" />
<FormButton type="button" onClick={this.resetForm.bind(this)} label="Reset" />
</div>
</form>
)
}
}
export default Form;
Fieldset
class Fieldset extends Component {
constructor(props) {
super(props);
this.state = {
secondaryEmailVisible: false
};
this.handleToggle = this.handleToggle.bind(this);
this.validation = this.props.validation || new FormValidationStateManager();
this.validation.addRules(this.validatorRules.bind(this));
}
validatorRules() {
var _rules = {
// TODO validate format of date, address and zip
date_of_birth: Joi.date().label("Date of Birth").required(),
address_street: Joi.string().label("Street Address").required(),
address_zip: Joi.string().label("Zip Code").required(),
password_confirmation: Joi.any()
.valid(Joi.ref('password'))
.required()
.label('Password Confirmation')
.messages({
"any.only": "{{#label}} must match password"
}).strip(),
password: Joi.string()
.pattern(/\d/, 'digit')
.pattern(/^\S*$/, 'spaces')
.pattern(/^(?!.*?(.)\1{2})/, 'duplicates')
.pattern(/[a-zA-z]/, 'alpha')
.required()
.min(CONSTANTS.PASSWORD_MIN)
.label('Password')
.messages({
"string.min": PASSWORD_HINT,
"string.pattern.name": PASSWORD_HINT
})
}
if (this.state.secondaryEmailVisible) {
_rules['secondary_email'] = Joi.string()
.label("Secondary Email")
.email({ tlds: false })
.required();
}
return _rules;
}
handleToggle() {
this.setState(state => ({
secondaryEmailVisible: !state.secondaryEmailVisible
}));
}
render() {
return(
<div class='info-fieldset'>
{/* for skipping chrome browser autocomplete feature
<input type='text' style={{ display: 'none '}} />
<input type='password' style={{ display: 'none '}} />
*/ }
<strong>Primary email for login: work#example.com</strong>
<FormElementToggle
field="secondary"
label={"Add another email for notifications?"}
onChange={this.handleToggle} />
<div class='info-form-element'>
<DisplayToggle remove={true} show={this.state.secondaryEmailVisible}>
<FormElementInput
field='secondary_email'
label="Secondary Email"
validation={this.validation}
placeholder='john#example.com' />
</DisplayToggle>
<FormElementInput
field='password'
type='password'
label={(
<div>
Password
<Tooltip
placement='top'
trigger={['hover']}
overlay={PASSWORD_HINT}>
<span><i className='fa fa-info-circle'></i></span>
</Tooltip>
</div>
)}
placeholder='********'
validation={this.validation} />
<FormElementInput
key={1}
field='password_confirmation'
type='password'
label="Password Confirmation"
placeholder='********'
validation={this.validation} />
<FormElementInput
field='date_of_birth'
label="Date of Birth"
validation={this.validation}
placeholder='MM/DD/YYYY' />
<FormElementInput
field='address_street'
label="Street Address"
validation={this.validation}
placeholder='123 Example St' />
<FormElementInput
field="address_zip"
label="Zip Code"
validation={this.validation}
placeholder='01234' />
</div>
</div>
)
}
}
export default Fieldset;
FormElementInput.jsx
class FormElementInput extends Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.cachedErrors = {};
this.cachedValue = null;
this.state = { focused: false };
this.validation = this.props.validation || new FormValidationStateManager();
}
componentDidUpdate(prevProps, prevState) {
}
shouldComponentUpdate(nextProps, nextState) {
var filter = function(val, key, obj) {
if (_.isFunction(val)) {
return true;
}
// Certain props like "label" can take a React element, but those
// are created dynamically on the fly and they have an random internal UUID,
// which trips the deep-equality comparision with a false-positive.
// This is wasted cycles for rendering.
if (React.isValidElement(val)) {
return true;
}
// validation is a complex obj that we shouldn't be caring about
if (_.contains(['validation'], key)) {
return true;
}
};
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (!_.isEqual(this.cachedErrors, this.getErrors())) {
return true;
}
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (this.cachedValue !== this.getDisplayValue()) {
return true;
}
return !_.isEqual(_.omit(nextProps, filter), _.omit(this.props, filter)) || !_.isEqual(this.state, nextState);
}
setValue(value) {
console.error('deprecated - can not set value directly on FormElementInput anymore');
throw 'deprecated - can not set value directly on FormElementInput anymore';
}
isDollarType() {
return this.props.type === 'dollar';
}
isRateType() {
return this.props.type === 'rate';
}
getType() {
if (this.isDollarType() || this.isRateType()) {
return 'text';
} else {
return this.props.type;
}
}
getPrefix() {
if (this.isDollarType()) {
return _.compact([this.props.prefix, '$']).join(' ');
} else {
return this.props.prefix;
}
}
getPostfix() {
if (this.isRateType()) {
return _.compact([this.props.postfix, '%']).join(' ');
} else {
return this.props.postfix;
}
}
getDisplayValue() {
var value = this.props.value || this.validation.getFormValue(this.props.field);
// while DOM input is focused, just return the same value being typed
if (this.state.focused) {
return value;
}
if (this.isDollarType()) {
if (_.isUndefined(value)) {
return value;
}
// keep in sync with the validation check in getOnChange()
// even if this is different - it wont break - cause accounting.js handles gracefully
else if (_.isNumber(value) || !value.match(/[a-z]/i)) {
return accounting.formatMoney(value, '', 2);
} else {
return value;
}
} else if (this.isRateType()){
if (_.isUndefined(value)) {
return '';
}
return accounting.formatNumber(value, 3)
} else {
return value;
}
}
getErrors() {
return this.props.errors || this.validation.getValidationMessages(this.props.field);
}
onBlur(event) {
this.validation.validate(this.props.field);
this.setState({ focused: false });
}
onFocus(event) {
this.setState({ focused: true });
}
onChange(event) {
if (this.isDollarType()) {
// only accept if the input at least resembles a currency
// accounting.parse already strips alot of characters and does this silently
// we want to be a little less strict than accounting.parse since we need to allow more
// different types of inputs +-,. and also let accounting.parse do its job
// very basic check here for a-z characters
if (!event.target.value.match(/[a-z]/i)) {
var parsedValue = accounting.parse(event.target.value);
event._parsedValue = parsedValue;
}
}
let _value = event._parsedValue || event.target.value;
console.log(this.input)
this.validation.setFormValue(this.props.field, _value);
}
_eventHandlers(field) {
return {
onChange: this.onChange.bind(this),
onBlur: this.onBlur.bind(this),
onFocus: this.onFocus.bind(this)
};
}
_mergeEventHandlers(p1, p2) {
var events = [
'onChange',
'onBlur',
'onFocus'
];
var r1 = {};
events.map(function(e) {
r1[e] = FuncUtils.mergeFunctions(p1[e], p2[e]);
}.bind(this));
return r1;
}
render() {
let _errors = this.cachedErrors = this.getErrors();
let _value = this.cachedValue = this.getDisplayValue();
let attrs = {
id: this.props.htmlId || ('field_' + this.props.field),
className: classnames({
'error': _errors && _errors.length
}),
type: this.getType(),
placeholder: this.props.placeholder,
autoComplete: "false",
disabled: this.props.disabled,
readOnly: this.props.disabled,
value: _value,
ref: this.input,
};
attrs = _.extend(attrs, this._mergeEventHandlers(this.props, this._eventHandlers()));
const prefix = this.getPrefix();
const postfix = this.getPostfix();
return (
<FormElementWrapper
type="input"
field={this.props.field}
errors={_errors}
label={this.props.label}>
<div className="form-element-input">
<DisplayToggle remove={true} hide={!prefix}>
<span className="prefix">{prefix}</span>
</DisplayToggle>
<div className={classnames({
"has-prefix": !!prefix,
"has-postfix": !!postfix
})}>
<input {...attrs} />
</div>
<DisplayToggle remove={true} hide={!postfix}>
<span className="postfix">{postfix}</span>
</DisplayToggle>
</div>
<div className="form-error">
{_errors.map((msg, idx) => {
return (<FormError key={idx}>{msg}</FormError>)
})}
</div>
</FormElementWrapper>
)
}
};
FormElementInput.PropTypes = {
field: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
value: PropTypes.any,
label: PropTypes.any,
errors: PropTypes.array,
type: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onFocus: PropTypes.func,
onKeyUp: PropTypes.func,
tooltip: PropTypes.string,
prefix: PropTypes.string,
postfix: PropTypes.string,
disabled: PropTypes.bool,
disableTrimming: PropTypes.bool
}
FormElementInput.defaultProps = {
type: 'text',
placeholder: 'Enter Value',
tooltip: undefined,
prefix: undefined,
postfix: undefined,
disabled: false,
disableTrimming: false
}
export default FormElementInput;
FormValidationStateManager.js
class FormValidationStateManager {
// default state, validation ref obj
constructor(defaultState) {
this.rules = [];
this.state = defaultState || {};
this.errors = {};
this.dirty = false;
}
getFormValue(key) {
return this.state[key];
}
setFormValue(key, value) {
this.dirty = true;
this.state[key] = value;
}
setFormState(newState) {
this.state = _.extend({}, newState);
}
addRules(rules) {
this.rules.push(rules);
}
isFormDirty(){
return this.dirty;
}
/**
* Clear all previous validations
*
* #return {void}
*/
clearValidations() {
this.dirty = false;
this.errors = {};
}
clearForm() {
this.state = _.mapObject(this.state, function(v, k) {
return '';
});
this.clearValidations();
}
/**
* Check current validity for a specified key or entire form.
*
* #param {?String} key to check validity (entire form if undefined).
* #return {Boolean}.
*/
isValid(key) {
return _.isEmpty(this.getValidationMessages(key));
}
// call within submit
validateAll() {
return this.validate();
}
/**
* Method to validate single form key or entire form against the component data.
*
* #param {String|Function} key to validate, or error-first containing the validation errors if any.
* #param {?Function} error-first callback containing the validation errors if any.
*/
validate(key, callback) {
if (_.isFunction(key)) {
callback = key;
key = undefined;
}
var schema = this._buildRules();
var data = this.state;
var result = this._joiValidate(schema, data, key);
if (key) {
this.errors[key] = result.errors[key];
} else {
this.errors = result.errors;
}
return {
valid: this.isValid(key),
errors: this.errors,
data: result.data
};
}
_buildRules() {
var allRules = this.rules.map(function(r) {
// Rules can be functions and be dynamically evalulated on-the-fly,
// to allow for more complex validation rules.
if (_.isFunction(r)) {
return r();
} else {
return r;
}
});
// collapse all the rules into one single object that will
// be evaluated against `this.state`
return Joi.object(_.extend({}, ...allRules));
}
/**
* Get current validation messages for a specified key or entire form.
*
* #param {?String} key to get messages, or entire form if key is undefined.
* #return {Array}
*/
getValidationMessages(key) {
let errors = this.errors || {};
if (_.isEmpty(errors)) {
return [];
} else {
if (key === undefined) {
return _.flatten(_.keys(errors).map(error => {
return errors[error] || []
}));
} else {
return errors[key] ? errors[key].map(he.decode) : [];
}
}
}
_joiValidate(joiSchema, data, key) {
joiSchema = joiSchema || Joi.object();
data = data || {};
const joiOptions = {
// when true, stops validation on the first error, otherwise returns all the errors found. Defaults to true.
abortEarly: false,
// when true, allows object to contain unknown keys which are ignored. Defaults to false.
allowUnknown: true,
// remove unknown elements from objects and arrays. Defaults to false
stripUnknown: true,
errors: {
escapeHtml: true,
// overrides the way values are wrapped (e.g. [] around arrays, "" around labels).
// Each key can be set to a string with one (same character before and after the value)
// or two characters (first character before and second character after), or false to disable wrapping:
label: false,
wrap: {
label: false,
array: false
}
},
messages: {
"string.empty": "{{#label}} is required",
"any.empty": "{{#label}} is required",
"any.required": "{{#label}} is required"
}
};
const result = joiSchema.validate(data, joiOptions);
let errors = this._formatErrors(result);
if (key) {
errors = _.pick(errors, key);
}
return {
errors: errors,
data: result.value
};
}
_formatErrors(joiResult) {
if (joiResult.error) {
return _.reduce(joiResult.error.details, (memo, detail) => {
// According to docs:
// - "detail.path": ordered array where each element is the accessor to the value where the error happened.
// - "detail.context.key": key of the value that erred, equivalent to the last element of details.path.
// Which is why we get the last element in "path"
var key = _.last(detail.path);
if (!Array.isArray(memo[key])) {
memo[key] = [];
}
if (!_.contains(memo[key], detail.message)) {
memo[key].push(detail.message);
}
return memo;
}, {});
} else {
return {};
}
}
}
export default FormValidationStateManager;
I think I got it. I changed value: _value to defaultValue: _value for the attributes in FormElementInput.jsx
saw it here
Anyone know why this fixed it under the hood?
I'm trying to build a little chat bot, and I seem to be nearing completion if it were not for this bug. The issue seems to be that my switch statement isn't handling the setState properly.
Uquestion extends React.Component {
constructor(props) {
super(props);
this.state = {
text: this.props.text,
response: "Here is the answer"
}
this.handleChange = this.handleChange.bind(this)
this.key = this.key.bind(this)
this.fetchResponse = this.fetchResponse.bind(this)
}
handleChange(event) {
this.setState({
question: event.target.value
})
}
fetchResponse() {
switch(this.state.searchKey) {
case "BBQ":
this.setState({
response:"Texas BBQ"
})
break;
case "book":
this.setState({
response:"It's close, but probably The Night in Question by Tobias Wolff."
})
break;
case "restaurant":
this.setState({
response:"Andaman, a little Thai spot in Denton, Texas."
})
break;
case "work":
this.setState({
response:"Lots of this and lots of that."
})
break;
case "upbringing":
this.setState({
response:"Texas thunderstorms over endless plains."
})
break;
case "future":
this.setState({
response:"I hope to work on meaningful applications and write meaningful narratives"
})
break;
case "fun":
this.setState({
response:"When the moon is full, I write by candle light."
})
break;
default:
this.setState({
response:"Um, what?"
})
}
}
//this function sets a key that I will later use to fetch a response to the user's question.
key() {
var question=this.state.question;
var questionUpper=question.toUpperCase();
// the is not -1 determines if the phrase appears anywhere in the question.
if(questionUpper.search("FAVORITE FOOD")!==-1) {
this.setState({
searchKey:"BBQ"
}, this.fetchResponse())
}
else if(questionUpper.search("FAVORITE BOOK")!==-1) {
this.setState({
searchKey:"Book"
}, this.fetchResponse())
}
else if(questionUpper.search("FAVORITE RESTAURANT")!==-1) {
this.setState({
searchKey:"Restaurant"
},this.fetchResponse())
}
else if(questionUpper.search("WORK EXPERIENCE")!==-1) {
this.setState({
searchKey:"work"
},this.fetchResponse())
}
else if(questionUpper.search("GROWING UP")!==-1) {
this.setState({
searchKey:"upbringing"
},this.fetchResponse())
}
else if(questionUpper.search("FAVORITE AUTHOR")!==-1) {
this.setState({
searchKey:"author"
},this.fetchResponse())
}
else if(questionUpper.search("FUTURE")!==-1) {
this.setState({
searchKey:"future"
},this.fetchResponse())
}
else if (questionUpper.search("FOR FUN")!==-1) {
this.setState({
searchKey:"fun"
},this.fetchResponse())
}
else {
this.setState({
searchKey:"default"
}, this.fetchResponse())
}
}
render() {
return (
<div>
<p> {this.state.response} </p>
<textarea onChange = {this.handleChange} className="q"> {this.props.text} </textarea>
<button className="a" onClick={this.key}>Ask!</button>
</div>
);
}
}
ReactDOM.render(<Uquestion text="Type Question Here."/>, document.getElementById("content"))
You are passing wrong callback in setState function. And in fetchResponse you've wrote some wrong cases. I've corrected your mistakes, you can see on working example in Codepen
wrong:
this.setState({
searchKey: "book"
}, this.fetchResponse())
correct:
this.setState({
searchKey: "book"
}, this.fetchResponse)
you can read react source code
ReactComponent.prototype.setState = function (partialState, callback){
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
enqueueCallback: function (publicInstance, callback, callerName) {
ReactUpdateQueue.validateCallback(callback, callerName);
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
if (!internalInstance) {
return null;
}
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
enqueueUpdate(internalInstance);
}
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
so, I think you the callback is like this:
this.setState({
searchKey:"BBQ"
}, this.fetchResponse)