OBJLoader with React, THREE, Parcel and react-three-fiber - javascript

WARNING This is not a copy of three.js OBJLoader not loading in react. react-three-renderer is deprecated and I use functional components here.
I need to load an .obj file but two problems appear. First, it throws TypeError: instance is undefined by react-three-fiber. Second, it loads .obj as HTML: Error: THREE.OBJLoader: Unexpected line: "<!DOCTYPE html>".
Are these issues related to Parcel, THREE or react-three-fiber?
Here's the code that doesn't work:
App.js
import React from 'react'
import { render } from 'react-dom'
import { Canvas } from 'react-three-fiber'
import Model from '/Model'
import Controls from '/Controls'
import '/style.css'
const App = () => {
return (
<main>
<Canvas>
<Model url="demo.obj" />
<Controls />
</Canvas>
</main>
)
}
render(<App />, document.getElementById('app'))
Controls.js
import React, { useRef } from 'react'
import { useThree, useRender, extend } from 'react-three-fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
extend({ OrbitControls })
const Controls = props => {
const { camera } = useThree()
const controls = useRef()
useRender(({ camera }) => {
controls.current && controls.current.update()
camera.updateMatrixWorld()
})
return <orbitControls ref={controls} args={[camera]} {...props} />
}
export default Controls
Model.js
import React, { useRef, useMemo } from 'react'
import { Math as THREEMath } from 'three'
import { useRender } from 'react-three-fiber'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
const Model = ({ url }) => {
const model = useRef()
let rot = 0
const obj = useMemo(() => new OBJLoader().load(url), [url])
useRender(() => {
const rad = 5 * Math.sin(THREEMath.degToRad(rot += 0.3))
model.current.rotation.set(rad, rad, 0)
})
return <primitive object={obj} ref={model} />
}
export default Model
OBJ File here

wrong path. the doctype stuff is the 404 page it pulls. as of r3f 3 you can use the useLoader hook, it makes error handling easier since it's based on react suspense, so you could wrap it into error boundaries and fallbacks.

Related

Attempting to load a .obj and .mtl file in my react app

I am currently trying to load a 3D model I made into my react app. The 3D model exported as a folder which had a model.obj and a model.mtl file. After surfing the web for a bit I figured I could use Three-JS for this. My website is currently not displaying anything on the page I have this on.
import React from "react";
import { Canvas } from "#react-three/fiber";
import { ObjectLoader } from "three";
import { useLoader } from "#react-three/fiber";
import { MaterialLoader } from "three";
const ThreeDFloorPlan = () => {
const materials = useLoader(MaterialLoader, "../models/floorplan.mtl")
const object = useLoader(ObjectLoader, "../models/floorplan.obj", loader => {
materials.preload()
loader.setMaterials(materials)
})
return (
<Canvas>
<primitive object={object} />
</Canvas>
)
}
export default ThreeDFloorPlan;
import React from 'react';
import Button from '#mui/material/Button';
import { NavLink } from 'react-router-dom';
import ThreeDFloorPlan from '../components/ThreeDFloorPlan';
const Entry = () => {
return (
<div>
<ThreeDFloorPlan />
</div>
);
};
export default Entry;
Would love some feedback or help on this matter!
I have tried various different ThreeJS walk throughs, scoured the internet, and pulled out a bit of hair.

React Loadable and Meteor separate bundle

