How to implement Three.js inside Gatsby? - reactjs

I've installed react-three-fiber and three packages. I'm following this tutorial, but I have doubts of where to put this line:
const rootElement = document.getElementById("root");
Also, I'm starting to receive this error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
My index.js:
import React, { Children, useRef, useState } from "react"
import { Link } from "gatsby"
import { Canvas} from 'react-three-fiber'
import Layout from "../components/layout"
const IndexPage = () => {
return(
<Layout>
<div>
<h1>Hi</h1>
</div>
<canvas>
<Children></Children>
</canvas>
</Layout>
)
}
const rootElement = document.getElementById("root");
export default IndexPage
Any ideas?

Make sure you have installed both three and react-three-fiber.
npm install three react-three-fiber
Then in your gatsby page component just import Canvas from react-three-fiber before using it in your JSX.
import React from "react"
import { Canvas} from 'react-three-fiber'
import Layout from "../components/layout"
const IndexPage = () => (
<Layout>
<Canvas />
</Layout>
)
export default IndexPage
Regarding const rootElement = document.getElementById("root"); :
Although Gatsby is built with React, it doesn't require you to select a root element in order to render your app. If this sounds confusing, you should spend a little bit of time to take a read of the Gatsby docs.
If you were to build the example from the react-three-fiber docs in Gatsby, it would look something like this.
import React, { useRef, useState } from "react"
import { Canvas, useFrame } from "react-three-fiber"
const Box = props => {
// This reference will give us direct access to the mesh so we can animate it
const mesh = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
// Rotate mesh every frame, this is outside of React without overhead
useFrame(() => (mesh.current.rotation.x = mesh.current.rotation.y += 0.01))
return (
<mesh
{...props}
ref={mesh}
scale={active ? [1.5, 1.5, 1.5] : [1, 1, 1]}
onClick={e => setActive(!active)}
onPointerOver={e => setHover(true)}
onPointerOut={e => setHover(false)}
>
<boxBufferGeometry attach="geometry" args={[1, 1, 1]} />
<meshStandardMaterial
attach="material"
color={hovered ? "hotpink" : "orange"}
/>
</mesh>
)
}
const IndexPage = () => (
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>
)
export default IndexPage

Related

OBJ Model not loading in react three fiber

I'm trying to add an OBJ Model (armchair.obj) but it's not being loaded. I'm using React three fiber library.
Here's my codesandbox: CodeSandbox
There's no problem with the model because I tried to load it using some other website and it is being loaded.
Anyway, I tried uploading another model (spongebob.obj) but it's not being really visible
However, in the other website, it's visible:
So, here's my codesandbox link
But, if you prefer the code here:
My App.js component:
import React, { Suspense } from "react";
import { Canvas } from "#react-three/fiber";
import { OrbitControls } from "#react-three/drei";
import LoadModel from "./components/LoadModel";
import Loader from "./components/Loader";
const App = () => {
return (
<main className="main-area">
<div id="canvas-container">
<Canvas pixelratio={[1, 2]} camera={{ position: [15, 15, 15], fov: 50, scale: [2,2,2] }}>
<ambientLight intensity={1} />
<Suspense fallback={<Loader />}>
<LoadModel url="./spongebob.obj" />
</Suspense>
<OrbitControls />
</Canvas>
</div>
</main>
);
};
export default App;
My LoadModel.js component:
import React, { useMemo, useState } from "react";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
const LoadModel = ({ url }) => {
const [obj, set] = useState();
// useMemo(() => new OBJLoader().load(url, set), [url])
useMemo(() => new OBJLoader().load(url, set), [url]);
//useMemo(() => new GLTFLoader().load(url, set), [url])
return obj ? <primitive object={obj} dispose={null} /> : null;
};
export default LoadModel;
You can improve the rendering of the SpongeBob model by adding a directional light to your scene. A single ambient light is not sufficient for proper illumination. Try adding the following line to your codesandbox:
<directionalLight />
The chair model has unfortunately some design issues. It has an extreme scale and is highly translated. I suggest you scale the model down and then center it after the loading process. However, it would be better to fix the model in a DCC tool like Blender and model the chair according to real world units.

can you portal elements into a Canvas in react-three-fibre?

