How to add mouse events to each mesh part loaded from Rhino3dm file in React-Three-Fiber? - reactjs

I want to realize a function that changes color when the mouse is over each mesh part loaded from an Rhino3dm file with react-three-fiber.But in the code below mouse events are not working properly for some reason.
For GLTF, using Object.values(gltf.nodes).map worked fine.
enter image description here
For Rhino3dm, I use object.children.map and I think this is bad.
Do you know the cause and solution?
This is the sandbox URL.
https://codesandbox.io/s/test-rhino3dmloader-wnrty6?file=/src/App.js
import "./styles.css";
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { Canvas } from "#react-three/fiber";
import { useLoader } from "#react-three/fiber";
import { Rhino3dmLoader } from "three/examples/jsm/loaders/3DMLoader";
import { Environment, OrbitControls} from "#react-three/drei";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { Suspense } from "react";
const Part = (child) => {
const objRef = useRef();
const [hovered, setHover] = useState(false);
const material = new THREE.MeshStandardMaterial({
color: hovered ? "hotpink" : "orange"
});
return (
<mesh
key={child.id}
ref={objRef}
castShadow
receiveShadow
onPointerOver={(e) => {
e.stopPropagation();
setHover(true);
}}
onPointerOut={(e) => {
e.stopPropagation();
setHover(false);
}}
>
<primitive object={child} material={material} />
</mesh>
);
};
const Model = () => {
// For some reason mouse events don't work well in Rhino3dm.
const object = useLoader(Rhino3dmLoader, "./rhino_logo.3dm", (loader) => {
loader.setLibraryPath("https://cdn.jsdelivr.net/npm/rhino3dm#0.15.0-beta/");
});
const model = object.children.map((child) => Part(child));
// For gltf the below code worked fine.
/* 
const gltf = useLoader(GLTFLoader, "./rhino_logo.glb");
const model = Object.values(gltf.nodes).map((child) => {
if(child.isMesh){
return Part(child)
}
});
*/
return model;
};
export default function App() {
return (
<div className="App">
<Canvas>
<Suspense fallback={null}>
<Model />
<OrbitControls />
<Environment preset="sunset" background />
</Suspense>
</Canvas>
</div>
);
}

I was able to solve it below! Thank you!!
https://discourse.threejs.org/t/how-to-add-mouse-events-to-each-mesh-part-loaded-from-rhino3dm-file-in-react-three-fiber/48191/2

Related

useRef() returning 'undefined' when used with custom hook on initial render when using filter()

