React checkbox doesn't work as expected - javascript

I had and idea to ad some object style to React project. That is why I have written that class
export class Tagi {
constructor() {
this.members = [{
tag: "No matter",
color: "Aquamarine",
isMarked: true
}];
this.handleTagMarking = this.handleTagMarking.bind(this);
}
add(x) {
this.members.push(x);
}
static fromTable(table) {
let tags = new Tagi();
let shortened = unique(table);
for (let value of shortened) {
let record = {
tag: value,
color: colors[shortened.indexOf(value)],
isMarked: false
}
tags.add(record)
}
return tags;
}
get getAllTags() {
return this.members;
}
handleTagMarking(event){
let x = event.target.value;
const index = this.members.findIndex((element)=>element.tag==x);
const currentMarkStatus = this.members[index].isMarked;
if (currentMarkStatus) this.UnMarkTag(index); else this.MarkTag(index)
console.log(this.members)
}
The last part thereof is event handler, more about it later.
That class is implemented in Parent component like this
let Taggs =Tagi.fromTable(d);
console.log (Taggs.getMarkedTags);
Please note that this is implemented in its render function. It has nothing to do with its state.
Later in this Parent component I send it as props
render() {
const label = this.props.labels;
const SingleCheckbox =()=>{return(label.map((element)=>
<label key={element.tag} ><input type="checkbox" value={element.tag} checked={element.isMarked} onChange={this.props.fn} />{element.tag}</label>))}
return (
<div className="checkbox">
<SingleCheckbox />
</div>);
The problem is that checkbox doesn't react to checking items properly. What I mean by this is that data is changed ( I send to console the data within evenhandler so that is clear) but it does not check the fields what is expected behaviour. I suspect it happens because in Parent, Taggs are not state-linked (but only suspect) Is that correct or could there be other reason? If so, how could I easily reshape the code keeping its object oriented style?

I have just chcecked my initial idea of being-not-a-state.
That is not correct I guess. Now in constructor Taggs are initiated like this:
Taggs:Tagi.fromTable(props.disciplines.map((d) => d.tags)),
and later in render{return()} are called like this
<CheckBox labels ={this.state.Taggs.getAllTags} fn={this.state.Taggs.handleTagMarking}/>
Does not change anything at all

Related

TypeError while updating Custom element created with LitElement

I created an element with URL and value properties. The value property takes in a JSON and populates the mwc-textfield. the element makes an AJAX GET call when the URL link is inputted and the JSON obtained is passed into the element as the value, thereby populating the mwc-textfields. This works well when the appropriate URL is passed. But I get a type error if a wrong URL is passed or an empty string is passed into the URL property(<my-el url=""></my-el>). A figure of the error is attached below
How can I reformat the element to show the default element when an empty string is passed to the URL property and update only when the ajax response obtained is the same with the value format
<my-el value= '[
{
"coding": [
{
"system": "http://snomed.info/sct",
"code": "166643006",
"display": "Liver enzymes abnormal"
}
]
}
]'></my-el>
<my-el url="http://hapi.fhir.org/baseR4/Medication/30072"></my-el>
import {LitElement, html} from 'lit-element'
import '#material/mwc-textfield/mwc-textfield.js'
import '#material/mwc-formfield/mwc-formfield.js'
import '#polymer/iron-ajax/iron-ajax.js'
class myEl extends LitElement {
static get properties(){
return{
url: {type: String},
value: {type: Array}
};
};
constructor(){
super();
this.value = [{coding:[{}]}];
};
render(){
if(typeof(this.value)=="string"){
this.value = JSON.parse(this.value);
}
return html `
<div id ="reasonDiv">
${this.value[0].coding.map((i,index) => html `
<mwc-textfield id="codeFieldID" outlined label ="code" .value = "${i.code || ""}"
#input = "${e=>this.value[0].coding[index].code = e.target.value}"></mwc-textfield>
<mwc-textfield id="systemFieldID" outlined label ="system" .value= "${i.system || ""}"
#input = "${e =>this.value[0].coding[index].system =e.target.value}"></mwc-textfield>
<mwc-textfield id="displayFieldID" outlined .value ="${i.display || ""}"
#input = "${e => this.value[0].coding[index].display =e.target.value}"></mwc-textfield>
`)}
</mwc-formfield>
</div>
<iron-ajax id ="ajax" auto bubbles handle-as ="json" .url ="${this.url}"></iron-ajax>
`
}
/**updated() delivers only after render*/
updated(){
this.shadowRoot.getElementById('ajax').addEventListener('iron-ajax-response', function (e){
let statusReason = this.parentNode.host;
if (e.detail.response.code !== undefined){
statusReason.value = e.detail.response.statusReason
}else {
this.parentNode.removeChild(this.parentNode.getElementById("reasonDiv"));
}
})
}
};
customElements.define('my-el',myEl);
Your exception is happening when you call this.value[0], but this.value is undefined so you can't call an indexer on it.
You also have a circular reference where setting this.value causes render to fire, but then in render you set this.value again. As a general rule try to avoid side effects: in render don't set properties that affect subsequent calls to render.
Next up, you have </mwc-formfield> but no opening tag, that will probably be handled but you should fix it.
I think the root cause of your problem is whatever sets the value attribute, but better error checking could help you identify that:
render(){
if(!this.value)
return html`No value set`;
const parsed = typeof this.value === 'string' ? JSON.parse(this.value) : this.value;
if(!(parsed?.length > 0))
return html`Value array is unpopulated: ${this.value}`;
return html`
<div id ="reasonDiv">
${parsed[0].coding.map((i,index) => html ` ... `)}
</div>
<iron-ajax id="ajax" auto bubbles handle-as="json"
.url=${this.url}></iron-ajax>`;
}
Then inside your loop you're creating the set events: #input= ${e=>this.value[0].coding[index].code = e.target.value} - this will likely cause issues because this.value might be a string, and setting the child won't cause a re-render, but other changes might.
There are a couple of ways around this - I usually use a readonly copy of the server data and update a completely different object to send back user input, but if you want this to be mutable you should implement error handling here too.
Replace the inline calls with a dedicated function, something like:
return html`
...
${parsed[0].coding.map((i,index) => html ` ...
<mwc-textfield id="codeFieldID" outlined label ="code" .value = "${i.code || ""}"
#input=${e=>this.setValue('code', i, index, e.target.value)}></mwc-textfield>
...`)}
...`;
setValue(key, coding, index, value) {
if(!this.value)
this.value = [{coding:[{[key]: value}]}];
else {
const parsed = typeof this.value === 'string' ? JSON.parse(this.value) : this.value;
parsed[0].coding[index][key] = value;
}
}
This makes it far easier to add breakpoints, console.log or whatever debugging to track this down.
Finally, you can skip the JSON parse entirely within Lit with .property syntax:
html`
<my-el .value=${[
{
coding: [
{
system: "http://snomed.info/sct",
code: "166643006",
display: "Liver enzymes abnormal"
}
]
}
]}></my-el>
`;

How can I modify an array in my class and get its new output outside of the class?

let arr = []
class Test extends React.Component {
handleConvertString = (event) => {
let str = this.inputRef.value;
let solutions = ['abb','klopp','lopp','hkhk','g','gh','a'] // Returnsolutions()
if (solutions.includes(str))
{
if (arr.includes(str))
{
alert("Answer already found");
}else{
arr.push(str)
}
}
}
}
Please note some code has been left out but the core issue I am having is outlined.
In this piece of code, I need to get the new contents of my arr(array) and return and access it outside my class.
I also need to be able to abstract it to a different file for usage after doing this.
How can I do this?
There are different ways to do this. Here you can follow the props method to solve your problem.
You have to pass values as props to other components to access the
value of the array outside of this component.
The below example will help you. I have done a few variations in your code to suit myself. The logic will remain the same and you can take help from it.
Note:
I am using a hardcoded value of str to check the result.
I am using displayResult variable and map function to just render the
result. In case the final array has more than one value then map
function will iterate and append each value of array in displayResult
variable and render it. You can avoid this as this is only written to show the result.
You can see clearly from the below code how we are passing values to another component (outside). Similarly, if you want to pass the value to another file you have to import that file and pass the value as props to the respective component defined in that file,
let displayResult = "";
function AnotherComponent(props) {
props.finalArr.map((num, index) => {
displayResult = displayResult.concat(num)
});
return (
<div>Value of final array is: {displayResult}</div>
)
}
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {arr: []};
this.handleConvertString = this.handleConvertString.bind(this);
}
handleConvertString(event) {
let str = "abb";
let solutions = ['abb','klopp','lopp','hkhk','g','gh','a']
if (solutions.includes(str)) {
let finalArr = this.state.arr;
if (finalArr.includes(str)) {
alert("Answer already found");
}else{
finalArr.push(str)
this.setState({arr: finalArr})
}
}
}
render() {
return (
<div>
<button type="button"
onClick={this.handleConvertString}>
Click Me
</button>
<AnotherComponent finalArr={this.state.arr}/>
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Why isn't the mobx #computed value?

Simple: the computed value isn't updating when the observable it references changes.
import {observable,computed,action} from 'mobx';
export default class anObject {
// THESE WRITTEN CHARACTERISTICS ARE MANDATORY
#observable attributes = {}; // {attribute : [values]}
#observable attributeOrder = {}; // {attribute: order-index}
#observable attributeToggle = {}; // {attribute : bool}
#computed get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = attrName
);
return orderedAttributeKeys;
};
changeAttribute = (existingAttr, newAttr) => {
this.attributes[newAttr] = this.attributes[existingAttr].slice(0);
delete this.attributes[existingAttr];
this.attributeOrder[newAttr] = this.attributeOrder[existingAttr];
delete this.attributeOrder[existingAttr];
this.attributeToggle[newAttr] = this.attributeToggle[existingAttr];
delete this.attributeToggle[existingAttr];
console.log(this.orderedAttributeKeys)
};
}
After calling changeAttribute, this.orderedAttributeKeys does not return a new value. The node appears unchanged.
However, if I remove the #computed and make it a normal (non-getter) function, then for some reason this.orderedAttributeKeys does display the new values. Why is this?
EDIT: ADDED MORE INFORMATION
It updates judging by logs and debugging tools, but doesn't render on the screen (the below component has this code, but does NOT re-render). Why?
{/* ATTRIBUTES */}
<div>
<h5>Attributes</h5>
{
this.props.appStore.repo.canvas.pointerToObjectAboveInCanvas.orderedAttributeKeys.map((attr) => { return <Attribute node={node} attribute={attr} key={attr}/>})
}
</div>
pointerToObjectAboveInCanvas is a variable. It's been set to point to the object above.
The changeAttribute function in anObject is called in this pattern. It starts in the Attribute component with this method
handleAttrKeyChange = async (existingKey, newKey) => {
await this.canvas.updateNodeAttrKey(this.props.node, existingKey, newKey);
this.setState({attributeEdit: false}); // The Attribute component re-renders (we change from an Input holding the attribute prop, to a div. But the above component which calls Attribute doesn't re-render, so the attribute prop is the same
};
which calls this method in another object (this.canvas)
updateNodeAttrKey = (node, existingKey, newKey) => {
if (existingKey === newKey) { return { success: true } }
else if (newKey === "") { return { success: false, errors: [{msg: "If you'd like to delete this attribute, click on the red cross to the right!"}] } }
node.changeAttribute(existingKey, newKey);
return { success: true }
};
Why isn't the component that holds Attribute re-rendering? It's calling orderedAttributeKeys!!! Or am I asking the wrong question, and something else is the issue...
An interesting fact is this same set of calls happens for changing the attributeValue (attribute is the key in anObject's observable dictionary, attributeValue is the value), BUT it shows up (because the Attribute component re-renders and it pulls directly from the node's attribute dictionary to extract the values. Again, this is the issue, an attribute key changes but the component outside it doesn't re-render so the attribute prop doesn't change?!!!
It is because you have decorated changeAttribute with the #action decorator.
This means that all observable mutations within that function occur in a single transaction - e.g. after the console log.
If you remove the #action decorator you should see that those observables get updated on the line they are called and your console log should be as you expect it.
Further reading:
https://mobx.js.org/refguide/action.html
https://mobx.js.org/refguide/transaction.html
Try to simplify your code:
#computed
get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = this.attributes[attrName])
);
return orderedAttributeKeys;
};
#action.bound
changeAttribute(existingAttr, newAttr) {
// ...
};
Also rename your Store name, Object is reserved export default class StoreName

