I am new to React and javascript and trying to use moroshko's component react-autosuggest, but failing to get event handlers to bind correctly. I'm writing in coffeescript, but will paste compiled javascript too.
define [
'jquery',
'react',
'reactdom',
'autosuggest'
], (jQuery, React, ReactDOM, Autosuggest) ->
escapeRegexCharacters = (str) ->
str.replace /[.*+?^${}()|[\]\\]/g, '\\$&'
getSuggestions = (praxes, value) ->
if value == ""
return []
val = escapeRegexCharacters(value.trim())
regex = new RegExp('^' + val, 'i')
praxes.filter((prax) => regex.test(prax.species))
getPraxSpecies = (prax) ->
prax.species
renderSpecies = (prax) ->
React.createElement("span", null, getPraxSpecies(prax))
Species = React.createClass
getInitialState: ->
value: ''
suggestions: getSuggestions(#props.praxes, '')
onChange: (event, {newValue}) ->
#setState({value: newValue})
onSuggestionsUpdateRequested: ({value}) ->
#setState {suggestions: getSuggestions(#props.praxes, value)}
render: ->
inputProps =
placeholder: "Choose a species"
value: ''
onChange: #onChange
autosuggest = React.createFactory Autosuggest
React.DOM.div {key: 'autosugg', className: 'praxis'},
autosuggest {
key: 'autoSp',
suggestions: #state.suggestions,
onSuggestionsUpdateRequested: #onSuggestionsUpdateRequested,
getSuggestionValue: getPraxSpecies,
renderSuggestion: renderSpecies,
inputProps: inputProps
}
Species
or coffeescript above compiled to javascript below:
(function() {
define(['jquery', 'react', 'reactdom', 'autosuggest'], function(jQuery, React, ReactDOM, Autosuggest) {
var Species, escapeRegexCharacters, getPraxSpecies, getSuggestions, renderSpecies;
escapeRegexCharacters = function(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
getSuggestions = function(praxes, value) {
var regex, val;
if (value === "") {
return [];
}
val = escapeRegexCharacters(value.trim());
regex = new RegExp('^' + val, 'i');
return praxes.filter((function(_this) {
return function(prax) {
return regex.test(prax.species);
};
})(this));
};
getPraxSpecies = function(prax) {
return prax.species;
};
renderSpecies = function(prax) {
return React.createElement("span", null, getPraxSpecies(prax));
};
return Species = React.createClass({
getInitialState: function() {
return {
value: '',
suggestions: getSuggestions(this.props.praxes, '')
};
},
onChange: function(event, _arg) {
var newValue;
newValue = _arg.newValue;
return this.setState({
value: newValue
});
},
onSuggestionsUpdateRequested: function(_arg) {
var value;
value = _arg.value;
return this.setState({
suggestions: getSuggestions(this.props.praxes, value)
});
},
render: function() {
var autosuggest, inputProps;
inputProps = {
placeholder: "Choose a species",
value: '',
onChange: this.onChange
};
autosuggest = React.createFactory(Autosuggest);
console.log('this: ' + this);
return React.DOM.div({
key: 'autosugg',
className: 'praxis'
}, autosuggest({
key: 'autoSp',
suggestions: this.state.suggestions,
onSuggestionsUpdateRequested: this.onSuggestionsUpdateRequested,
getSuggestionValue: getPraxSpecies,
renderSuggestion: renderSpecies,
inputProps: inputProps
}));
}
}, Species);
});
}).call(this);
The autosuggest component displays properly initially, accepts input, and calls onChange and onSuggestionsUpdateRequested functions in that order. State is updated, but possibly not attached to the correct component. The autosuggest then repaints exactly as initially (i.e. value='').
Substituting fat-arrow => for -> in defining onChange results in an error: Uncaught TypeError: _this.setState is not a function
I have also tried using the coding style of moroshko's example without success. Clearly, I am missing something ...
I think the problem is in your inputProps. You need to use this.state.value:
inputProps =
placeholder: "Choose a species"
value: ''
onChange: #onChange
to:
inputProps =
placeholder: "Choose a species"
value: #state.value
onChange: #onChange
Related
I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});
I am trying to get my "submit" button to be enabled or disabled based on the validity of the email that is passed. However, the RegExp expression I am using is not reading the string properly. I've tried testing my isWorking() function with just email.length > 0 to make sure that whatever is being passed to isWorking() is indeed a string, and we are good there but RegExp is always returning false regardless of the input it receives. I'm using React without JSX in this app. Any help at all would be deeply appreciated. Thank you so much!
const validEmailRegex = RegExp(/^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
class Signup extends React.Component {
constructor() {
super();
this.state = {
email: ""
};
this.handleSubmit = this.handleSubmit.bind(this);
}
isWorking () {
const email = event.target;
//if (email.length > 0) {
// return false;
// }
// return true;
if (validEmailRegex.test(email) === true) {
return false;
}
return true;
}
handleSubmit(event) {
event.preventDefault();
if (!event.target.checkValidity()) {
this.setState({
invalid: true,
displayErrors: true,
});
return;
}
const form = event.target;
const data = new FormData(form);
for (let name of data.keys()) {
const input = form.elements[name];
const parserName = input.dataset.parse;
console.log('parser name is', parserName);
if (parserName) {
const parsedValue = inputParsers[parserName](data.get(name));
data.set(name, parsedValue);
}
}
this.setState({
res: stringifyFormData(data),
invalid: false,
displayErrors: false,
});
}
render() {
const { res, invalid, displayErrors } = this.state;
//pass data to the button for disabling or not
const isEnabled = this.isWorking();
return (
React.createElement("div", { className: "container" },
React.createElement("div", { className: "row" },
React.createElement("form", { onSubmit: this.handleSubmit, noValidate: true, className: displayErrors ? 'displayErrors' : '' },
React.createElement("input", { className: "form-control", name: "formEmail", id: "formEmail", type: "email", placeholder: "email"}),
),
React.createElement("span", { className: "span"},
React.createElement("fieldset", { className: "form-group" },
React.createElement(Link, { className: "nav-link", activeClassName: "nav-link-active", to: "/contact" },
React.createElement("button", { className: "button1", disabled: isEnabled, type: "button"}, "next")
),
)
),
),
)
);
}
}
Your isWorking() does not receive event from anywhere. Also, event.target will be an HTML element and definitely not an input value. For a form, you do event.target.elements["<name_of_input>"] (here, name of input if formEmail) to get input value.
I'm trying to use custom ReactJS radio buttons but when I'm unable to get them to render as to which some errors are showing on my console, inside my component I've got the following:
D.div({ className: 'stra-on-loss' },
D.span({ className: 'bet-title' }, 'On loss:'),
ReactRadio({name: 'onLoss', onChange: this.updateOnLoss, defaultValue: this.state.onLossSelectedOpt },
D.input({
type: 'radio',
className: 'stra-on-loss-return-to-base-radio',
value: 'return_to_base',
disabled: this.state.active
}, D.span(null, 'Return to base bet')),
D.input({
type: 'radio',
className: 'stra-on-loss-increase-bet-by',
value: 'increase_bet_by',
disabled: this.state.active
}, D.span(null, 'Increase bet by: '),
D.input({
type: 'text',
ref: 'onLossQty',
onChange: this.updateOnLossQty,
value: this.state.onLossIncreaseQty,
disabled: this.state.active || this.state.onLossSelectedOpt !== 'increase_bet_by' }
), D.span(null, 'x'))
)
)
But then I've also got a class that's imported ReactRadio which handles the buttons:
return CreateReactClass({
displayName: 'reactRadio',
propTypes: {
name: PropTypes.string.isRequired,
value: PropTypes.string,
defaultValue: PropTypes.string,
onChange: PropTypes.func.isRequired
},
componentDidMount: function() {
this.update();
},
componentDidUpdate: function() {
},
update: function() {
if(this.props.defaultValue && !this.props.value)
this.setSelectedRadio(this.props.defaultValue);
},
change: function() {
if(!this.props.value)
this.props.onChange(this.getSelectedRadio());
},
getSelectedRadio: function() {
const radios = this.getRadios();
for(let i=0, length = radios.length; i < length; i++)
if(radios[i].checked)
return radios[i].value;
return null;
},
setSelectedRadio: function(value) {
const radios = this.getRadios();
for(let i = 0, length = radios.length; i < length; i++)
if(radios[i].value === value)
radios[i].checked = true;
},
getRadios: function() {
return this.getDOMNode().querySelectorAll('input[type="radio"]');
},
render: function() {
const self = this;
return D.div({ onChange: this.change },
React.Children.map(this.props.children, function(child) {
let newProps = {name: self.props.name};
if(child.props.children) {
React.Children.map(child.props.children, function (child) {
if (child.props.onChange) {
var childrenChange = child.props.onChange;
}
_.extend(child.props, {
onChange: function (e) {
e.stopPropagation();
if (childrenChange)
childrenChange();
}
});
});
}
if(self.props.value) {
if (child.props.value !== self.props.value)
newProps.disabled = true;
else {
newProps.checked = true;
newProps.readOnly = true;
}
}
_.extend(child.props, newProps);
return child;
})
)
}
});
When I visit my browser I'm getting the following in my console & the component isn't being loaded: Uncaught Invariant Violation: input is a void element tag and must neither have children nor use dangerouslySetInnerHTML. Check the render method of AutoBetWidget.
i'm trying to traverse an Array and perform some operation. But cann't access the values as i want to.
inputObj.map(el => {
const msg = this.state.validation[el.id].message;
const msg2 = this.state.validation['name'].message;
})
Here, el.id can be name, dob or address. When i use this.state.validation[el.id].message;, it shows TypeError: Cannot read property 'message' of undefined. But if i hardcode it like this.state.validation['name'].message;, it works fine. when comparing both el.id and 'name', they have same datatype and same value. So, why having problem when using el.id instead of hardcoding it.
NB: i'm using reactjs.
Edit:
this.state:
this.state = {
super(props);
this.validator = new FormValidator([
{
field: 'name',
method: 'isEmpty',
validWhen: false,
message: 'Name is required'
},
...
]);
orderForm: {
name: {
elementType: 'input',
elementConfig: ''
},
...
}
validation: this.validator.setValid() // it will be the updated upon submitting form on submitHandler by calling validate() from FormValidator
}
inputObj:
const inputObj= [];
for(let key in this.state.orderForm){
inputObj.push({
id : key,
config: this.state.orderForm[key]
});
}
FormValidator
import validator from 'validator';
class FormValidator {
constructor(rules){
this.rules = rules;
}
setValid(){
const validation = {};
this.rules.map(rule => (
validation[rule.field] = {isValid: true, message: ''}
));
return {isValid: true, ...validation};
}
validate(form){
let validation = this.setValid();
this.rules.forEach( rule => {
if (validation[rule.field].isValid){
const field = form[rule.field].toString();
const args = rule.args || [];
const validationMethod = typeof rule.method === 'string' ?
validator[rule.method] : rule.method;
if (validationMethod(field, ...args, form) !== rule.validWhen){
validation[rule.field] = {isValid: false, message: rule.message};
validation.isValid = false;
}
}
});
return validation;
}
}
export default FormValidator;
You can check if this.state.validation[el.id] is defined before getting message key.
Like that you can't get fatal error.
inputObj.map(el => {
this.state.validation[el.id] && (
const msg = this.state.validation[el.id].message
);
})
I am pretty new to React. I am trying to create a simple form and pass values into an 'onclick' handler. You can see the code below:
const reactContainer = document.getElementById('react');
let SForm = React.createClass({
getApps: function(){
getAppsExternal(document.getElementsByClassName("token")[0].value,document.getElementsByClassName("publisher_id")[0].value)
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: ","",
React.createElement("input",{type: "password",className:"token",maxLength:"30"})),
React.createElement("div",{},"Publisher ID: ",
React.createElement("input",{type: "text",className:"publisher_id",maxLength:"7"})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps},"Get Apps"))
)
}
})
let elementTester =React.createElement(SForm)
ReactDOM.render(React.createElement(SForm),reactContainer)
My question is, how do I pass the parameters into getAppsExternal the 'react' way without using document.getElementsByClassName ?
See: https://reactjs.org/docs/forwarding-refs.html
Assuming you use the lattest React, you can use React.createRef()
const reactContainer = document.getElementById('react');
let SForm = React.createClass({
componentWillMount: function() {
this.tokenRef = React.createRef()
this.publisherRef = React.createRef()
},
getApps: function(){
getAppsExternal(this.tokenRef.current.value, this.publisherRef.current.value)
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: ","",
React.createElement("input",{type: "password",className:"token",maxLength:"30", ref: this.tokenRef})),
React.createElement("div",{},"Publisher ID: ",
React.createElement("input",{type: "text",className:"publisher_id",maxLength:"7", ref: this.publisherRef})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps},"Get Apps"))
)
}
})
let elementTester =React.createElement(SForm)
ReactDOM.render(React.createElement(SForm),reactContainer)
If it's not available for you, there is the callback approach
const reactContainer = document.getElementById('react');
let SForm = React.createClass({
setTokenRef: function(ref) {
this.tokenRef = ref
},
setPublisherRef: function(ref) {
this.publisherRef = ref
},
getApps: function(){
getAppsExternal(this.tokenRef.value, this.publisherRef.value)
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: ","",
React.createElement("input",{type: "password",className:"token",maxLength:"30", ref: this.setTokenRef.bind(this)})),
React.createElement("div",{},"Publisher ID: ",
React.createElement("input",{type: "text",className:"publisher_id",maxLength:"7", ref: this.setPublisherRef.bind(this)})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps.bind(this)},"Get Apps"))
)
}
})
let elementTester =React.createElement(SForm)
ReactDOM.render(React.createElement(SForm),reactContainer)
As you're not using arrow functions, don't forget to bind your callbacks like above
The simplest way, you can create a getInitialState then make a onChange function that will set the values to the state then you will be able to use them like this {this.state.password}
getInitialState: function() {
return {password: '', publisher: ''};
},
onChange: function(e){
this.setState({ [e.target.name]: e.target.value });
},
render: function(){
return (
React.createElement("div",{className: "container"},"",
React.createElement("div",{},"Authentication Token: {this.state.password}","",
React.createElement("input",{type: "password",className:"token",maxLength:"30",name: 'password',value: this.state.password,onChange: this.onChange.bind(this)})),
React.createElement("div",{},"Publisher ID: {this.state.publisher} ",
React.createElement("input",{name: 'publisher',type: "text",className:"publisher_id",maxLength:"7",value: this.state.publisher, onChange: this.onChange.bind(this)})),
React.createElement("button",{className:"get_apps_button",onClick:this.getApps},"Get Apps"))
)
}