share CSS variable var() to :global() svelte parent component reactivelly? - javascript

I tried to put the minimal example: (not real code, but similar and simpler to demonstrate the problem)
<script>
let parentOverflow = false;
function setParentOverflow() {
if(parentOverflow) return 'hidden'
return 'auto'
}
</script>
<div
style="--parent-overflow:{setParentOverflow()}"
transition:scale={{duration: 2000}}
on:introstart={() => (parentOverflow = true)}
on:introend={() => (parentOverflow = false)}
>
hello world
</div>
<style>
:global(.parent) {
overflow: var(--parent-overflow);
}
</style>
As you see I am using :global() in the style tag,
which means you can style globally also the parent div
everything work fine in all the code (functions, styles, transitions, introstart, introend)
❌ but overflow: var(--parent-overflow) give me the problem
technically in dev tools is shown (so the parent have the style, see image below)
but the the parent can't have the var(), yes, because ✅ if I write overflow: hidden work very good.
but var() not.
is there a way to pass also var() the value reactively to the parent?
another info is that if a child works fine the var()
I can't move the code function to the parent since the animation happens on the child.
but I can pass/export in some way a prop if there is a way for the parent and then write the CSS there.
I am open to everything that can make pass the returned value of the function to the parent and then style the parent reactively from child var.
also if the class .parent is present to multiple is there a way to only change the parent one of the child, and not all .parent?
<parent> <!-- yes only this -->
<child>
<parent>
<parent> <!-- don't change this -->
<parent> <!-- the child style on the first don't need to change this, because they are totally different things -->
<child>
<parent>
basically a concept like this .closest() https://developer.mozilla.org/en-US/docs/Web/API/Element/closest?retiredLocale=it (but without javascript, only css :global())
to mention .closest() can't work correctly in svelte so isn't worth it. I think can be a way with only CSS only (or svelte magic)
sorry for the long details, but I tried to put everything I know.
If you more details tell me I will add them too.
thanks for reading :)

If Child and Parent component are connected via slot
<Parent>
<Child />
</Parent>
you could use setContext / getContext for the communication. Like this only a Parent and it's Children share the information, the other sibling Parent components are not affected. A store is used so that the value is reactive. REPL
Parent.svelte
<script>
import {setContext} from 'svelte'
import {writable} from 'svelte/store'
const parentOverflow = writable('hidden')
setContext('parentOverflow', parentOverflow)
</script>
<div class="parent" style:overflow={$parentOverflow}>
<slot />
</div>
Child.svelte
<script>
import {scale} from 'svelte/transition'
import {getContext} from 'svelte'
let parentOverflow = getContext('parentOverflow')
</script>
<div transition:scale="{{duration: 2000}}"
on:introend="{() => ($parentOverflow = 'auto')}"
>
Lorem ipsum dolor sit amet, ...
</div>

Related

how to add attribute without actually editing the HTML element in react

I have a below HTML component created in REACT which I cant edit directly. But I need to add one attribute to one of its HTML element. I can't use jquery as well other wise its easy to do with jQuery.
Below is the base HTML for table which I cant edit directly but I can just use this component in my code.
Challenge : I need to add attribute to the SVG element. e.g. -> data-id="1". Can it be done with CSS or any other way.
<TablePresenter>
<div>
<svg>this is a actually a sort button</svg>
<div>Column 1 Name</div>
</div>
<div>
<svg>this is a actually a sort button</svg>
<div>Column 2 Name</div>
</div>
</TablePresenter>
main file which I can edit is as below.
const MyComponent = () => {
some logic here...
Can we do something here may be CSS or any react hack to get underline component HTML change.
return(
<TablePresenter></TablePresenter>
)
}
export default MyComponent;
Not a recommended way of doing, but using a ref you can achieve this, a sample below:
Add a wrapper div to your parent component (your own component which can be edited) like below and add a ref to it, that will helps you to get the DOM reference of the html inside the <TablePresenter>
const divRef= useRef<HTMLDivElement>(null);
return <div ref={divRef}>
<TablePresenter></TablePresenter>
</div>
// This is an example of getting the reference of the svg.
divRef.current.firstElementChild.firstElementChild.firstElementChild
May be something like this:
const MyComponent = () => {
const divRef = useRef<HTMLDivElement>(null);
useEffect(()=>{
divRef.current.firstElementChild.firstElementChild.firstElementChild.attributes["data-id"]=1
}, [])
return <div ref={divRef}>
<TablePresenter></TablePresenter>
<SuperChild />
</div>
}

Change id of multiple item of array React

