Conditionally render or prop for modal render - javascript

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

Related

How can I have a component control a sibling component in React Native?

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)

Why does v-dialog reset another element's visibility toggle?

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.

How can I make a parent component be the label of its child component?

I am using a library that renders a certain button to my layout. This is the library react-add-to-calendar.
I don't have any customization options, so I am currently wrapping the button in a div that gives the button the look that I want. The problem is though, that the child component only takes a very small portion of the parent component, and only the child component reacts to the click.
So for the user, there's a button, that most of it is unclickable, even though it looks like it should be. Only clicking on the text itself sparks the desired action.
How can I make the whole button react appropriately to the click?
This is my code:
<div className="stream__button stream__button-line clickable">
<AddToCalendar event={event} />
</div>
According to the docs you can pass custom classes with the following props:
buttonClassClosed
buttonWrapperClass
buttonClassOpen
dropdownClass
rootClass
Can you try this.
<AddToCalendar event={event} rootClass="stream__button stream__button-line clickable" />
and remove the outer div you have now.
You can try buttonWrapperClass if rootClass is not working.

VueJS Material.io Drawer component variable binding

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!

React render, change radio button programmatically

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.

Categories

Resources