Hide Svelte component from outside script - javascript

I am importing a third-party Svelte3 component with several children. For the sake of example, the third-party component looks like the following:
<script>
import Child1 from './third-party/Child1.svelte';
import Child2 from './third-party/Child2.svelte';
</script>
<div>
<Child1/>
<Child2/>
</div>
What would be the cleanest way to hide one of the children components from within my app (or from the dev console) given that:
it has no easily identifiable CSS selector, and
I cannot modify the third-party code?
In Vue, I would use the root instance, but is this possible in Svelte?
Thank you very much!
Guido

There is no clean way. Messing with the DOM from outside a component is a hacky solution and can lead to unpredictable results.
This is a feature the third party needs to add to their public api.
An option for the third party is to expose the feature is via props using $set using the component api:
// App.svelte
<script>
import Child1 from './third-party/Child1.svelte';
import Child2 from './third-party/Child2.svelte';
export let child2Visible = true;
</script>
<div>
<Child1/>
{#if child2Visible}
<Child2/>
{/if}
</div>
// my-app.js
import App from "./App.svelte";
window.app = new App({target});
// From dev console
app.$set({ child2Visible: false })
Another way for the third party is to expose a way for setting a store value which could toggle some of the ui.

Related

Executing Functions Between Two Child Components Svelte

I'm working on an Electron project using Svelte for the frontend. I'm relatively new to Svelte. So here is the problem, I have a parent component named MainContent.svelte and two child components editor.svelte and preview.svelte. The Editor and Preview are both placed in the MainContent component. What I want to do is when the content of the Editor is changed, I want to update the Preview pane to reflect those changes. I previously had done this same project using Vanilla JavaScript but thought of using Svelte as it was easier to manage the project.
There is function which listens for any changes in the Editor pane and another function to update the Preview pane. But what I can't wrap my head around is how to call the function to update the Preview when the Editor content changes. Any help would be greatly appreciated.
I would use a property and lift the state up to the main component, you can still use functions internally if you have to, e.g.
<script>
// ...
let content = '...';
</script>
<Editor bind:content />
<Preview {content} />
If the preview needs to update via a function you can call that in a reactive statement:
<script>
// ...
export let content;
$: updatePreview(content);
</script>

Change in HTML attribute of web component not reflected in Vue component

I'm facing the below problem.
I have a pure web component:
<my-web-comp options='["op1", "op2"]' active-option="op2"></my-web-comp>
This renders as two tabs with the second one selected by default. When you click on the other, the active-option HTML attribute changes to op1 and you can actually see that the property is changing in the DOM if you open the DevTools.
However, I cannot detect the change in the Vue component where I am using the web component. I have:
<template>
<div>
<my-web-comp :options="options" :active-option="activeOption"></my-web-comp>
</div>
</template>
<script>
export default {
name: 'MyVueComponent',
data() {
return {
options: '["op1", "op2"]',
activeOption: "op2"
}
},
computed: {
testVar() {
console.log("activeOption", this.activeOption) <--------- THIS LINE
},
}
}
</script>
The marked line only gets fired on the first load of the Vue component (printing "op2"). After that, testVar never gets modified again, doesn't mind if I click on the other tab and I don't see nothing in the console.
What can I be missing? I think it can be something related with Vue reactivity system, but can't wonder what.
This happens because your web-component mutates copy not a reference of this variable (copy created by your web component is also not reactive). There are two ways to change this:
You can modify your web component to use getters and setters to change value of this variable
You can use MutationObserver. To detect changes in your web-component. This approach will not require changes in this web-component
If you choose approach with MutationObserver then create this observer in vue mounted life-cycle-hook

Why does my react component keep re-rendering?

I'm trying to embed a tockify calender into a component, within a react project I'm building. I should note that I'm using a library called react-script-tag that allows me to use <script/> tags within my component.
Anyway, the calender is rendering - but then keeps on re-rendering as if it's stuck in some sort of loop. I have a feeling I need to implement some sort of lifecycle method. Any suggestions? Code as follows:
import React from 'react'
import Nav from './Nav'
import ScriptTag from 'react-script-tag'
class Events extends React.Component {
render() {
return (
<div>
<Nav/>
<div data-tockify-component="calendar" data-tockify-calendar="hzevents2"></div>
<ScriptTag isHydrating={false} data-cfasync="false" data-tockify-script="embed" src="https://public.tockify.com/browser/embed.js"></ScriptTag>
</div>
)
}
}
export default Events
Use React.PureComponent instead of React.Component because of React.PureComponent will prevent to re-rendering if there is no need or update.
The documentation of 'react-script-tag' says
It is recommended that the Script tag is placed in a component that
only renders once in the entire life of your app. Otherwise, a new
tag will be appended each time the component mounts again.
There are plans down the road to prevent this.
You may want to use a pure component to prevent rerendering.
Edit: Regardless of my answer you should show the rest of your code to be able to detect the problem.

Use React Portals within the React-App (not outside the DOM)

I want to implement modals within my React app. Portals seems to be quite nice for this but I don't want to change my outer HTML-structure.
The HTML-should still be:
<div id="app"></div>
I don't want to add an additional div to the HTML-structure.
In the App.js I tried to add the root-modal-container like this:
class App extends Component {
render() {
return (
<ResponsiveProvider>
<div id="root-modal"></div>
<Modal>
<div>Modal :-)</div>
</Modal>
</ResponsiveProvider>
);
}
}
But when trying to getElementById in the Modal-component I always get the error: appendChild on null...
The problem is, that the root-modal-div isn't renderend when initiating the Modal-component.
Any solutions how I can render Modals right in the level after from anywhere in my app?
You can see the not-running-code here. Uncomment the second line in HTML to get the code running.
You can do this with React portals, but it's a little awkward, and you have to get quite involved with the DOM itself to render across an app like this (rather than to totally external DOM nodes).
I've just released a library to fix this exact problem, since although it's possible with portals, it's not easy.
You can see the full details at https://github.com/httptoolkit/react-reverse-portal.
In your example, a solution would look something like:
import React from "react";
import ReactDOM from "react-dom";
import * as portals from "react-reverse-portal";
class App extends React.Component {
constructor(props) {
super(props);
// Create a portal node: the link between the two ends of the portal
this.modalNode = portals.createPortalNode();
}
render() {
// Place an OutPortal somewhere: this is where the content will appear.
// Could be many levels deep in your app - you just need to get the modalNode
// there. For complex cases you might want to put it in a context to distribute it.
return <div>
<Child modalNode={this.modalNode} />
<portals.OutPortal node={this.modalNode} />
</div>;
}
}
const Child = props => {
// Place an InPortal somewhere: this is where the content is defined
return <portals.InPortal node={props.modalNode}>
<Modal>
<div>Modal :-)</div>
</Modal>
</portals.InPortal>
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In the above: Child defines some modal content it wants to appear, and App defines the space where modal content should appear. You can use this to link parts of your app, and send rendered content between them. Hope that helps!

