Currently I'm learning VueJS and I'm working with http://vuematerial.io.
I have build an application with several pages - each of them contains a sidebar (the drawer component https://vuematerial.io/components/drawer).
Since I don't want to copy and paste the same drawer component code over and over again in each page, I just want to create one sidebar component, which I'll then import on each page.
So far, so good.
This is working fine.
But now - I want to be able to open and close the sidebar.
Just before, when the component was directly in the page, it was easy - just a variable assignment with a boolean value to whether show or hide the sidebar.
But now, it seems very hard for me, to synchronize the property over the components.
Let me show you the current new code to clarify what's the problem:
So, this is the page component:
<md-toolbar class="md-primary">
<md-button class="md-icon-button" #click="showSidebar=true">
<md-icon>menu</md-icon>
</md-button><span class="md-title">Dashboard</span>
</md-toolbar>
<Sidebar v-bind:showSidebar="showSidebar"></Sidebar>
So that's the Vue Structure - you can see - I want to bind the showSidebar property.
That's how I'm implementing it within the page
import Sidebar from './sidebar.vue';
export default {
data: function () {
return {
showSidebar: false
}
},
components: {
Sidebar: Sidebar
},
And now the Sidebar component itself:
<md-drawer v-bind:md-active.sync="showSidebar">
The sidebar component then fetches the value over a property like this:
export default {
name: 'sidebar',
props: ['showSidebar'],
And this seems to work!
I can click on the menu button on the page, set the property to true - and the sidebar shows! Great! But.. When I click outside of this sidebar, this warn message appears - and - furthermore - I can't reopen it again on a new click. It closes, but I can't open it again, until I completely reload the page.
How can I be able to solve that?
I have also thinked about using events, since the drawer component seems to listen on events, but I wasn't successful.
That's the current code from the drawer component: https://github.com/vuematerial/vue-material/blob/dev/src/components/MdDrawer/MdDrawer.vue
I hope that it was clear, what my problem is.
I hope, anyone can help me out.
This is my first question here - so please be nice :)
/EDIT:
Opps, here is the warn message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "showSidebar"
I'm not a Vue pro - still learning it myself - but I think I can see what is going on here.
I think the warning in particular is because you have a prop AND a data property of the same name. Try removing the data setting. You can change the props setting to this:
props: {
showSidebar: {
type: Boolean,
default: false
}
}
See if that fixes it. Also, given how you seem to be using this, I'd suggest looking into using Vuex. The documentation is good and it could really help manage your app. Possibly even fix that issue with reopening the drawer.
Edit:
After reviewing this user's code more closely (connected with them on discord), I've determined the issue is that while the process of opening the sidebar was managed by a property on the parent component, the act of closing it was strictly occurring in the child. When you have data bound like that from parent to child, you need to be sure to update the source (the parent component) of the relevant changes. In Vue, those changes are only pushed one direction. To pass info up to the parent, you have to $.emit events.
My current recommendation is to add a custom event to the sidebar component tag on the parent component:
<Sidebar v-bind:showSidebar="showSidebar" v-on:hide-sidebar="showSidebar=false"></Sidebar>
And then change the close tag in the child component to this:
<span class="md-title" #click="$emit('hide-sidebar')">FleaMaster</span>
Hopefully this information helps someone else as well!
Related
I'm a little new to React Native and making an app. There are three components I'm currently concerned with:
AllList.js: A screen comprised of a search bar and a FlatList of RowCard.js instances.
RowCard.js: a custom TouchableHighlight component that displays an item from an API, and currently returns an alert when tapped.
drinkPopup.js: A custom Modal component that needs to take an ID from AllList but be controlled by tapping a RowCard.
I have the list of RowCard instances working, but I need to find a way to make the modal from drinkPopup appear when RowCard is tapped. I'm super confused as to how to approach this, since as far as I know props can only be sent from parent to child.
Any suggestions for how to do this? I've looked around to find answers but the results I've found have just been confusing.
So you need a state that will be accessible by both the drinkPopup and RowCard. The way to go is to keep it in their parent (AllList) and pass it accordingly.
So you Parent should be something like:
const AllList = () => {
const [visibleModalId, setVisibleModalId] = useState(null)
return <>
<RowCard setVisibleModalId={setVisibleModalId}>
<drinkPopup visibleModalId={visibleModalId}>
</>
}
That way you can control the modal from RowCard (by calling setVisibleModalId there) and you also know if the drinkPopup should be visible (because it knows if the visibleModalId is null or not)
I have a component which is shown in a particular view.
I am now taking that component using <compose> to show it in a pop-up as well.
The component has several components within it.
example code would be like this:
<div if.bind="something">
<inner-component if.bind="something else"></inner-component>
</div>```
this is obviously not the actual code, I am just showing how it is set up. The reason being That when I added the outer if.bind The bindingContext supposed to be coming in through bind() in the inner component is now undefined BUT this is only in the original view of the component, NOT in the new pop-up view. Before I added the outer if it worked fine for the original view (the pop-up view didnt exist yet)
What am I missing?
I have a little question about some techniques that are used to render/not render modals.
At the moment there are 2 main ways to do so.
For the first example, we use a visible prop onto the modal and based on that, we will apply a style that will hide the modal. This will be handled by state and then toggled with a button for example :
<Modal
title="Foo"
visible={this.state.visible}
>
Foo
</Modal>
The second way of doing also use state, but uses a condition to render the modal or not :
{this.state.visible && (
<Modal title="Foo">
Foo
</Modal>
)}
The handy thing with this is that the modal will not be rendered until it should.
So what is the best way of doing? I suppose the 2 are right but is there one that is better than the other?
Personally second one is better, because by checking the state at Parent Component, you separate Parent Component logic and Child Component logic, since Modal component only responsible for showing the modal, but the logic whether open or close modal belongs to Parent Component logic. But both solutions will work :)
Just research the question in UI libs docs: antd, material-ui, semantic-ui.
And you will find the answer => prop (with names open, show, visible etc.) is the best way to control visibility (inner state of component).
For example you can see antd modal that use this package
(react-component/dialog):
https://github.com/react-component/dialog/blob/master/src/Dialog.tsx
You can return null or use css (display: none; for sample) for invisible modal
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 working on a React project with a messaging component nested inside a show component. I am trying to bring the scroll within the messaging component to the last message by using the scrollIntoView javascript function. However, when the function is called, the entire show component page is focused on the message, when I want the page when rendered to start at the top, and just have the last message be the focus only of that component.
I have tried breaking up the components and rendering messaging inside the show component, then calling scrollIntoView there, but that does not work.
Is there a way to bring the scroll only within the component and not the entire page itself? Any hints or direction would be appreciated.
The code for the components are quite long and not that relevant so I can simplify it if needed.
I am using the following function inside the nested messaging component:
scrollLastMessageIntoView() {
const items = document.querySelectorAll('.each-message-box');
const last = items[items.length - 1];
if (last) {
last.scrollIntoView();
}
}