I was creating a Dropdown component for React. Inside the dropdown, I have a form of radio group buttons.
<DropdownButton />
<DropdownForm />
In the DropdownButton, I have an state to know if it is open or not. Depends on that, DropdownForm it's hidden or not (using display: none).
The use case is: User selects a radio button, click apply and something happen. However, if user selects some radio button, and mouse out the dropdown (without clicking the apply button), the one that is selected should be the one that I get from the store.
Something like:
render: function () {
...
if(store.getSomeParam() != this.state.someParam && !this.props.isOpen){
someParam = store.getSomeParam()
}
Then the radio buttons are like:
<input checked={someParam == "something"} ... />
It doesn't really work. It re-renders but it doesn't change the button that is checked. I also tried with refs:
this.refs.myInput.getDOMNode().checked = true
But still nothing. Is this a correct behaviour?
The only solution I found so far is not using a css hiding class (display: none). So what I do is that the DropdownButton renders the DropdownForm depending on if it's open or not (so if you close it, you are forcing DropdownForm to unmount). Then when opening again, it is taking the values from the store (getInitialState) and it shows the correct radio button selected. But, I am not sure if this is the best solution and if there is any drawback in unmounting the component instead of just css hiding it.
This probably has nothing to do with React at all.
Most browsers don't validate the value of the checked attribute, but merely if it is there or not: http://jsfiddle.net/7jzm7gvw/
Just set the checked attribute to either true or null:
<input checked={someParam == "something" ? true: null} ... />
TL;DR: You must use the componentDidMount lifecycle method, not render, to work with the rendered dom nodes directly.
I was struggling with this as well, and after doing some online research I figured I might as well look into it for myself. Here's what I came up with:
Use the componentDidMount lifecycle method and update whatever you need to in there. Here's a Pen I used to prototype this, and I think it looks okay: http://codepen.io/gholts/pen/GpWzdb
You could drop this in pretty easily to what your'e working on by just putting a componentDidMount method on your object and doing it there. I used document.getElementById but you could definitely use jQuery or whatever else you wanted in there, as once the component has mounted it's available to DOM selectors.
I'm using this now to update 20 radio button groups (so it has to check a prop for three different states and update accordingly) and it loads instantly.
Hope it helps! I used the ES6 class syntax in my Pen, you should check it out if you have some time to refactor :) It's fun.
EDIT: So I figured it out, I'm a dummy. You don't need to do the whole document.getElementById business that I was doing. Just use your this.refs.whichever.getDOMNode().checked = true and it'll work, so long as you do it in componentDidMount. It works there because there is an actual DOM element on the page at that point.
Related
Been hacking at this for days, hopefully there are some Vuetify wizards around.
Here's the situation: I have rendered a set of v-expansion-panels and if one of them is expanded, it also shows an arrow button (display: none is toggled by clicking on v-expansion-header). Upon that button click, my aim is to show a dialog.
Problem: Once dialog is prompted with the button click, the button display toggle is reversed. It disappears as soon as you click on the button to prompt a dialog, and appears again once the v-expansion-panel is collapsed.
How it should be: The arrow button should always be visible as long as the v-expansion-panel is expanded, regardless of whether it is clicked to see the dialog or not.
Here's a codepen replicating and illustrating the problem.
Any thoughts would be much appreciated!
It has to do with using style directly on the element.
Use v-show instead of toggling the styles by hand:
<v-btn v-on="on" class="ml-1" width="36px" v-show="expanded[i]">
Update your data to hold an array for the pannels
data () {
return {
dialog: false,
expanded: [false, false, false]
}
}
And update your toggleMoveUp method to update expanded instead of using HTML ids.
toggleMoveup(i) {
this.$set(this.expanded, i, !this.expanded[i])
this.show=true;
}
Notes:
You need to use Vue.set when updating an array
You should not rely on HTML ids, if you use your components in more than one place at a time you'll run into multiple ids.
Why didn't your approach work? I'm guessing that vuetify is updating an element's style property, but doesn't take care of retaining already existing values so your display:none gets erased.
Posting a solution a colleague helped with. This also works with any array size (which is more of a real life scenario in dynamic webapps). It implements a created() lifecycle hook that adds an expanded: false property to each element in the array, which we can use to keep track of the expand state and toggle the button visibility. Here's the codepen.
However, in general, it is recommended in this scenario to actually make an independent component <v-expansion-panels /> and in the parent component actually loop the components. That would solve the state problems on its own already, since each component maintains their own state in their scope.
I have a directive that controls what buttons someone can see based on their user role:
import { store } from '../store/';
import * as types from '../store/types';
const hide = vnode => {
if (vnode.elm.parentElement) {
vnode.elm.parentElement.removeChild(vnode.elm);
}
};
export const userRole = {
update(el, binding, vnode) {
const userId = store.getters[types.GET_USER_ID];
const { value, modifiers } = binding;
if (value.role) {
if (Reflect.has(modifiers, 'manager')) {
if (value.role[0] !== userId) hide(vnode);
}
};
Then I'll have a button like this:
<vue-button
v-userRole.manager="{role: job.role}"
#click.prevent.stop="e => payoutJob(job.id)"
>
Button Text
</vue-button>
All the buttons will show on the page before the user directive loads. So tons of buttons flash on the page. And then 2 seconds later only two buttons show, as that is what they have permission to see. How do I prevent this?
I would want at the very least, no buttons to appear on the page until the logged in user is matched against the user role directive file.
User information is stored in location storage, in vuex and every page that loads checks for a signed in user.
The way you have created this I think means that this will always happen - your directive is removing the HTML only after it has been created. For the element not to have been created in the first place you need instead to use a v-if or similar. I can see two ways to fix this, one a work-around as a minimal change to what you have, the other I would consider a better solution (but it is of course up to you).
To work around your problem have all of your buttons with a style of display:none and then in your directive either delete the elemet or clear the style (or change the class - however you choose to implement it). That way at least the button won't appear.
However, a better fix, I think, is to create your own component with a userRole property. That component will then do the check you have above (for example through a computed property) and then show or hide the component as required.
EDIT
I ran a quick fiddle to test the principals behind what you were doing - just changing the hook from update to inserted may fix it. Obviously your code will be more complex so your milage may vary ;).
I would focus on Vue instance - https://v2.vuejs.org/v2/guide/instance.html - I think you might use beforeCreate.
Also another idea as Euan Smith wrote, use v-if and put in your data true/false information of loading rights (set it false, and when the role is known set it to true).
I am trying to test the states of slide-toggles within my app using Cypress.
These time out and fails the test:
cy.get('label.mat-slide-toggle-label').eq(2).should('be.checked')
or
cy.get('div.mat-slide-toggle-bar').eq(2).should('be.checked')
Where as these pass
cy.get('label.mat-slide-toggle-label').eq(2).should('not.checked')
or
cy.get('div.mat-slide-toggle-bar').eq(2).should('not.checked')
The only difference is that the state of the toggle has changed.
Can someone help explain why the "not.checked" tests pass, but the others don't?
The documentation states:
The <mat-slide-toggle> uses an internal <input type="checkbox">
to provide an accessible experience. This internal checkbox
receives focus and is automatically labelled by the text content of
the <mat-slide-toggle> element.
When Angular Material adds the switch, it adds a whole little hierarchy of elements under the outer <mat-slide-toggle> element; divs with classes like mat-slide-toggle-label, mat-slide-toggle-bar, etc. But it also adds a real (but hidden) <input> element.
The 'checked' test only applies to input elements (this is probably why your should('not.be.checked') tests are working--because non-input elements can never be checked. So, to use Cypress's should('be.checked') test, you need to tell Cypress to get a reference to the actual <input> contained within the <mat-slide-toggle>, and not one of the other mat-xxx elements.
Example:
cy.get('mat-slide-toggle#whateverId input').should('be.checked');
// get reference to the single <input> inside the <mat-slide-toggle>
or:
cy.get('mat-slide-toggle#whateverId .mat-slide-toggle-input').should('be.checked');
// get reference to the element with class "mat-slide-toggle-input" inside the <mat-slide-toggle> (which is the <input> itself)
I was going to invite you to use the GUI snapshots panel to better understand what could be wrong, and maybe increase the timeout(s).
But in fact, I'm tempted to conclude that neither <label> nor <div> can be checked. <input type="checkbox"> can.
Is there another property you can assert on your label ?
I have managed to find a element for each toggle that allows me to check the state (checked or not checked).
input#mat-slide-toggle-29-input.mat-slide-toggle-input.cdk-visually-hidden
All I need to do is change the number to related to the toggle under test. I can check that the toggle is checked, press the master switch and then check that it is unchecked. I will also created a test where I test each toggle individually to ensure that the toggle works in a ground and singularly.
I am using 'keen-ui' library in my project. Here is special component for select item. I want to handle event from another component and set focus on this, but I don't know how to.
Another components, that have inside <input/> tag can be focused like this.$refs[component name].$el.children[0].children[1].focus, where children[0].children[1] is <input/> element. This is ugly, but if the component doesn't contain input tag, we can not do even this.
Examining the widgets, I see that they contain a div with tabindex="0", which means they can receive focus.
If you have a ref on the component, you should be able to do something like
const focusableEl = this.$refs.uiselect.querySelector('[tabindex="0"]');
focusableEl.dispatchEvent(new Event('focus'));
and the widget will light up. I actually did that from the console to test it out. Interestingly, blur did not work for me.
I am struggling for a few days with React, and all the tutorials saying how magnificent it is make me feel that somebody is tricking me :) Learning curve especially while i need to comunicate children-parent-children is so annoying! Anyways...
Consider such a situation:
I have multiple buttons. Each button has its own type. There may be mutiple buttons with one type making a group. Whole idea is to have buttons enabling other buttons, and disabling other ones.
I provide pen to see working example, however imagine i have buttons of types:
settingsOn - it enables all the buttons of type settings
settings - disabled at the begining, enabled after clicking settingsOn button
next - it enables all the buttons of type answer
answers - like settings, enabled after clicking next button.
Normal button looks like this:
<MyButton name='next' type="next" check={() => this.checker("next")} toggle={this.toggler.bind(this)} />
Parent send toggle function and check function with parameters to child.
Only settingOn and next are enabled at beginining. It is made by App.state.next set to true and App.state.answers to false. If App.state.answers is false all the components of type answers are disabled.
Now, everything seems to be working here however one last thing is not:
If i press button "next" it checks what is parent's state.answer and sets it to true (check console log in my pen for confirmation). However changing parent's this.state.answer to true doesnt rerender components of type answer.
To be honest i wanted to make it less messy and learn React, but it seems i could do it via jQuery in one hour a few days ago :/ Is there a good solution for my problem? Should i come back to jQuery and forget React? Or i am one step from solving the problem?
Please help!
Link to codepen
Here's a couple of things I noticed on your codepen that stood out as possible issues.
onClicked(){
this.setState = { answer: this.props.toggle("answer") };
// should be
this.setState({
answer: this.props.toggle("answer)
});
}
Another observation is on componentDidMount you're setting a default state with this.setState({ answer: false, isActive: this.props.check("next") }), I would move these default states to the constructor() as it'd avoid a rerender.
I'm not exactly sure if this is an anti-pattern or not, but I'd avoid setting/getting state like
this.setState({
[type]: !this.state[type]
// instead use
type: !this.state.type
})
Finally I think I was able to get a working component of what you were asking, see this codepen.io. Hopefully this helps, happy coding!