React/redux + bootstrap, make modal show unique for component - javascript

I have a component that shows a modal to pop up some content in my map. I have a pretty straight forward set up :
The JSX looks like this :
<Modal show={this.props.results.showPreviewModal} >
{myPreviewContent}
</Modal>
2 action-creators to open, close, and set the current item :
export function previewAsset(result) {
return {
currentResult: result,
type: actions.PREVIEW_ASSET
};
}
export function closePreviewModal() {
return {
type: actions.CLOSE_PREVIEW_MODAL
};
}
And their reducers :
case actions.PREVIEW_ASSET:
return state.set('currentPreview', action.currentResult).set('showPreviewModal', true);
case actions.CLOSE_PREVIEW_MODAL:
return state.set('showPreviewModal', false);
Now, this seems to work fine. However, the issue is that the component that has the modal inside of it is inside a map, as it is a singular search result (each result component has a some functionality so it is it's own component that is mapped over with results). The issue is that if I have 10 results, this modal opens 10 times when I click the button that fires the previewAsset action.
This makes sense, because the showPreviewModal is accessible by all components, but what I am wondering is if there is a way to make then unique for each component individually, so only the 1 modal opens, not all 10. Unsure how to approach this within react/redux, would very much appreciate any advice, thanks!

An approach I've used successfully is to pull the Modal component out of the loop (or map() in this case), and have a reducer for currentItem or something similar, which gets set when an item is selected (you could also use currentItemIndex, and then select the current item based on that in your connect() call).
In the parent component, you'd have the Modal as a child, and only display it if that currentItem is not null.
Here's a quick JSBin example to show you what I mean:
http://jsbin.com/fefoxoy/edit?html,js,console,output

Related

Deleting a react component from component list deletes the other components in the list

I have a list of components that are added upon button click, so every button click adds one of the components. Each component has a button that can be clicked to delete the component from the list.
Each pair of text "New Item" and button is created as one component in the insertNewCollectorFields function. This function is called when the maroon button is clicked. Each component is added to a list of components "collectorsList" to be displayed. For each component, I use the current length of this collectorsList as the id and key.
This is my code:
import {useEffect, useState} from "react";
export default function Test(){
const [collectorsList, setCollectorsList] = useState([])
const insertNewCollectorFields = () => {
setCollectorsList(collectorsList.concat(
<div id={collectorsList.length} key={collectorsList.length}>
<p>New Item</p>
<button onClick={()=>deleteNewCollectorFields(collectorsList.length)}>Delete New Item</button>
</div>
))
}
const deleteNewCollectorFields = (id) => {
console.log(id)
setCollectorsList(collectorsList.filter((item) => item.id!==id))
}
return <div>
{collectorsList}
<button type={"button"} className={`btn col-auto maroonButton`} onClick={insertNewCollectorFields}>Add Another Collector</button>
</div>
}
I am able to add the components, and I can delete components that were added last without an issue. However, if I delete one of the components that was added first, any component that was added after it is deleted too, and I don't understand why.
This is an example of the issue:
Here I add three components:
Now I delete that third component, and no issue:
If I however delete the first component, all three components disappear, and this is the issue:
I created the list of components as a state. This is my first big project using React. What I know is that on creating a state, the value is updated and should be seen by all the components with the updated value? But apparently on debugging, I found that the "collectorsList" seen by every component in the deleteNewCollectorFields function is limited to the list that was present before creating that component. For example, if collectorsList contained 6 components, component number 3 only sees that there is component 1 and 2 in the collectorsList, and does not see that the collectorsList actually contains 6 components. So on deleting component number 3, the filter process in the delete function results in only component 1 and 2, and everything else disappears from the screen.
I don't understand why this happens and why the components are not able to see the updated list with all the components that were added to it. I also searched for other examples were such a delete function was created and it seems that my method should work, and I couldn't find any case were this problem was faced.
What am I doing wrong? Is there something I understood incorrectly about the use of states?
Your problem is that you are using id={collectorsList.length}, which is wrong, because the .length is changing every time you delete an item and this give wrong id to the deleteNewCollectorFields, just make unique id

How to send data from v-for to event handler in Vue?

I am having problems with sending the right data to the event handler of a subcomponent in my Vue component.
Here is my code:
<template>
<div>
<div v-for="item in [1, 2, 3]">
<div #click=test(item)>test</div>
<ConfirmModal v-if="showModal" #confirmed=test(item) />
<button v-on:click="showModal = true" ></button>
</div>
</div>
</template>
<script>
import ConfirmModal from 'ConfirmModal'
export default
{
components: {ConfirmModal},
data()
{
return {showModal: false}
},
methods:
{
test(item)
{
console.log(item);
this.showModal = false;
},
},
}
</script>
And here is the code for a subcomponent:
<template><div class="modal" #click.self="$emit('confirmed')">subtest</div></template>
So, basically, I here have an array of numbers and am using the v-for to iterate over an array.
For each item one test label, one subcomponent (containing a subtest label) and one button is created. When button is clicked, subcomponent is shown. When I click on the subcomponent (subtest label) it disappears.
div is a standard component which emmits #click events when it is clicked upon.
confirmModal is a very simple component which listens for #click events and redirects them into #confirmed events.
So, this code generates 3 times 3 items. When I click on any of the labels, test method is called which logs the value of the item.
When I click on any of the test labels, I get the correct value in the console, that is, 1 for the first item, 2 for the second item and so on.
But, when I click on any of the subtest labels, I allways get the 3, whichever link I clicked. (If I change the order of items in the array, then I allways get whichever item I put on the last place in the array).
I do not understand what is going on here, it is the same method called on the same way. Why is 3 allways logged in console when I click on the subtest label?
If you wonder what is class=modal in my subcomponent used for, I have no idea. This is a skeleton of two components in a very complex project consisting of thousands of files built on top of the Liferay, and there are more then ten files where is .modal defined in many directories. So I am not sure from which file does this particular component load this class. I did not throw it out of this skeleton code just because when I remove this class from the div in the subcomponent, everything works fine. I have no idea why. How can a class stop the correct data from being received? I do not get this.
I know that this bug may not be reproducable, because I did not provide the definition of the .modal style, but I posted it anyway, because I hope that you can see something else in the code what is wrong or propose another way how to execute the test method on #confirmed event.
I am using Vue 2.

Vue.js v-for generated html block capturing collapse state with two way data binding

I'm trying to capture click action state (for collaps function) within html v-for generated block. To achieve this, I'm using declared data table, and it looks like state is being captured correctly. Below I'm ataching simplified v-for section, where I display state after click action. Displayed state is always false, even though after click, console.log shows table fields changes. Can someone please try to explain me why is that, and how to achieve what is expected here? I'm pretty new to vue, and must be doing something wrong...
<div v-for="address in userAddressData">
<a #click="expandControl(address.id)">
Address {{expandArea[address.id]}}
</a>
</div>
...
export default {
data () {
return {
userAddressData: '',
expandArea: [false,false,false]
}
},
methods: {
expandControl (id) {
this.expandArea[id] = !this.expandArea[id]
console.log(this.expandArea)
}
},
...
If I understood right, the problem you are having is that the template is not being updated according to the data, as we can see on this fiddle: https://jsfiddle.net/gen1kL3y/
This is issue is related to how Vue Reactivity works.
You can find another similar question here and I made a working JS fiddle for you here: https://jsfiddle.net/vz11zdjc/
Basically, instead of setting the value as this.expandArea[id] = !this.expandArea[id], you should (among other options) use Vue.set(this.expandArea, id, !this.expandArea[id])

How to update the data of a grandchild component in React

So I am using 'react-popout' from https://github.com/JakeGinnivan/react-popout to create a popout window has this subWindow component which has its own data fields in it. I have a MainComponent which defines a list of these popoutwindows (there can be mulitple) and as I define this list I also define that the PopoutWindow has a child subWindow (I leave PopoutWindow component package alone). The PopoutWindow is a wrapper.
So I call the subWindow as part of PopoutWindow inside MainComponent and I want to be able to change the data that I pass into subWindow (currentWindowData) and trigger a re-render but right now it is not working AND I want to only change the data of just one of these PopoutWindows in the list, and not any others.
How can I have it so that upon say a button click, I change the data of only ONE of the subWindows but not all of them (assuming multiple PopoutWindows are rendered and in the list)?
<MainComponent>
openWindow() {
const keyId = shortid.generate();
const window= <PopoutWindow
title={keyId}
options={{height: '700px', width: '350px'}}
key={keyId}
>
{
<subWindow key={shortid.generate()}
data={this.state.currentWindowData}
/>}
</PopoutWindow>;
this.setState({
renderSubWindow: true,
subWindowArray: [window, ...this.state.subeWindowArray],
});
}

Replacing child react component with another based on user intreaction or button click

I have created bootstrap modal component in react.
var carModal=<Modal handleHideModal={this.handleHideCarModal}
onConfirm={this.confirmCarModal}
title="Car"
id="CarModal">
<CarStructure callbackParent={this.showCarOptionsUpdate}/>
</Modal>
In car structure I have a form where based on button click I want to remove <CarStructure> child component from the modal carModal and update it with another component with different form structure. How can I replace <CarStructure> with another component having totally different user interface and options?
Remember that the job of a React component's render function is to describe how the component should look at any given point at time, so you don't "remove" or "change" pieces of the DOM so much as you describe when they should change themselves. So, you'll want to base the internal component of <Modal> on some sort of state (which may change throughout the life of the parent component). Something like this might do the trick:
var carModalBody;
if (this.state.something) {
var carModalBody = <CarStructure callbackParent={this.showCarOptionsUpdate} />;
} else {
var carModalBody = <SomeOtherComponent />;
}
var carModal = <Modal handleHideModal={this.handleHideCarModal}
onConfirm={this.confirmCarModal}
title="Car"
id="CarModal">
{carModalBody}
</Modal>

Categories

Resources