How to Dynamically add nodes in the graph using react-sigma - javascript

I'm new to Sigma and I'm trying to dynamically add a node to the graph but I'm receiving an error
react-dom.development.js:22839 Uncaught Error: Sigma: invalid graph instance.
The above error occurred in the <ForwardRef> component:
I want to create a simple react Sigma application where users can dynamically add nodes to the graph. In Chrome, I'm able to successfully add a single node, but the moment I update my input tag it breaks and prints the error messages shown above.
here below is my code
import React, { useState } from "react";
import Graph from "graphology";
import { SigmaContainer } from "#react-sigma/core";
const GraphPage = () => {
const [graph, setGraph] = useState(new Graph());
var i = 0;
const [node, setNode] = useState("");
const addNode = (e) => {
e.preventDefault();
console.log(
"just want to see what the e.target.node_name.value is : " +
e.target.node_name.value
);
const nodeName = e.target.node_name.value;
setGraph(
graph.addNode(nodeName, {
x: i,
y: i,
label: nodeName,
size: 15,
color: "#FA4F40",
})
);
setGraph(
graph.addNode(nodeName + i, {
x: i + 1,
y: i + 1,
label: nodeName + i,
size: 15,
color: "#FA4F40",
})
);
setGraph(
graph.addEdge(nodeName, nodeName + i, {
type: "arrow",
label: "works with",
size: 5,
})
);
i++;
};
return (
<div>
<h1>Graph</h1>
<h2>Add Node</h2>
<form onSubmit={addNode}>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium text-gray-900 "
>
Node name
</label>
<input
type="text"
id="node_name"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
// value={name}
// onChange={handleChange}
required
/>
<button className="btn-start px-8">Submit</button>
</form>
<SigmaContainer
style={{ height: "1000px" }}
graph={graph}
></SigmaContainer>
</div>
);
};
export default GraphPage;
My guess is somewhere sigma doesn't like to rerender the graph. Not sure how to go about it, and I don't see any examples of anyone dynamically adding nodes. Any help would be appreciated. Thank you

Related

Deleting single value from Pinia reactive data store