I am using the following Component with Meteor
https://github.com/CaptainN/npdev-react-loadable
import { Loadable } from 'meteor/npdev:react-loadable';
I create my Loadable component as follows
const HomePageBlog = Loadable({
loading: () => <FullPageLoader />,
loader: () => import('./HomePageBlog'),
});
I have gone through the SSR setup in the docs and it looks something like this
Server index.js
import React from 'react';
import { renderToString, renderToNodeStream } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { StaticRouter } from 'react-router';
import { Helmet } from 'react-helmet';
import Loadable from 'react-loadable';
import { ServerStyleSheet } from 'styled-components';
import {
LoadableCaptureProvider,
preloadAllLoadables,
} from 'meteor/npdev:react-loadable';
preloadAllLoadables().then(() => {
onPageLoad(async (sink) => {
const context = {};
const sheet = new ServerStyleSheet();
const loadableHandle = {};
const routes = (await import('../both/routes.js')).default;
const App = (props) => (
<StaticRouter location={props.location} context={context}>
{routes}
</StaticRouter>
);
const modules = [];
// const html = renderToNodeStream((
const html = renderToString(
<LoadableCaptureProvider handle={loadableHandle}>
<App location={sink.request.url} />
</LoadableCaptureProvider>,
);
// we have a list of modules here, hopefully Meteor will allow to add them to bundle
// console.log(modules);
sink.renderIntoElementById('app', html);
sink.appendToBody(loadableHandle.toScriptTag());
const helmet = Helmet.renderStatic();
// console.log(helmet);
sink.appendToHead(helmet.meta.toString());
sink.appendToHead(helmet.title.toString());
sink.appendToHead(helmet.link.toString());
sink.appendToHead(sheet.getStyleTags());
});
});
client index.js
import { Meteor } from 'meteor/meteor';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, withRouter } from 'react-router-dom';
import { onPageLoad } from 'meteor/server-render';
import { createBrowserHistory } from 'history';
import { preloadLoadables } from 'meteor/npdev:react-loadable';
console.log('hi');
const history = createBrowserHistory();
/**
* If browser back button was used, flush cache
* This ensures that user will always see an accurate, up-to-date view based on their state
* https://stackoverflow.com/questions/8788802/prevent-safari-loading-from-cache-when-back-button-is-clicked
*/
(function () {
window.onpageshow = function (event) {
if (event.persisted) {
window.location.reload();
}
};
})();
onPageLoad(async () => {
const routes = (await import('../both/routes.js')).default;
const App = () => (
<>
<Router history={history}>
<div>{routes}</div>
</Router>
</>
);
preloadLoadables().then(() => {
ReactDOM.hydrate(<App />, document.getElementById('app'));
});
});
What I am trying to determine is what exactly react loadable does. I am wanting to separate my bundle so I can only load code via SSR when it is needed. Right now I have quite a low score on lighthouse for page speed.
The code that I have here works.
But what I expected to happen was have a separate request to grab more js for the loadable component when it is requested. So it's not in the initial bundle. Is this not how this package works.
Could someone one help me me understand this better.
Thanks for any help ahead of time

Getting SyntaxError when using lightweight-charts in NextJS

I'm trying to use the lightweight-charts package in my nextjs project, however when i try to call the createChart function I get this error in my nodejs console.
...\lightweight-charts\dist\lightweight-charts.esm.development.js:7
import { bindToDevicePixelRatio } from 'fancy-canvas/coordinate-space';
^^^^^^
SyntaxError: Cannot use import statement outside a module
Component:
import styled from "styled-components"
import { createChart } from 'lightweight-charts';
const Wrapper = styled.div``
const CoinPriceChart = () => {
const chart = createChart(document.body, { width: 400, height: 300 });
return <Wrapper></Wrapper>
}
export default CoinPriceChart
Page:
import styled from "styled-components"
import CoinPriceChart from "../../components/charts/CoinPriceChart"
const Wrapper = styled.div``
const CoinDetailPage = () => {
return (
<Wrapper>
<CoinPriceChart />
</Wrapper>
)
}
export default CoinDetailPage
Does someone have an idea what I could do to enable me to use the library within nextjs?
Thank you!
That because you are trying to import the library in SSR context.
Using next.js Dynamic with ssr : false should fix the issue :
import styled from "styled-components"
import dynamic from "next/dynamic";
const CoinPriceChart = dynamic(() => import("../../components/charts/CoinPriceChart"), {
ssr: false
});
const Wrapper = styled.div``
const CoinDetailPage = () => {
return (
<Wrapper>
<CoinPriceChart />
</Wrapper>
)
}
export default CoinDetailPage

No QueryClient set, use QueryClientProvider to set one