react js trigger function from any other component

I have a component that basically loads a video in an overlay using JW Player (simplified example below).
import React, { Component } from 'react'
import ReactJWPlayer from 'react-jw-player'
class VideoPopup extends Component {
render() {
return (
<div id="video">
<ReactJWPlayer
playerId='video-player'
playerScript='https://content.jwplatform.com/libraries/vr6ybmGf.js'
file='path to video file'
/>
</div>
)
}
}
export default VideoPopup;
I would like the component to sit directly in the root of my app, but I need to be able to display it when called from ANY other component - this might be a child component, a child of a child, a sibling etc. etc. I was hoping to be able to call it and pass the video file reference simply like below:
<button onClick={somehow show video popup}>show video popup</button>
I understand how to do this easily if there is a direct parent-child relationship, but not if I want to place the link in a variety of different components; I hope someone can point me in the right direction.
If you want to get rid of the parent-children relationships when it comes to actions, use an event manager like Redux (react-redux). It is pretty standard an becomes necessary as your app grows anyway.
The principle is that wherever you place your link, it will fire an "action" on click, which is sent to a global dispatcher that other components listen to for changes.
Multiple ways to do this
You can define a function that controls the show/hide functionality
of the video player in the app component itself and pass it down as a
prop to all the components where the event can be fired.
Use Redux. This is the ideal choice. You just have to dispatch an action from anywhere in your app and the corresponding reducer will take care of the functionality.
Using a global function (not recommended).
Please comment if you need more explanation.
You can try to make global function and call it wherever you want.
showVideoPopup () {
ReactDOM.render(
<VideoPopup />,
document.getElementById('popupHolder')
);
}

Categories

Resources