I have an image slider component and a simple custom hook that gets the refElement and the width of the element using the useRef hook. -
The code sandbox is here Image Slider
When I use the slider component and just map the data in without filtering, everything works fine. If I filter and map the data then I get Uncaught TypeError: elementRef.current is undefined . (In the sandbox you have to comment out the second instance (unfiltered) of SliderTwo to recreate the error. Why does it work without the filter but not with (when rendered by itself)? More in depth explanation below.
useSizeElement()
import { useState, useRef, useEffect } from 'react';
const useSizeElement = () => {
const [width, setWidth] = useState(0);
const elementRef = useRef();
useEffect(() => {
setWidth(elementRef.current.clientWidth); // This will give us the width of the element
}, [elementRef.current]);
return { width, elementRef };
};
export default useSizeElement;
I call the hook (useSizeElement) inside of a context because I need the width to use in another hook in a different component thus:
context
import React, { createContext, useState, useEffect} from 'react';
import useSizeElement from '../components/flix-slider/useSizeElement';
export const SliderContext = createContext();
export const SliderProvider = ({children}) => {
const { width, elementRef } = useSizeElement();
const [currentSlide, setCurrentSlide] = useState();
const [isOpen, setIsOpen] = useState(false)
console.log('context - width', width, 'elementRef', elementRef)
const showDetailsHandler = movie => {
setCurrentSlide(movie);
setIsOpen(true)
};
const closeDetailsHandler = () => {
setCurrentSlide(null);
setIsOpen(false)
};
const value = {
onShowDetails: showDetailsHandler,
onHideDetails: closeDetailsHandler,
elementRef,
currentSlide,
width,
isOpen
};
return <SliderContext.Provider value={value}>{children}</SliderContext.Provider>
}
I get the width of the component from the elementRef that was passed from the context.-
Item Component
import React, { Fragment, useContext } from 'react';
import { SliderContext } from '../../store/SliderContext.context';
import ShowDetailsButton from './ShowDetailsButton';
import Mark from './Mark';
import { ItemContainer } from './item.styles';
const Item = ({ show }) => {
const { onShowDetails, currentSlide, isOpen, elementRef } =
useContext(SliderContext);
const isActive = currentSlide && currentSlide.id === show.id;
return (
<Fragment>
<ItemContainer
className={isOpen ? 'open' : null}
ref={elementRef}
isActive={isActive}
isOpen={isOpen}
>
<img
src={show.thumbnail.regular.medium}
alt={`Show title: ${show.title}`}
/>
<ShowDetailsButton onClick={() => onShowDetails(show)} />
</ItemContainer>
</Fragment>
);
};
export default Item;
The width is passed using context where another hook is called in the Slider Component:
Slide Component
import useSizeElement from './useSizeElement';
import { OuterContainer } from './SliderTwo.styles';
const SliderTwo = ({ children }) => {
const {currentSlide, onHideDetails, isOpen, width, elementRef } = useContext(SliderContext);
const { handlePrev, handleNext, slideProps, containerRef, hasNext, hasPrev } =
useSliding( width, React.Children.count(children));
return (
<Fragment>
<SliderWrapper>
<OuterContainer isOpen={isOpen}>
<div ref={containerRef} {...slideProps}>
{children}
</div>
</OuterContainer>
{hasPrev && <SlideButton showLeft={hasPrev} onClick={handlePrev} type="prev" />}
{hasNext && <SlideButton showRight={hasNext} onClick={handleNext} type="next" />}
</SliderWrapper>
{currentSlide && <Content show={currentSlide} onClose={onHideDetails} />}
</Fragment>
);
};
export default SliderTwo;
Now everything works fine if I just map the data with no filters into the slider as shown in the sandbox. But if I apply a filter to display only what I want I get -
Uncaught TypeError: elementRef.current is undefined
I do know that you can't create a ref on an element that does not yet exist and I've seen examples where you can use useEffect to get around it but I can't find the solution to get it to work.
Here is the App.js - To see the error I'm getting, comment out the second instance of . As long as I'm running one instance without filtering the data, it works, but it won't work by itself.
import { useState, useEffect, Fragment } from "react";
import SliderTwo from "./components/SliderTwo";
import Item from "./components/Item";
import shows from "./data.json";
import "./App.css";
function App() {
const [data, setData] = useState(null);
const datafunc = () => {
let filteredData = shows.filter((show) => {
if (show.isTrending === true) {
return show;
}
});
setData(filteredData);
};
useEffect(() => {
datafunc();
}, []);
console.log("Trending movies", data);
return (
<Fragment>
<div className="testDiv">
{shows && data && (
<SliderTwo>
{data && data.map((show) => <Item show={show} key={show.id} />)}
</SliderTwo>
)}
</div>
<div className="testDiv">
<SliderTwo>
{shows.map((show) => (
<Item show={show} key={show.id} />
))}
</SliderTwo>
</div>
</Fragment>
);
}
export default App;
Full code: Sandbox - https://codesandbox.io/s/twilight-sound-xqglgk
I think it may be an issue when the useSizeElement is first mounted as the useEffect will run once at the beginning of each render.
When it runs at the first instance and the ref is not yet defined so it was returning the error: Cannot read properties of undefined (reading 'clientWidth')
If you modify your code to this I believe it should work:
import { useState, useRef, useEffect } from "react";
const useSizeElement = () => {
const [width, setWidth] = useState(0);
const elementRef = useRef();
useEffect(() => {
if (elementRef.current) setWidth(elementRef.current.clientWidth); //
This will give us the width of the element
}, [elementRef]);
return { width, elementRef };
};
export default useSizeElement;
This way you are checking if the elementRef is defined first before setting the width
UPDATE:
<Fragment>
<div className="testDiv">
<SliderTwo>
{shows
.filter((show) => {
if (show.isTrending === true) {
return show;
}
return false;
})
.map((show) => (
<Item show={show} key={show.id} />
))}
</SliderTwo>
</div>
{/* <div className="testDiv">
<SliderTwo>
{shows.map((show) => (
<Item show={show} key={show.id} />
))}
</SliderTwo>
</div> */}
</Fragment>

Recharts slow rendering issue

import React, { useState, useEffect } from "react";
import './App.css';
import {
Line,
LineChart,
XAxis,
YAxis,
} from 'recharts';
import socketIOClient from "socket.io-client";
const ENDPOINT = "http://localhost:4001";
function App() {
const [data, updateData] = useState([]);
const socket = socketIOClient(ENDPOINT, {
transports: ['websocket', 'polling']
});
useEffect(() => {
socket.on("a", a => {
updateData(currentData => [...currentData, a])
});
}, []);
if(data.length>200)
{
updateData(data.shift())
}
return (
<div className="App">
<h1>size of data is :- {data.length}</h1>
<LineChart width={1000} height={500} data={data}>
<XAxis dataKey="name" />
<YAxis />
<Line dataKey="value" />
</LineChart>
</div>
);
}
export default App;
This is my code I am getting maximum of 200 data per second from socket.io .Code is working fine but as some time passes it start lagging , graph rendering becomes slow .
can any one please tell how can I fix it or optimize it .
There is an anti pattern here, you are calling data.shift() which directly modifies the state, which is a no no.
The other issue is your length check is outside the useEffect.
Instead, use Array.slice() like this:
import React, { useState, useEffect, useMemo } from 'react';
import './App.css';
import { Line, LineChart, XAxis, YAxis } from 'recharts';
import socketIOClient from 'socket.io-client';
const ENDPOINT = 'http://localhost:4001';
function App() {
const [data, updateData] = useState([]);
const socket = socketIOClient(ENDPOINT, {
transports: ['websocket', 'polling'],
});
useEffect(() => {
socket.on('a', (a) => {
updateData((currentData) => {
const a = [...currentData, a];
return a.length <= 200 ? a : a.splice(1);
});
});
}, []);
const graph = useMemo(() => {
return (
<LineChart width={1000} height={500} data={data}>
<XAxis dataKey='name' />
<YAxis />
<Line dataKey='value' />
</LineChart>
)
}, [data]);
return (
<div className='App'>
<h1>size of data is :- {data.length}</h1>
{graph}
</div>
);
}
export default App;

Unable to display an API data using map function on Material UI tabs

I'm new to this programming world. Can anyone please help me on this.
I have implemented Material UI's tabs successfully by hard-coding the content, but when I tried to make my hard coded tabs with a .map function to populate the content from a data source (json), it no longer works. The tab displays nothing.
Here are the codes,
Planet component:
import React from 'react';
function Planet(props) {
return (
<ul>
<li>{props.name}</li>
</ul>
);
}
export default Planet;
Planets component:
import React, { useEffect, useState} from 'react';
import Planet from './Planet';
function Planets(props) {
const [planets, setPlanets] = useState([]);
useEffect(() => {
getPlanets();
}, []);
const getPlanets = async () => {
const response = await fetch("https://assignment-machstatz.herokuapp.com/planet");
const data = await response.json();
setPlanets(data);
}
return (
<div>
{planets.map((planet, index) => {
return (
<Planet key={index} name={planet.name} />
);
})}
</div>
);
}
export default Planets;
App component:
import React, { useState } from 'react';
import { AppBar, Tabs, Tab } from '#material-ui/core';
import Planet from './Planet';
import Favplanets from './Favplanets';
function App() {
const [selectedTab, setSelectedTab] = useState(0);
function handleChange (event, newValue) {
setSelectedTab(newValue);
}
return (
<>
<AppBar position="static">
<Tabs value={selectedTab} onChange={handleChange} >
<Tab label="Planets" />
<Tab label="Favourite Planets" />
</Tabs>
</AppBar>
{selectedTab === 0 && <Planet />}
{selectedTab === 1 && <Favplanets />}
</>
);
}
export default App;
Thanks for your help!

Disable effect via props react-three-fiber

I have scene created with react-three-fiber which has an EffectsComposer with a Glitch effect using react-postprocessing npm package.
I would like to toggle the active state of the Glitch effect using an html input checkbox, however, toggling the state of the active prop does not disable the effect. What's the correct way to disable effects on checkbox input?
I've set up a sandbox with the issue here
https://codesandbox.io/s/tender-star-lw07h?file=/src/App.tsx:0-1488
Here's the code.
import React, {
Suspense,
useRef,
useEffect,
useState,
createRef,
Fragment
} from "react";
import Outline from "./components/Outline";
import { EffectComposer, Glitch, Noise } from "react-postprocessing";
import { Canvas } from "react-three-fiber";
import { OrbitControls } from "drei";
import { Mesh } from "three";
import { GlitchMode } from "postprocessing";
const Box = (props: any) => (
<mesh {...props}>
<meshBasicMaterial attach="material" color="red" />
<boxGeometry args={[1, 1, 1]} attach="geometry" />
</mesh>
);
const Composer = ({ active }: any) => {
const ref = React.useRef();
return (
<EffectComposer>
<Glitch ref={ref} active={active} mode={GlitchMode.CONSTANT_WILD} />
</EffectComposer>
);
};
const App = () => {
const ref = useRef();
const [selection, select] = useState<Mesh>();
const [active, activate] = useState<boolean>(true);
useEffect(() => {
if (selection) {
console.log(ref.current);
}
}, [selection]);
return (
<Fragment>
<div style={{ background: "#fff" }}>
<label htmlFor="">Active {JSON.stringify(active)}</label>
<input
type="checkbox"
defaultChecked={active}
onChange={($event) => {
activate($event.target.checked);
}}
/>
</div>
<Canvas>
<Box onClick={(mesh) => select(mesh)} />
<Composer active={active} />
</Canvas>
</Fragment>
);
};
Just do
const App = () => {
...
<Canvas>
<Box .../>
{active && <Composer/>}
</Canvas>
and
const Composer = () => {
return (
<EffectComposer>
<Glitch active={true} mode={GlitchMode.CONSTANT_WILD} />
</EffectComposer>
);
};

My state changes, but does not add class when useEffect, when I scroll

I need to change the background of a JSX element when the page goes down by 320 px, all with useEffect and useState. So far I managed to change the state, but does not add background class of another color.
I am using NODE 8.9.3, NPM 5.5.1 and REACT JS 16.9.0
import React, { useEffect, useState } from 'react'
import { useScrollYPosition } from 'react-use-scroll-position'
import { Container } from '../../styles/Container'
import { ContainerCustom, HeaderComp } from './styles'
import Logo from './Logo'
import Menu from './Menu'
import Icons from './Icons'
const ContainerBox = () => {
return (
<ContainerCustom fluid>
<Container>
<HeaderComp>
<Logo />
<Menu />
<Icons />
</HeaderComp>
</Container>
</ContainerCustom>
)
}
const Header = () => {
const [back, setBack] = useState(0)
const handleBackState = () => {
const scrollY = window.scrollY
if (scrollY > 320) {
setBack(1)
console.log(`Estado: ${back}`)
} else {
setBack(0)
console.log(`Estado após remover: ${back}`)
}
}
useEffect(() => {
window.addEventListener('scroll', handleBackState)
return () => {
window.removeEventListener('scroll', handleBackState)
}
}, [handleBackState])
return <ContainerBox className={back === 1 ? 'removeGradients' : ''} />
}
On console has the output State: 0, and after 320, State after remove:
1
Not every component also has a representation in the DOM. You need to apply the className to a component that actually has a corresponding DOM element to have your styles take any effect:
// className will not affect the DOM as this component does not render a DOM element
const WrappingComponent = ({className}) => (
<WrappedComponent className={className} />
);
// this className will be applied to the div in the DOM
const WrappedComponent = ({className}) => (
<div className={className}>Content here</div>
);

Resources