I was trying to react query for the first time then I got this at the start of my React app.
import React from 'react'
import { useQuery } from "react-query";
const fetchPanets = async () => {
const result = await fetch('https://swapi.dev/api/people')
return result.json()
}
const Planets = () => {
const { data, status } = useQuery('Planets', fetchPanets)
console.log("data", data, "status", status)
return (
<div>
<h2>Planets</h2>
</div>
)
}
export default Planets
As the error suggests, you need to wrap your application in a QueryClientProvider. This is on the first page of the docs:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
While this is most commonly caused by not having your application wrapped in a <QueryClientProvider>, in my case it happened because I was importing some shared components, which ended up with a different context. You can fix this by setting the contextSharing option to true
That would look like:
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient = new QueryClient()
function App() {
return <QueryClientProvider client={queryClient} contextSharing={true}>...</QueryClientProvider>
}
From the docs: (https://react-query.tanstack.com/reference/QueryClientProvider)
contextSharing: boolean (defaults to false)
Set this to true to enable context sharing, which will share the first and at least one instance of the context across the window to ensure that if React Query is used across different bundles or microfrontends they will all use the same instance of context, regardless of module scoping.
Just make changes like below it will work fine
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>,
document.getElementById('root')
);
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
const queryClient = new QueryClient();
const fetchPanets = async () => {
const result = await fetch('https://swapi.dev/api/people')
return result.json()
}
const Planets = () => {
const { data, status } = useQuery('Planets', fetchPanets)
console.log("data", data, "status", status)
return (
<div>
<h2>Planets</h2>
</div>
);
}
export default function Wraped(){
return(<QueryClientProvider client={queryClient}>
<Planets/>
</QueryClientProvider>
);
}
Single SPA (micro-frontend) - React Query v3.34.15
I was getting this error while trying to integrate a sigle-spa react parcel into the root application.
I used craco-plugin-single-spa-application for the building of a CRA app as a way to adapt it for a parcel. In the entry config I was pointing to my single-spa-react config.
// craco.config.js
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application')
module.exports = {
plugins: [
{
plugin: singleSpaApplicationPlugin,
options: {
orgName: 'uh-platform',
projectName: 'hosting',
entry: 'src/config/single-spa-index.cf.js',
orgPackagesAsExternal: false,
reactPackagesAsExternal: true,
externals: [],
minimize: false
}
}
]
}
In the single-spa-index.cf.js file I had the following configs.
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import App from '../App'
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary() {
return <div>Ocorreu um erro desconhecido!</div>
}
})
export const { bootstrap, mount, unmount } = lifecycles
After reading a bunch of forums and the react-query documentation, the only thing that I figured out I needed to change was pass in the QueryClientProvider the prop contextSharing as true. After had did this change, ran the building and access the route that opens my parcel. I got the same error.
import React from 'react'
import ReactDOM from 'react-dom'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App'
const queryClient = new QueryClient()
const isDevelopmentEnv = process.env.NODE_ENV === 'development'
if (isDevelopmentEnv) {
import('./config/msw/worker').then(({ worker }) => worker.start())
}
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider contextSharing={true} client={queryClient}>
<App />
{isDevelopmentEnv && <ReactQueryDevtools initialIsOpen={false} />}
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
)
But, how do I solved that. Well, it was was simple. I couldn't even imagine why it was working locally. But not after building and integration.
The problem was because I put the React Query Provider inside the index o the application and in my single-spa-index.cf.js I was importing import App from '../App' which really wasn't wrapped by the provider. Once I also was importing App in the application index, where It was wrapped making It works locally. 😢😢
So after figure that out, my code was like that:
CODE AFTER SOLUTION
// craco.config.js
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application')
module.exports = {
plugins: [
{
plugin: singleSpaApplicationPlugin,
options: {
orgName: 'uh-platform',
projectName: 'hosting',
entry: 'src/config/single-spa-index.cf.js',
orgPackagesAsExternal: false,
reactPackagesAsExternal: true,
externals: [],
minimize: false
}
}
]
}
// src/config/single-spa-index.cf.js
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import App from '../App'
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary() {
return <div>Ocorreu um erro desconhecido!</div>
}
})
export const { bootstrap, mount, unmount } = lifecycles
// App.tsx
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import { config } from 'config/react-query'
import Routes from 'routes'
import GlobalStyles from 'styles/global'
import * as S from './styles/shared'
const queryClient = new QueryClient(config)
const isDevelopmentEnv = process.env.NODE_ENV === 'development'
if (isDevelopmentEnv) {
import('./config/msw/worker').then(({ worker }) => worker.start())
}
function App() {
return (
<QueryClientProvider contextSharing={true} client={queryClient}>
<S.PanelWrapper>
<Routes />
<GlobalStyles />
</S.PanelWrapper>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
export default App
// index.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
)
Well, it was long but I hope it helps someone that's undergoing for the same problem as mine. 🙌🙌🙌
I was trying to fix the same thing:
I followed the React Query docs
and used the concept of Higher Order Component
See if it helps:
import React from 'react';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Planet from './Planet';
const queryClient = new QueryClient();
const fetchPlanets = async () => {
const res = await fetch('http://swapi.dev/api/planets/');
return res.json();
}
const Planets = () => {
const { data, status } = useQuery('planets', fetchPlanets);
return (
<div>
<h2>Planets</h2>
{ status === 'loading' && (<div>Loading data...</div>)}
{ status === 'error' && (<div>Error fetching data</div>)}
{
status === 'success' && (
data.results.map(planet =>
<Planet
key={planet.name}
planet={planet}
/>
)
)
}
</div>
)
}
// Higher order function
const hof = (WrappedComponent) => {
// Its job is to return a react component warpping the baby component
return (props) => (
<QueryClientProvider client={queryClient}>
<WrappedComponent {...props} />
</QueryClientProvider>
);
};
export default hof(Planets);
In my case I was importtng from 'react-query' in one place and '#tanstack/react-query' in another.
I got that error when trying to add the react-query devtools.
The problem was I was installing it wrongly according my version, I was using react-query v3.
WRONG FOR react-query V3 (GOOD FOR V4)
import { ReactQueryDevtools } from '#tanstack/react-query-devtools';
OK FOR react-query V3
import { ReactQueryDevtools } from 'react-query/devtools';
In my case I accidentally used two different versions of react-query in my modules.
In my case
Error import { QueryClient, QueryClientProvider } from "#tanstack/react-query";
Solution import { QueryClient, QueryClientProvider } from "react-query";
remove it #tanstack/
Just be careful when upgrade from react-query v3 to #tanstack/react-query v4.
Ensure that you replace all imports as "react-query" to "#tanstack/react-query" and then run yarn remove the lib that you won't use anymore, otherwise you may accidentally import the unexpected one.
This happened to me and caused this error.

React hooks in Gatsby: Invalid hook call

Trying to implement a custom hook from a react example: https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state, but I get the following error:
ERROR:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
The error happen on useEffect when I try to import it as a custom hook usePrevious.
I tried following:
verified that react-dom and react is on the same version
react-dom#16.8.5 react#16.8.5
Verfied I only have one version of React
Also I do belive the code is not breaking any rules of hooks.
Also tried looking at related issues here on stackoverflow.
CODE:
// file: use-previous.js
import { useRef, useEffect} from "React"
export const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
// file: layout.js
import React, { useState, useEffect } from "react"
import Header from "./header/header"
import { useFacetsData } from "../hooks/use-facets-data"
import { usePrevious } from "../hooks/use-previous"
export default ({ children, ...props }) => {
const [ searchValue, setSearchValue ] = useState("")
const [ facetsSelected, setFacetsSelected ] = useState([])
const facets = useFacetsData()
const oldSearchValue = usePrevious(searchValue)
// const oldFacetsSelected = usePrevious(facetsSelected)
useEffect(() => {
// if new searchvalue or new facetvalue
// if (searchValue !== oldSearchValue || facetsSelected !== oldFacetsSelected) makeSearch()
})
function makeSearch() {
console.log('make search')
// move to searchpage
}
function handleSearchChange(search) {
setSearchValue(search)
}
function handleFacetChange(facets) {
setFacetsSelected(facets)
}
return (
<div>
{/* Main sticky header */}
<Header
facets={ facets }
onSearchChange={ handleSearchChange }
onFacetChange={ handleFacetChange } >
</Header>
{/* route content */}
{React.cloneElement(children, { facets })}
</div>
)
}
Your import in usePrevious hooks file is incorrect. useRef and useEffect should be import from 'react' and not React;
import { useRef, useEffect} from "react"

Categories