Suppose there's a table that needs to keep some state, for example, knowing the last element that was hovered over. To do so, we'd need a onMouseOver event on a child element like this:
<tr onMouseOver={handleMouseOver}></tr>
and then the component containing the table would re-render all the children after setState({lastHovered: e.target}) (something along those lines)
If the goal of hover is to make changes to just one row (let's say show/hide something without using CSS) is there a way to keep this state at a higher level than the row and only render changes in the <tr>s that are affected by the hover?
As an example, let's say there's a delay on hovering in or out of the table. When hovering in, children should show a tooltip, but only if hovered directly over. The table/parent need to maintain some state to keep track of this but is there a way to not have to re-render the entire list of children?
For Functional Components
Wrap all the tr elements inside a React.memo (read) to only listen for changes to its internal props. Also, place the onMouseOver event on the table itself and not every child immediate element, then check which child element is in "focus" (i.e., event.target).
Inside table component:
const [lastHovered, setLastHovered] = useState(null);
const [trProps, setTrProps] = useState(null);
return (
<table onMouseOver={event => setLastHovered(event.target) /* Also check if 'TR' type */ }>
<TableRows someData={trProps} />
</table>
)
Table rows component:
const TableRows = React.memo( ({someData}) => /* Triggers update only if 'someData' changes */
<> { someData.map( data => <tr>{data}</tr> ) } </>
)
Note: The above code isn't tested; written to demonstrate a possible solution.
For Class Components
If you're using class components, then create a custom TableRow element and add shouldComponentUpdate() (read) to control rerenders, or use PureComponents (read) instead.
Related
I'm reactively loading elements using Svelte's {#each} functionality, like so:
{#each $items as item}
<div>
<Button on:click={someFunction}>{item.text}</Button> (*)
</div>
{/each}
(*) a component which forwards its on:click
What I want to do is when someFunction is called by clicking the created item, reference the actual DOM element. I know you can reference the specific array item by passing the index to the function, but that gives me the array item, not a reference to the unique DOM element. How would one go about doing this?
Things I tried so far:
on:click={() => someFunction(this)}: returns undefined
on:click={(el) => someFunction(el)}: returns undefined
on:click={(e) => someFunction(e)}: followed by using e.target, which does return the button that is clicked, but would need .parentElement to get to the div, which doesn't seem like a very Svelte way.
on:click={someFunction}: combined with bind:this={anItem}, which of course only returns the last created element in the {#each} block.
If you just want the parent div, a quick and dirty solution would be to just use an Array.
A more elegant solution would be to wrap <Button> within a parent component and get the reference from there, through the context API for instance or a slotted prop.
<script>
let itemsElements = [];
const someFunction = (i) => console.log(itemsElements[i]); // <- this is your div
</script>
{#each $items as item, i}
<div bind:this={itemsElements[i]}>
<Button on:click={() => someFunction(i)}>{item.text}</Button>
</div>
{/each}
the button in question is a modal which I want to have an open/close functionality. So, upon clicking itself I want it to toggle the class "open". However, using the class directive this results in all buttons/modals generated by {#each} to simultaneously toggle the class.
That just means you are using the wrong property. If you have multiple objects you need to store the state per item and then use that for the class directive.
E.g.
{#each $items as item}
<div>
<Button on:click={someFunction}>{item.text}</Button>
<div class="modal" class:open={item.show}>...</div>
</div>
{/each}
Alternatively you can store the state in a separate list or a dictionary. You can also extract the content of the {#each} to a component which then can have a local state variable for the open state.
I have two components, a parent and a child.
Parent Component
const Parent = () => {
return (
<>
<div className="container"></div>
<div>more content</div>
<Child/>
</>
)
}
Child Component
const Child = () => {
const importantFunctionMustBeInChild = () => {
//Does a bunch of stuff that can't be done in the parent due to a lot of state that doesn't make sense to have in the parent
}
return (
<>
<button onClick={importantFunctionMustBeInChild}>Important Button</button>
</>
)
}
The problem is that I have a button in the child component. This button renders conditionally based off many different state toggles and has functions that can't be in the parent component because it wouldn't make sense to put it there and would take a long time to move all the state and functions up.
The issue is I now need to have the button to where the container div is in the parent component. Is there any way this can be done?
Going by the logic, you can assign a value(string, object, array) to parent component's state in the child component, right? So why can you not assign a function to the state and run it on click in the parent?
Definetely you can! In your child, assign function as a reference to the parent's state and thats it!
useEffect(() => {
// assign, don't invoke the function yet!
setFunctionToExecute(() => importantFunctionMustBeInChild);
}, []);
Here's a complete working demo
There are multiple tabs like this:
<Menu.Item className="tab-title tab-multiple" key="key">
<p className="tab-title-text">
Tab title
<span className="items-counter">{showText}</span>
</p>
</Menu.Item>
the one that is the active/selected one, beside of its original class (tab-title tab-multiple) it also has active and its class looks like this: active tab-title tab-multiple
I want to show that element only if the class contains "active".
Is there a way to do this in React? Without taking in account onClick.
Tried with a ternary but it seems it does not work:
{element.classList.contains('active') ? (
<span className="items-counter">{showText}</span>
) : (<></>)}
Normally, you don't have to do that in React because you drive the classes on the element from state information in your component, and so you just look at that state information rather than looking at the class list. Your best bet by far is to do that, rather than accessing the DOM class list later.
If the active class is being added by something outside the React realm that's operating directly on the DOM element, you'll have to use a ref so you can access the DOM element.
To create the ref:
const ref = React.createRef();
To connect it to your React element, you add the ref property;
<Menu.Item className="tab-title tab-multiple" key="key" ref={ref}>
Then when you need to know, you check the current property on the ref:
if (ref.current && ref.current.classList.contains("active")) {
// ...
}
Beware that if you do that during a call to render (on a class component) or to your functional component's function, on the first call the ref will be null and on subsequent calls it'll always refer to the element for the previous version of the component. That element will probably get reused, but not necessarily.
React is driven by the data model (props & state). Use whatever data property you use to assign the active class name, to also hide/show the contents.
Another option is to use CSS:
.items-counter {
color: red;
}
.tab-title:not(.active) .items-counter {
display: none;
}
<div class="tab-title tab-multiple" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Not Active</span>
</p>
</div>
<div class="tab-title tab-multiple active" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Active</span>
</p>
</div>
You need to have an indicator, that maintains the active class.
let className ="";
if(isActive)
{
className = className +" active"; // props.isActive in case of child component
}
Now that you have added the className based on the flag.
instead of checking for,
if(element.classList.contains('active'))
you can check for,
if(isActive)
This is applicable for subcomponents also, where you read the isActive flag through props.
I have components 'Parent' and 'Child'.From Parent we can add or remove child. so children are dynamic. in Parent I have rendered Child component in loop like below
Parent.component.html
<child *ngFor="let child of children" [data]="child"></child>
Now in child component I have added a function called IsValid() to check child is valid or not
Child.component.ts
IsValid()
{
//check validity of component and return true if valid else false
}
in parent component I have a button called 'Save' I have to enable that button if all child's are valid else need to disable that button.
So I need a way to call Child components IsValid function for each child component from Parent and then determine the validity result and apply it to Save button to enable or disable
What I have tried
1.
I have emited valid or invalid result from child to parent and if any childs result is invalid i have disabled save button.
but problem here is : if I have added one child, make it valid, save button will be enabled. now I have added another child which is invalid so save button will be disabled but if I remove invalid child save button will be disabled though we have only one child which is valid.. since IsValid event get emmited only if current child get change.
2.
I can use something like this
<child #varName></child>
#ViewChild('varName') childElement;
and then from parent I can call
childElement.IsValid()
but since I have rendered childrens in loop how to give unique name in loop and how to add reference to that unique HTML tag in ts file.
I have created case here SlackBlitz
Any help will be appreciated. Thanks
You might want to use #ViewChildren
In your parent component:
#ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
areChildrenValid(): boolean {
const invalid = this.children.some(c => !c.IsValid());
return !invalid;
}
Note that children will be defined after AfterViewInit hook.
#angular/core provides ViewChildren and QueryList, probably that should help you.
<child #varName></child>
import { ViewChildren, QueryList } from '#angular/core';
#ViewChildren("varName") customComponentChildren: QueryList<YourComponent>;
this.customComponentChildren.forEach((child) => { let retult = child.IsValid(); })
You could use Component selector:
#ViewChildren(ChildComponent) childrenList: QueryList<ChildComponent>;
and run a loop through it and determine the validity.
I have the following Vue.js components, which basically are supposed to have a radiobutton-like behaviour:
// Parent Component
<template>
<child-component
v-for="element in elements"
</child-component>
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: {
ChildComponent
},
props: {
elements: Array
},
methods: {
activate(e) {
for (let i of this.$children) {
i.active = false;
}
if (e < this.$children.length) {
this.$children[e].active = true;
}
}
}
}
</script>
and
// Child Component
<template>
{{active}}
</template>
<script>
export default {
props: {
active: Boolean
}
}
</script>
This works fine but only the parent can decide to activate one of the children (and thus deactivate all others).
I want however also be able to allow each child to activate itself (and by a magic property of its parent, deactivate all other siblings).
Obviously I do not want each child to know about its siblings and mess with their .active prop (super bad design).
I would rather not have a children communicate back up to the parent and call some method (still bad design as I could only reuse the child components in parents that have activate() method).
Instead I would like the parent to listen to changes to all children active props and take action when one of them changes. That way the parent entirely encapsulates the radio button behavior.
How can this interaction be implemented in Vue.js?
Take a look at two-way binding: http://vuejs.org/guide/components.html#Prop_Binding_Types
This allows you to sync a property's value in both directions, meaning the parent or the child has access to change the variable. Then you can watch for changes on the parent and update accordingly.
I think a better option would be to create a RadioSet component, which then would house a number of radio buttons. This would eliminate your concern about a parent having to have the activate() method. You could simply pass in an object with a series of id and values that could be used to generate the buttons.