Is it possible to portal some react-three-fibre elements into a canvas from outside of one?
I.e. in the following example, which was my first attempt to portal a mesh into a Canvas which errored with "target element is not a DOM element" (which makes sense, given these aren't DOM elements)
import * as React from 'react'
import { Canvas, createPortal } from '#react-three/fiber'
export const Element = ({ parent }) => {
return createPortal(
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
, parent);
};
export const MyPage = () => {
const ref = React.useRef(null);
return (
<div>
<Canvas>
<group ref={ref} />
</Canvas>
<Element parent={ref.current} />
</div>
);
};
So I found the createPortal method (which has incredibly limited documentation) from react-three-fiber, but with that it throws errors, presumably here because the createPortal method only works when already inside a Canvas context and is for portaling around inside a Canvas
import * as ReactDOM from 'react-dom'
import * as React from 'react'
import { Canvas } from '#react-three/fiber'
export const Element = ({ parent }) => {
return ReactDOM.createPortal(
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
, parent);
};
export const MyPage = () => {
const ref = React.useRef(null);
return (
<div>
<Canvas>
<group ref={ref} />
</Canvas>
<Element parent={ref.current} />
</div>
);
};
the above throws this error:
react-dom.development.js:22839 Uncaught R3F hooks can only be used within the Canvas component!
Is there a way of doing this?
My hope is to be able to portal elements from elsewhere in the project into a single document-sized Canvas element that sits at the root.
update:
I have managed to get this to work by wrapping the call to createPortal in a second <Canvas />
export const Element = ({ parent }) => {
return (
<Canvas>
{createPortal(
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="blue" />
</mesh>,
portalTo
)}
</Canvas>
);
};
</code>
however this is unworkable as three.js lmits the number of canvases in a project (and having unused actual canvases everywhere in the DOM is really gross anyway)
is there a way of giving createPortal access to that context without needing to render it inside a Canvas?

grandchild component doesn't recognize the global state react redux

Hello everyone and thank you for reading,
I'm new to redux and Iv been trying to connect a grandchild component with the store, but for some reason It doesn't work.
The child component (called canvas) uses the store as well and doesn't show any problem, but when I'm trying to get the same data in a component I call from within the child component I get: "Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a Provider tag"
I searched for the solution on the internet and everybody suggests to wrap the app component in provider tags inside index.js, but I already did that in the beginning
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {createStore} from 'redux';
import allReducer from './reducers'
import { Provider } from 'react-redux';
const store = createStore(allReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
ReactDOM.render(
// <React.StrictMode>
<Provider store={store}>
<App/>
</Provider>,
//</React.StrictMode>,
document.getElementById('root')
);
App.js
import React from 'react';
import "./App.css";
import { Canvas } from "./components/Canvas";
export default function App() {
return (
<div className="main-div">
<Canvas/>
</div>
);
}
relevant data from canvas.js (child component)
import { Stage, Layer, Rect } from 'react-konva';
.
.
.
.
const theTextObjs = TextObjs.map((text,key) => {
return (
<TextBox
//some irrelevant props
/>
{/* some irrelevant info and other components . . . */}
)});
.
.
.
.
return(
<Stage
width={ window.innerWidth * 2}
height={ window.innerHeight * 2}
//style={{ border: '1px solid grey' }}
ref={stageRef}
>
<Layer>
<Rect
x = {0}
y= {0}
width={ window.innerWidth * 2}
height={ window.innerHeight * 2}
fill={"white"}
stroke={"white"}
strokeWidth={5}
/>
{theTextObjs}
</Layer>
</Stage>
//some more components
)
inside of TextBox.js (grandchild component)
import React from "react";
import { useSelector} from 'react-redux';
export function TextBox({textEditVisible, textX,fill,textY,textValue,fontSize,width,height,fontStyle,align,id,onDragStart,isSelected,onClick,onChangeEnd,onContextMenu,onChangeEdit,onClickedit,onKeyDownEdit,event}) {
const selectedTextObj = useSelector(state => state.selectedTextObj)
const TextObjs = useSelector(state => state.TextObjs)
const dispatch = useDispatch()
//
//some irrelevant info
// ...
return (
<>
{/* more irrelevant info */}
</>
);
}
When using a different React renderer such as Konva, you need to wrap the components within the different Renderer within a new provider. In the case of Konva, the children of Stage.
See this issue.
const store = useStore()
return ( <Stage
width={ window.innerWidth * 2}
height={ window.innerHeight * 2}
//style={{ border: '1px solid grey' }}
ref={stageRef}
>
<Provider store={store}>
<Layer>
<Rect
x = {0}
y= {0}
width={ window.innerWidth * 2}
height={ window.innerHeight * 2}
fill={"white"}
stroke={"white"}
strokeWidth={5}
/>
{theTextObjs}
</Layer>
</Provider>
</Stage>

How to move the camera using react-three-fiber and #react-three/drei?

I'm drawing a 3d model with the following configuration.
react-three-fiber
#react-three/drei
The following is a CodeSandBox with a model drawn.
CodeSandBox-A
The drawn model is close to the top of the canvas, and I want to center it.
Could you please tell me how to center the model on the canvas?
This is the first time I've touched Three.js, so I'm sure I may have made some elementary mistakes.
The code is as follows.
I referred to the following document for the props of the Canvas component.
React Three Fiber Documentation
import { useMemo, Suspense } from "react";
import "./style.css";
import { Canvas } from "#react-three/fiber";
const { useGLTF, OrbitControls, Stage } = require("#react-three/drei");
const CanvasContainer = () => {
const { scene } = useGLTF("/scene.glb");
return (
<Canvas camera={{ fov: 70, position: [0, 1000, 1000] }}>
// No matter what I put in this "position" value, I could not see any change in the canvas.
<OrbitControls enablePan={true} enableZoom={true} enableRotate={true} />
<Stage>
<group dispose={null}>
<primitive scale={[1, 1, 1]} object={scene} />
</group>
</Stage>
</Canvas>
);
};
const App = () => {
const isWindow = typeof window === "undefined";
const memoCanvas = useMemo(() => <CanvasContainer />, []);
return (
<>
<h1>Canvas</h1>
<div className="canvas_container">
{!isWindow && <Suspense fallback={<div />}>{memoCanvas}</Suspense>}
</div>
</>
);
};
export default App;
setDefaultCamera removed in v6.x. · Issue #1147 · pmndrs/react-three-fiber
I also tried the code to use useThree as described in the above Issue.
The following is its CodeSandBox.
CodeSandBox-B
As you can see from the CodeSandBox, I was unable to move the camera.
give orbitcontrols the "makeDefault" prop, then it should in the center. otherwise check out dreis Bounds component. see: https://twitter.com/0xca0a/status/1453807293074661385
the memocanvas thing btw makes no sense, don't put jsx into usememo.

React three fiber and Textureloader not display

I am learning to develop some mobile applications using react native and expo.
I have created some code to apply a texture on a sphere. I have tried both with http url image and an image stored on my machine. When I am running the project in the expo web browser, everything is OK, but when I am launching it on android and ios devices, the sphere object is not displayed.
Any idea why this would work in the browser but not in a native environment?
Here is my code:
import React, { useRef, useState,useMemo, Suspense } from 'react';
import { StyleSheet, View } from 'react-native';
import * as THREE from "three";
import { Canvas, useFrame,useLoader } from 'react-three-fiber';
// import myImage from './img/world.png'
function SphereElement(props) {
// This reference will give us direct access to the mesh
const mesh = useRef();
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false);
const [active, setActive] = useState(false);
// Rotate mesh
useFrame(() => {
if (mesh && mesh.current) {
mesh.current.rotation.y = mesh.current.rotation.y += 0.002;
}
});
const image_pth='https://i.imgur.com/3yEEsnO.png'
const texture = useLoader(THREE.TextureLoader,image_pth)
return (
<mesh
{...props}
ref={mesh}
>
<sphereBufferGeometry attach="geometry" args={[2, 32, 32]} />
<meshBasicMaterial
attach="material"
transparent side={THREE.DoubleSide}
map={texture}
>
</meshBasicMaterial>
</mesh>
);
}
export default function App() {
return (
<View style={styles.container}>
<Canvas>
<Suspense fallback={null}>
<SphereElement position={[0, 0, 0]} />
</Suspense>
</Canvas>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black",
},
});
You should use useTexture hook from drei, as of the newer version of three and react-three-fiber, that is the recommended way to load textures.
https://github.com/pmndrs/drei#usetexture

Resources