What do you call this React Component pattern?

I am having a hard time finding some documentation on this pattern.
Does it have a name?
TextBase is a styled component. so I can extend it as following:
Text.H1 = withComponent('h1') however I want html attributes to be passed as well. Hence the function component. However when I extend my Text component the props are being overridden, resulting with all components being h1's.
const Text = (props) => {
const { children, testid, ...rest } = props;
return <TextBase data-testid={testid} {...rest}>{children}</TextBase>
}
Text.defaultProps = {color: 'red'}
Text.H1 = Text
Text.H1.defaultProps = { as: 'h1'}
Text.H2 = Text
Text.H2.defaultProps = { as: 'h2'}
Text.H3 = Text
Text.H3.defaultProps = { as: 'h3'}
🙏🏽
Try binding the function call using this or use arrow function or manually bind the function using bind. I am sure it will work. Reference fault is all this is

How to add class to Vue component via $refs

I need to add class name to some Vue components using their ref names. The ref names are defined in a config file. I would like to do it dynamically, to avoid adding class manually on each Vue component.
I have tried to find each component using $refs and if found, add the class name to the element's class list. The class is added, but it is removed as soon as user interaction begins (e.g. the component is clicked, receives new value etc.)
Here is some sample code I've tried:
beforeCreate() {
let requiredFields = config.requiredFields
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
You can use this:
this.$refs[field].$el.classList.value = this.$refs[field].$el.classList.value + 'my-class'
the only thing that you need to make sure of is that your config.requiredFields must include the ref name as a string and nothing more or less ... you can achieve that with :
//for each ref you have
for (let ref in this.$refs) {
config.requiredFields.push(ref)
}
// so config.requiredFields will look like this : ['one','two]
here is an example of a working sample :
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('one', {
template: '<p>component number one</p>'
})
Vue.component('two', {
template: '<p>component number two</p>'
})
new Vue({
el: "#app",
beforeCreate() {
let requiredFields = ['one','two'] // config.requiredFields should be like this
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
})
.my-class {
color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<one ref="one" ></one>
<two ref="two" ></two>
</div>
I know this question was posted ages ago, but I was playing around with something similar and came across a much easier way to add a class to $refs.
When we reference this.$refs['some-ref'].$el.classList it becomes a DOMTokenList which has a bunch of methods and properties you can access.
In this instance, to add a class it is as simple as
this.$refs['some-ref'].$el.classList.add('some-class')
You've to make sure classList.value is an array. By default its a string.
methods: {
onClick(ref) {
const activeClass = 'active-submenu'
if (!this.$refs[ref].classList.length) {
this.$refs[ref].classList.value = [activeClass]
} else {
this.$refs[ref].classList.value = ''
}
},
},
this post helped me tremendously. I needed to target an element within a v-for loop and I ended up writing a little method for it (i'm using Quasar/Vue).
hopefully this will save someone else some time.
addStyleToRef: function(referEl, indexp, classToAdd) {
//will add a class to a $ref element (even within a v-for loop)
//supply $ref name (referEl - txt), index within list (indexp - int) & css class name (classToAdd txt)
if ( this.$refs[referEl][indexp].$el.classList.value.includes(classToAdd) ){
console.log('class already added')
} else {
this.$refs[referEl][indexp].$el.classList.value = this.$refs[referEl][indexp].$el.classList.value + ' ' + classToAdd
}
}
let tag = this.$refs[ref-key][0];
$(tag).addClass('d-none');
Simply get the tag with ref let tag = this.$refs[ref-key][0]; then put this tag into jquery object $(tag).addClass('d-none'); class will be added to required tag.

Categories

Resources