I'm using React JSX with Hooks, and I have a problem. I'd like to change a p id of all the vector items when I click on one of them.
In particular:
I have a vector and for every item of this vector I call a component:
cards.map((item, index) => (<Card key={index} card={item} index={index} hover={hover} /> ))}
Inside the Card componet I have the div tag with onClick event: <div onClick={TitleTransition}>
The TitleTransition function changes a state of click: function TitleTransition() { setClick(true);}
This is the p tag inside the Card component: <p id={click ? "titleTransition" : ""}>{props.card.p1}</p>
Hence, when I click in one of these item (I have called them card) I'd like to change the p id for all the items (cards).
Anyone could help me? Thank you!
this is more a guess answer than anything else, but i'm unable to comment before 50 reputation.
If i had to guess, I'd say you are trying to change the styling onClick on a Card component and maybe additionally reset the styling of the other cards.
In that case you should try to use instead of the id prop of the p tag either the style or the className prop. I imagine you have a css file with something similar to
#titleTransition {
/* Your styling if element is clicked */
}
If you use the className prop, which is the React equivalent of the html class attribute, you have to change your css line to something like
.titleTransition {
/* Your styling if element is clicked */
}
or
:global(.titleTransition) {
/* Your styling if element is clicked */
}
or whatever way of styling you prefer in react, there are so many.
If you want to change all Card elements, onClick you will probably have to save the dataset for the cards in a state in their parent component (where the .map(...) is used to add the elements) and add an attribute to that data, such as 'isClicked'. Then define a function that takes the index of the and add the function to the props of the Card, like () and call that function in the Card by adding it in the . Then use it in the :
<div onClick={() => props.onChildClickChangeDataState(props.index)} />
This will trigger the function in the parent, where you can iterate over the dataset and change the isClicked attribute to the value you like.
To change the styling you then can use
<p className={props.item.isClicked ? 'titleTransition' : '' />
This an approach that helps you changing all at once as well as avoiding the invalid HTML #CertainPerformance mentioned.
I hope i guessed right and it helps you on the right track. It is hard to help you more at the moment. Maybe you could share the Code of you Card Parent Component, Card, your CSS file, and why you would like to change all id's at once. Then somebody might be able to help you a bit better.

Using the hidden attribute vs conditionally rendering a component

I have recently discovered an alternative to conditionally rendering a component in JSX, which is to use the hidden HTML attribute.
Example
function Parent() {
return {!hideChild && <Child />}
}
vs
function Parent() {
return <Child hidden={hideChild} />
}
function Child({ hidden }) {
return (
<div hidden={hidden} >
//my content
</div>
)
}
So far I have not noticed any performance or alike issues when using hidden. In saying that, are there any downsides to have lots of HTML on the page that is hidden?
For me, this approach has served well when I want to retain the component state and have the functionality of toggling the visibility of the components UI.
Is this bad practice? Should we be conditionally rendering components instead?
The difference is that when using conditional rendering, the logic inside the conditionally rendered UI will not be executed if the condition fails.
But using the hidden attribute will execute the logic but only hides the UI.
Example:
import React from 'react';
const A = () => {
console.log('A rendrerd');
return <h1>A</h1>;
};
const B = ({ hidden }) => {
console.log('B rendrerd');
return <h1 hidden={hidden}>B</h1>;
};
const Test = () => {
return (
<div>
{false && <A />}
<B hidden={true} />
</div>
);
};
export default Test;
A will never call its console.log statement.
B is hidden but it will log B rendered.
I think this is worth mentioning. hidden attribute acts more or less like display: none with css. The truth is "the component will be rendered but only hidden from your display."
Just to illustrate, below is a photo of some html and their output. you realize that the output doesn't show the <p> that is decorated with hidden attribute but if when you inspect the rendered code, you realize that <p> was actually renderd. So you can image have multiple components in react where they will all be rendered but only displayed based on that hidden attribute.
Well I may not be sure of the performance involved but it's obvious that doign a simple if() to condition render a component will depending on the size of your components be much quicker than rendering everything and only relying on their hidden attribute decoration. And this also means that a user can just Inspect element and remove hidden attribute to display that component which is intended to be hidden.

React replace event.target with input

UPDATE
Here's are some demos
contentEditable demo - requires double click for H1 to become editable
replace with input demo - adopts event.target styles but makes the UI 'twitch' when rendered
So I have some functional components, let's say:
component1.js
import React from 'react';
const component1 = props => (
<div>
<h1>Title</h1>
</div>
);
export { component1 };
They are variable. event.target could be anything with text, so paragraph, heading, anything. I'm trying to let users edit content inline by clicking on it, so I'll pass a function editMode to these functional components, that'll update parent state with editing info, let's say like this:
<h1 onClick={event => {editMode(event, props.name, props.title, 'title')}}>title</h1>
This changes parent local state to have all the necessary information to grab the value from redux, define a target etc. For this example, props.name is the name of the component, props.title is the value, and 'title' is object key in redux.
So I'll add something to my component1.js and make it look a bit like this:
import React from 'react';
const component1 = props => (
<div>
{props.editState === 'true' &&
<EditLayout
name={props.name}
target={props.target}
value={props.value}
onChange={event => someFunc(event)}
/>
}
<h1>Title</h1>
</div>
);
export { component1 };
Now this works fine, except it doesn't scale. EditLayout, in this case, will just return an input with correct value. What I need it to do is to adapt to whatever is being clicked, get font size, background, padding, margin, position. Am I doing this right? Every way I try, I run into huge issues:
Idea 1 - move EditLayout component outside of the functional component
Issue: positioning
So I'll move EditLayout to parent component that contains both component1.js and EditLayout. This will allow me to manipulate it from inside the functional component, without having to include it everywhere. I'll then grab coordinates and other important information from event.target like so:
const coords = event.target.getBoundingClientRect();
const offsetX = coords.left;
const offsetY = coords.top;
const childHeight = coords.height;
const childWidth = coords.width;
const childClass = event.target.className;
I'll then wrap the EditLayout to return a container which contains an input, and apply size/coordinates to the absolutely positioned container. This'll present an issue of input being offset by a random amount of pixels, depending on how big/where is the event.target.
Idea 2 - pass relevant computed styles to EditLayout
Issue: twitching on render, and I have to add EditLayout for every possible event.target there is, as well as condition its' render
So I'll grab all important computed styles like this:
const computedTarget = window.getComputedStyle(event.target);
const childMargins = computedTarget.marginBottom;
const childPaddings = computedTarget.padding;
const childFontSize = computedTarget.fontSize;
const childTextAlign = computedTarget.textAlign;
And pass it to component1.js, and then pass it to EditLayout component inside the component1.js. I'll then condition theevent.target to hide if it's being edited like this:
<h1 className={ props.target === 'title' ? 'd-none' : ''}>Title</h1>
And condition the EditLayout to show only if it's needed:
{props.target === 'title' && <EditLayout />}
In this example, clicking h1 will show the input, but the layout itself with twitch on render. Input will have the exact same margin and font size as the h1, or event.target, but it'll appear bigger and extend the layout. Demo:
Idea 3 - Use conditional contentEditable
Issue: Requires double click to enable, doesn't work in safari, doesn't let me preselect the value
This is the weirdest of them all. I figured it'd be pretty simple, do something like this inside the functional component render:
<h1 contentEditable={props.target === 'title'} onClick={event => props.setTarget(event)}>Title</h1>
However, I have to double click to enable it. I have no idea why, if I attach a console log every time onClick is fired, I'll get correct outputs, I'll get the correct target value as well. I've tried numerous ways, but it simply requires double click. Even attempted to handle this inside the functional component, as most of the stuff is handled by a parent component, doesn't make a difference.
I have oversimplified the examples, so it's safe to assume/understand the following:
I am passing props in a correct fashion, they aren't undefined
I am using bootstrap
I am using styled components, and EditLayout is a styled component
which accepts props and turns them into CSS like: font-size: ${props
=> props.fontSize};
The values should be correct, I am not manipulating anything I get back from getComputedStyle() or getBoundingClientRect()
I am keen on keeping my functional components functional, and easy to
add. Functional components, in this case, are simple HTML structures,
and I'd like to keep them as simple as possible
So there's a neat solution to contentEditable requiring two clicks instead of one, instead of binding onClick and passing it to enable contentEditable, simply keep contentEditable true and handle the change however you like. Here's a working h1 that doesn't require two clicks to enable contentEditable, unlike the one in the demo
<h1
className="display-4 text-center"
contentEditable
suppressContentEditableWarning
onBlur={event => updateValues(event)}
>
Title
</h1>
The available methods for trigger update could be onBlur or onInput.

'this.$refs' is always empty in parent component

I'm trying to do a standard implementation of ref so that I can insert children elements into my InfoBox. But whatever I seem to put as a 'ref' element, never makes it to my InfoBox component. The result is always {} undefined from the log calls.
The click handler is to test timing issues, as using created vs mounted seemed to be a common issue.
<InfoBox
v-if="waitingForCode">
<p ref="infoboxcontent">A 7-digit verification code has been sent.</p>
</InfoBox>
and
<template>
<div
class="info-box"
#click="clicked" >
{{ this.$refs.infoboxcontent }}
</div>
</template>
<script>
export default {
name: 'InfoBox',
mounted() {
console.log(this.$refs, this.$refs.infoboxcontent)
},
methods: {
clicked() {
console.log(this.$refs, this.$refs.infoboxcontent)
}
}
}
</script>
<style scoped>
// some style
</style>
I'm starting to think I fundamentally misunderstand the usage of the 'ref' attribute since this seems like a trivial example. Any help would be greatly appreciated.
The ref Vue special attribute is used to refer a DOM node (or a child component) from your current component template.
If you want to pass some content to a custom component, this is the use case for a <slot> Vue built-in component.

Categories