I am creating a multistep register form in which I provide an avatar upload. Because it is a multistep form, I want to store the data in a Pinia store until the form finally gets submitted. Everything works fine so far. But I want to be able to delete the value that contains the Blob URL for the avatar, so the user can choose a different image. What I am trying to do is this userRegisterStore.cardOwner.avatar = '' cause the initial state of that value is just an empty string. But I get this error message:
runtime-core.esm-bundler.js:218 Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'avatar'
I also use cropperjs and vue-cropperjs. But I think that's irrelevant in this case.
I Googled all day and found nothing. So, I hope someone here can help.
[EDIT]
I created a codesandbox.io I hope it works. The first file you should see is RegisterFormFive.vue. To view it, you need to go to this link or use the integrated preview in codesandbox: https://n9dfv3-5173.preview.csb.app/register. Then upload an image, crop it (orange button beneath the image), and then try to delete it (red button)
Here's my code:
// RegisterDataStore.js
export const useRegisterDataStore = defineStore('RegisterDataStore', {
state: () => ({
imgReady: false,
cardOwner: reactive({
firstName: '',
lastName: '',
email: '',
password: '',
agbAccepted: false,
dsgvoAccepted: false,
title: '',
companyName: '',
companyPublic: false,
position: '',
positionPublic: false,
avatar: '',
addresses: [],
contacts: [],
links: [],
}),
}),
// Cropper part
<Cropper
v-if="registerDataStore.cardOwner.avatar && !registerDataStore.imgReady"
class="mx-auto max-h-[350px] max-w-[350px] overflow-hidden rounded-lg border-2 border-skin-primary bg-skin-primary"
ref="cropper"
alt="User avatar"
drag-mode="move"
:src="registerDataStore.cardOwner.avatar"
:aspect-ratio="1 / 1"
:crop-box-movable="false"
:crop-box-resizable="false"
:auto-crop-area="0.6"
:guides="false"
:movable="true"
:scalable="true"
:zoomable="true"
:zoo-on-touch="true"
:max-canvas-width="350"
:max-canvas-height="350"
:zoom-on-wheel="true"
:rotate-on-drag="false"
:rotatable="false"
:background="false"
:modal="true"
:initial-aspect-ration="1 / 1"
:view-mode="1"
></Cropper>
// Conponent script
<script setup>
import HeaderNav from '#/components/HeaderNav.vue'
import HeaderTitle from '#/components/HeaderTitle.vue'
import { useRegisterDataStore } from '#/stores/RegisterDataStore'
import Cropper from 'vue-cropperjs'
import 'cropperjs/dist/cropper.css'
import { ref } from 'vue'
import { useObjectUrl } from '#vueuse/core'
name: 'RegisterFormFive'
const registerDataStore = useRegisterDataStore()
const avatarInput = ref(null)
const cropper = ref(null)
const fileChanged = (event) => {
const file = event.target.files[0] || e.dataTrtansfer.files[0]
const reader = new FileReader()
reader.onload = (e) => {
registerDataStore.cardOwner.avatar = e.target.result
}
reader.readAsDataURL(file)
}
const deleteAvatar = (event) => {
registerDataStore.cardOwner.avatar = null
registerDataStore.imgReady = false
}
</script>
// The button that tiggers the storage
<div class="mt-4 flex justify-center">
<button
v-if="!registerDataStore.imgReady"
#click.prevent="
cropper.getCroppedCanvas().toBlob((blob) => {
registerDataStore.cardOwner.avatar = useObjectUrl(blob)
registerDataStore.imgReady = true
})
"
type="button"
class="hover:bg-skin-primary-dark inline-flex items-center rounded-md border border-transparent bg-skin-primary px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-skin-primary focus:ring-offset-2"
>
// The file input field
<input
type="file"
ref="avatarInput"
accept=".jpg,.png"
#change="fileChanged"
:style="{ display: 'none' }"
/>
// The button that should "delete" the value
<button
v-if="registerDataStore.imgReady"
#click.prevent="deleteAvatar"
type="button"
class="hover:bg-skin-primary-dark inline-flex items-center rounded-md border border-transparent bg-red-700 px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-skin-primary focus:ring-offset-2"
>
<IconWarning
class="mr-2 h-5 w-5 fill-current text-skin-primary"
aria-hidden="true"
/>
Bild löschen
</button>
Nested reactive isn't needed in state, Pinia state is already reactive. #click.prevent handler doesn't need to be created in a template, it doesn't affect how it works but makes debugging harder.
VueUse useObjectUrl composable is the problem. Due to how Vue reactive API works, refs are unwrapped inside reactive object. Since useObjectUrl returns readonly ref, it makes cardOwner.avatar property readonly and prevents from reassigning a value. Changing it would require the whole object to be reassigned:
registerDataStore.cardOwner = { ...registerDataStore.cardOwner, avatar: ... }
The actual problem is that useObjectUrl is misused. Since blob value doesn't change in the scope of then function, it can't benefit from being reactive. The composable should be replaced with the actual thing that it does:
registerDataStore.cardOwner.avatar = URL.createObjectURL(newObject)

Need help making a switch selector compnent in reactJS similar to that on https://www.themoviedb.org/

So I'm trying to make a dynamic reusable switch selector component exactly like that on https://www.themoviedb.org/ to select between a number of options such as ["a", "b", "c"].
I've got most of the logic down, it might be a bit messy now, but my problem really is that I can't seem to figure out at which width or distance to move the coloured div to accurately place it right on top of the label/option title.
This is what I've got so far, the text colour changes correctly when selected, and the transaction is also smooth, but the position is wrong.
type SwitchProps = {
optionTitles: string[];
};
type Selector = {
isToggled: boolean;
optionTitle: string;
width: number | undefined;
};
const Switch: FC<SwitchProps> = (props) => {
const [selectors, setSelectors] = useState<Selector[]>([]);
const [currentToggled, setCurrentToggled] = useState<Selector & { index: number }>({
index: 0,
isToggled: true,
optionTitle: props.optionTitles[0],
width: 110,
});
const elementsRef = useRef<RefObject<HTMLDivElement>[]>(props.optionTitles.map(() => createRef()));
useLayoutEffect(() => {
if (selectors.length >= props.optionTitles.length) {
return;
}
props.optionTitles.map((optionName, index) => {
setSelectors((prevState) => [
...prevState,
{
isToggled: index === 0 ? true : false,
optionTitle: optionName,
width: 110,
},
]);
});
}, []);
const handlerToggleClick = (sectorIndex: number, toggleState: boolean) => {
let data = selectors;
data.forEach((selector, index) => {
selector.isToggled = false;
selector.width = elementsRef.current[sectorIndex].current?.offsetWidth;
});
data[sectorIndex].isToggled = true;
setCurrentToggled({ index: sectorIndex, ...data[sectorIndex] });
setSelectors(data);
};
return (
<div className="relative z-[1] h-8 border border-solid border-tmdbDarkBlue rounded-[30px] font-medium flex items-center">
{selectors.map((sector, index) => (
<div
key={index}
ref={elementsRef.current[index]}
className={`py-1 px-5 h-8 text-sm font-semibold flex items-center ${
sector.isToggled && "switch-active-text"
}`}
>
<span
className="cursor-pointer flex items-center"
onClick={() => handlerToggleClick(index, !sector.isToggled)}
>
{sector.optionTitle}
</span>
</div>
))}
<div
className={`absolute z-[-1] h-8 w-20 bg-tmdbDarkBlue rounded-[30px] transition-all duration-150 ease-in`}
style={{
width: currentToggled.width,
left: currentToggled.index === 0 ? 0 : (currentToggled.width as number) * 1.8,
}}
></div>
</div>
);
};
export default Switch;
If there are other ways to improve my code, please do let me know. I'm trying to get better at things, which is why I'm working on this clone sorta project.

Dynamically create object literals based on prop title and color

I am working on my Next.js website. Using Tailwind CSS I have managed to change the color dynamically before returning a component with static names & colors.
Now I am fetching data from the API and the title + color needs to be dynamically created with the values from the API.
the title prop equals the hardcoded project (1,2,3) name. The color should be color prop.
Is there an intelligent way to create an object literal with the dynamic data?
Hardcoded values work just perfectly fine.
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
import { projectsArrayProps } from '#typings/propTypes';
const Projects = ({ allProjects }) => {
console.log(allProjects);
return (
<div className="card--grid grid grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[100px] sm:auto-rows-[120px] md:auto-rows-[200px]">
{allProjects.map(
({ id, logo, title, color }: projectsArrayProps, index) => {
// const projectColor: { [key: string]: string } = {
// project1: 'bg-[#fbc340]/[0.07]',
// project2: 'bg-[#70d1db]/[0.07]',
// project3: 'bg-[#ea5b52]/[0.07]',
// project4: 'bg-[#ff1e00]/[0.07]',
// project5: 'bg-[#ff99f3]/[0.07]',
// };
const projectColor: { [key: string]: string } = {};
return (
<motion.div
key={id}
className={`rounded-md bg-[${projectColor['title']}]`}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{
duration: 0.1,
stiffness: 200,
delay: index * 0.085,
type: 'spring'
}}
variants={{
hidden: { opacity: 0, scale: 0.6 },
visible: { opacity: 1, scale: 1 }
}}
>
<Link href="">
<a
target="_blank"
className="flex flex-col items-center justify-center w-full h-full"
>
<div className="flex flex-col items-center justify-center relative w-full h-full max-w-[64px] md:max-w-[100px] lg:max-w-[120px]">
<img
className="object-contain"
src={logo.url}
alt={logo.alt}
/>
</div>
</a>
</Link>
</motion.div>
);
}
)}
</div>
);
};
export default Projects;
Tailwind extracts classes at build time, so you cannot create dynamic classes at runtime. You could safelist some classes to make sure they are created even when they are not present in the code at build time (https://tailwindcss.com/docs/content-configuration#safelisting-classes). Unfortunately this only helps you if you know all possible colors that can be returned from the API.

Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering

I am making a website using Next.js and the above error is shown every time.
Don't know what is wrong in my code.
const Login = () => {
const [userMsg, setUserMsg] = useState("");
const [email, setEmail] = useState("");
const router=useRouter();
const handleOnChangeEmail = (e) => {
e.preventDefault();
setUserMsg("");
console.log("event", e);
const email = e.target.value;
setEmail(email);
};
const handleLoginWithEmail = (e) => {
e.preventDefault();
if (email) {
if (IsEmail.validate(email)){
router.push("/")
}else{
setUserMsg("Enter a valid email address")
}
} else {
//show usermssg
setUserMsg("Enter an email address");
}
};
return (
<div className="bg-[url('/static/bglg.jpg')] flex items-stretch flex-col h-screen w-full">
<head>
<title>NeoVest SignIn</title>
</head>
<header className="text-4xl px-10 py-2 font-black">
<span className="text-indigo-700">NeoVest</span>
</header>
<div className="w-full max-w-xs m-auto bg-[#C9C9C9] rounded p-5 bg-opacity-50 border-gray-200">
<header>
<div className="text-indigo-700 font-black text-3xl py-2">
<p>Sign In</p>
</div>
</header>
<form className="py-5">
<div>
<label className="block mb-2 text-indigo-500" for="username">
Email
</label>
<input
className="w-full p-2 mb-6 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
type="text"
name="username"
placeholder="Email Address"
onChange={handleOnChangeEmail}
/>
<div className="block mb-2 text-red-700">
<p>{userMsg}</p>
</div>
</div>
<div>
<input
className="w-full bg-indigo-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
type="button"
value="Submit"
onClick={handleLoginWithEmail}
/>
</div>
</form>
</div>
</div>
);
};
Another error shown is due to some suspense boundary causing root to switch to client side rendering :
Error: There was an error while hydrating. Because the error happened
outside of a Suspense boundary, the entire root will switch to client
rendering.
I am also using Tailwind if that information is important.
If you would look at the console you would see a warning.
Warning: validateDOMNesting(...): <head> cannot appear as a child of <div>
So, to fix this you just have to move head out of the div and move it to a different higher component.
if you have this Warning in chrome:
> `validateDOMNesting(...): <head> cannot appear as a child of <div>`
Your DOM Tree is not printed in the browser correctly, one or more tags are not closed properly.
The problem is this:
<head>
<title>NeoVest SignIn</title>
</head>
To solve it, first import Head, and then, use it with Capital "H"
import Head from "next/head"
<Head>
<title>NeoVest SignIn</title>
</Head>
I verified some topics in my code. First, verified if in next.config.js was styled components declared.
reactStrictMode: true,
compiler: {
styledComponents: true,
},
In second, I checked if file babel ".babelrc" was created in root folder with this content:
{
"presets": ["next/babel"],
"plugins": ["styled-components"]
}
The error yet showed. Component by component I checked and more two errors was found. First, HTML errors like this:
<!-- wrong -->
<p>
<ul></ul>
</p>
<!-- right -->
<p></p>
<ul></ul>
<p></p>
<!-- or right -->
<div>
<ul></ul>
</div>
Finally, I found an error in initializing component. In my case, I declared - incorrectly - a const items (object). The right way to execute this is using useState.
<!-- WRONG WAY THAT I WAS DID -->
const Works = () => {
const items = [
{
id: 1,
name: 'any name',
src: 'srcpath'
},
{
id: 2,
name: 'any name 2',
src: 'srcpath'
},
{
id: 3,
name: 'any name 3',
src: 'srcpath'
},
];
return(
items.map((item, index) => {
return(
<div key={index}>{item.name}</div>
)
}
)
});
<!-- RIGHT WAY THAT I FIXED -->
const Works = () => {
const [ item, setItem ] = React.useState();
React.useEffect(() => {
setItem([
{
id: 1,
name: 'any name',
src: 'srcpath'
},
{
id: 2,
name: 'any name 2',
src: 'srcpath'
},
{
id: 3,
name: 'any name 3',
src: 'srcpath'
},
]);
}, []);
if(item)
return(
items.map((item, index) => {
return(
<div key={index}>{item.name}</div>
)
}
)
});
Time and randomness are two of the things that most commonly produce
this
as discussed in Hydration errors - Text content does not match server-rendered HTML. #38263
Possible solution might be to use useEffect hook
// Example component with an error
import React from "react";
const AllNames = ["Ali", "Elisa", "Bella", "Carmen"];
const RandomUniqueNames = Array.from({ length: 4 }).map((_, index) => {
let randomIndex = Math.floor(Math.random() * AllNames.length);
console.log(randomIndex);
let name = AllNames[randomIndex];
return {
name,
};
});
export default function ModernNames() {
return (
<div>
<h1>ModernNames</h1>
{RandomUniqueNames.map((name) => (
<p>{name.name}</p>
))}
</div>
);
}
// Example solution
import React, { useState, useEffect } from "react";
const AllNames = ["Ali", "Elisa", "Bella", "Carmen"];
export default function ModernNames() {
const [randomNames, setRandomNames] = useState([]);
useEffect(() => {
const RandomUniqueNames = Array.from({ length: 4 }).map((_, index) => {
let randomIndex = Math.floor(Math.random() * AllNames.length);
console.log(randomIndex);
let name = AllNames[randomIndex];
return {
name,
};
});
setRandomNames(RandomUniqueNames);
}, []);
return (
<div>
<h1>ModernNames</h1>
{randomNames.map((name, index) => (
<p key={index}>{name.name}</p>
))}
</div>
);
}
You can try downgrading react version. Try react#17.

Hide transparent image from download while using React Konva

I want to download only the papayawhip square box without the transparent image behind it.
I am using React Konva. I have the following code:
import * as React from "react"
import { Stage, Layer, Rect } from "react-konva"
import type { Stage as StageType } from "konva/types/Stage"
import { observer } from "mobx-react"
import "./styles.css"
import { useStore } from "./context"
const downloadURI = (uri: string | undefined, name: string | undefined) => {
const link = document.createElement("a")
link.download = name as string
link.href = uri as string
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
function App() {
const [fillColor, setFillColor] = React.useState("")
const [downloadClicked, setDownloadClicked] = React.useState(false)
const stageRef = React.useRef<StageType>(null)
const transparentBackground = new window.Image()
transparentBackground.src =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABlBMVEUAAADY2NjnFMi2AAAAAXRSTlMAQObYZgAAABVJREFUGNNjYIQDBgQY0oLDxBsIQQCltADJNa/7sQAAAABJRU5ErkJggg=="
const store = useStore()
const { win, canvas, browser, pad } = store
return (
<div className="flex">
<div id="sidebar">
<h1 className="text">
Center <span>papayawhip</span> Canvas
</h1>
<input
type="text"
placeholder="Enter Fill Color"
value={fillColor}
onChange={(e) => setFillColor(e.target.value)}
/>
<button
className="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick={() => {
setDownloadClicked(true)
const options = { mimeType: `image/png`, quality: 1, pixelRatio: 1 }
const img = stageRef.current?.getStage().toDataURL(options)
downloadURI(img, "download.png")
setDownloadClicked(false)
}}
>
Download Image
</button>
</div>
<Stage
ref={stageRef}
width={canvas.width}
height={canvas.height}
id="konva"
>
<Layer>
{fillColor === "" && downloadClicked && (
<Rect
width={browser.width + 200}
height={browser.height + 200}
x={pad / 2}
y={(win.height - browser.height) / 2}
fillPatternImage={transparentBackground}
fill={fillColor}
/>
)}
<Rect
width={browser.width}
height={browser.height}
x={pad / 2}
y={(win.height - browser.height) / 2}
fill="papayawhip"
/>
</Layer>
</Stage>
</div>
)
}
export default observer(App)
Codesandbox → https://codesandbox.io/s/add-padding-to-centered-canvas-with-sidebar-gqhhl?file=/src/App.tsx
How do I download only the papayawhip rectangle part?
In principle,
hide any shapes you don't want included in the image,
Get the image
unhide what you hid in 1.
Note: do not draw the layer/stage between 1 and 3 or you will see an unwanted flicker.
You can pass x, y, width and height attributes to stage.toDataURL(options) function to capture only a part of the screen:
const options = {
mimeType: `image/png`,
quality: 1,
pixelRatio: 1,
width: browser.width,
height: browser.height,
x: pad / 2,
y: (win.height - browser.height) / 2
};
const img = stageRef.current?.getStage().toDataURL(options);
https://codesandbox.io/s/react-konva-export-part-of-the-stage-hf5sn
I solved it with the help of Anton. Vanquished Wombat's approach was the same as well.
const backgroundSelector = React.useRef(null)
/* gave a `name` to `transparentBackground` rectangle */
<Rect
name="transparentBackground"
/>
/* then searched for it & hid it */
stageRef.current?.findOne(".transparentBackground").hide()
/* download the image */
downloadURI(img, "download.png")
/* show it again */
stageRef.current?.findOne(".transparentBackground").show()
Updated Codesandbox → https://codesandbox.io/s/add-padding-to-centered-canvas-with-sidebar-gqhhl?file=/src/App.tsx

Categories