in ReactJs the ThreeJs Objects not showing - javascript

I'm a beginner-level programmer. I want to add route for each room(components). How can I do that so that whenever I click on "/room/1" it will show me "Room1" When I click "/room/2" then it will show "Room2". I want to use there dynamic route useParams. I don't want to add route for each room(3d object). Is it possible to do that?
Also when I go to "/rooms/1" and refresh there the page shows blank.
Here's the code which am working:
import { Canvas } from '#react-three/fiber';
import React, { Fragment, Suspense, useState } from 'react';
import { ContactShadows, OrbitControls } from '#react-three/drei';
import { Route, useParams } from 'react-router-dom';
import { Room1 } from "../assets/Room1";
import { Room2 } from "../assets/Room2";
import { Room3 } from "../assets/Room3";
import { Room4 } from "../assets/Room4";
import { Room5 } from "../assets/Room5";
import { Room6 } from "../assets/Room6";
import { Room7 } from "../assets/Room7";
import { Room8 } from "../assets/Room8";
const Rooms = () => {
const { id } = useParams();
const rooms = [
Room1,
Room2,
Room3,
Room4,
Room5,
Room6,
Room7,
Room8
];
const Room = rooms[id - 1];
return (
<div className='eachRoom'>
<Canvas >
<Suspense fallback={null}>
<ambientLight intensity={0.3} />
<directionalLight
castShadow
receiveShadow
intensity={0.5}
position={[-80, 50, -85]}
shadowNormalBias={0.1}
shadowCameraLeft={-12}
shadowCameraRight={12}
shadowCameraTop={12}
shadowCameraBottom={-12}
shadowCameraNear={0.5}
shadowCameraFar={200}
/>
<directionalLight
castShadow
receiveShadow
intensity={1}
position={[30, 100, 90]}
shadowNormalBias={0.1}
shadowCameraLeft={-12}
shadowCameraRight={12}
shadowCameraTop={12}
shadowCameraBottom={-12}
shadowCameraNear={0.5}
shadowCameraFar={200}
/>
<Fragment>{Room}</Fragment>
<ContactShadows />
</Suspense>
<OrbitControls enablePan={true} enableZoom={true} enableRotate={true} />
</Canvas>
</div>
)
}
export default Rooms
Here's the full code in codesandbox: https://codesandbox.io/s/stackoverflow1111-hn92iu?file=/src/components/Room.js
I try to loop over. But in the console it shows
"Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it."
I'm expecting a proper solution of my these 2 problem.
Thank You.

Related

Can I use UseState with Server-Side-Rendering? Nextjs/JavaScript

Can you use useState (and other react hooks?) with Server Side Rendering? Everytime I am trying to run the code below I get the error TypeError: Cannot read property 'useState' of null. However, when I comment out the getServerSideProps function at the very bottom I have no problem running the code as intended. So my questions is can useState be used with Server Side Rendering in nextjs? If the answer is yes, then where am I going wrong in the code below?
import React from "react";
import { useRouter } from "next/router";
import useSelectedGenreInfoExtractor from "../../hooks/useSelectedGenreInfoExtractor";
import { useState } from "react";
import { useEffect } from "react";
import Navbar from "../../components/Navbar";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import Moviegenreresults from "../../components/Moviegenreresults";
export default function genre(props) {
const [myresultsfromhook, setMyresultsfromhook] = useState();
const [myreturnedmovies, setMyreturnedmovies] = useState();
const router = useRouter();
const { genre } = router.query;
if (genre == "Trending") {
let mymovies = useFetchTrendingCatagory();
console.log("This is a log of my props", props);
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else if (genre == "Top Rated") {
let mymovies = useFetchTopRatedCatagory();
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else {
let mymovies = useFetchMovieGenreResults(genre);
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
}
}
export async function getServerSideProps(context) {
if (context.params.genre == "Trending") {
let mymovies = useFetchTrendingCatagory();
return {
props: {
results: mymovies.results,
},
};
} else if (context.params.genr == "Top Rated") {
let mymovies = useFetchTopRatedCatagory();
return {
props: {
results: mymovies.results,
},
};
} else {
let mymovies = useFetchMovieGenreResults(genre);
return {
props: {
results: mymovies.results,
},
};
}
}
I think fundamentally the problem is the way you are using getServerSideProps.
Even thought the answer is you can not use useState inside getServerSideProps because this function run in the server, it is important to understand what getServerSideProps does and when, I think you can find very clear explanation about that in next docs.
https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
Inside getServerSideProps use axios or the fetch api to get your data and pass it to the props.
I am not 100% sure but I thinnk inn your case you can also use Promise.all() to get the data from those three api calls.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
useState should be inside the component, it is a React hook. serverside functions are independent of React components.
I think the issue is the name of the component should be with capital letter:
// not genre
export default function Genre(props)

HTML overlay always gets clipped when it is larger than the canvas in react-three-fiber

I am trying to display a tooltip or a HTML overlay on the instances in an instanced mesh in three.js via react-three-fiber. However I am facing an issue in the sense that if the content is large, which is often the case, the content gets clipped vertically.
As you can see here, the HTML overlay div gets clipped vertically. No scrolling bar appears. I can change the width if I specify it in terms of vw, but nothing seems to work for height. Moreover the idea is that it should automatically adapt to the size of the display or a predetermined max size.
I feel that it is getting "sucked in" or constrained by the canvas size. Perhaps this issue would not arise if it was on top or somehow detached from it.
The code that I have so far is -
Steps to setup -
npx create-react-app demo
cd demo
npm install three
npm i #react-three/fiber
npm i #react-three/drei
App.jsx
import React from 'react'
import { Suspense } from "react";
import { OrbitControls, Html } from "#react-three/drei";
import Spheres from "./IScatter";
import * as THREE from "three";
import { Canvas, extend, useThree, useFrame } from '#react-three/fiber'
function App() {
return (
<div>
<Canvas style={{width:"100%",height:"100vh"}}>
<OrbitControls enableZoom={true} />
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]}/>
<Suspense fallback={null}>
<primitive object={new THREE.AxesHelper(1.5)} />
<Spheres />
</Suspense>
</Canvas>
</div>
);
}
export default App;
Tooltip.jsx
import React, {useState, useEffect, useLayoutEffect, useRef} from "react";
function Tooltip( {title, story, author, emotions} ){
return (
<div style={{display: "inline-block", width: "50vw", height: "30vh"}}>
<h6 class="title is-6">{ title }</h6>
<p>{story}</p>
{/* <br /> */}
<span class="is-pulled-right">-{author}</span><br/>
</div>
)
}
export default Tooltip;
IScatter.jsx
import * as THREE from "three";
import React, { useRef, useState, useMemo, useLayoutEffect } from "react";
import { OrbitControls, Stats, Html } from "#react-three/drei";
import { useEffect } from "react";
import { DoubleSide } from "three";
import data from "./story.json";
import Tooltip from "./Tooltip";
const points = [ [1, 0, -1], [0, 1, -0.5], [0.5, 0.5, 0.5], [1,0.25,-1], [1,0,1], [0,1,0.5] ];
const colors = [0,0,0,5,5,5];
const tempColor = new THREE.Color();
const tempSphere = new THREE.Object3D();
const Spheres = () => {
const material = new THREE.MeshLambertMaterial({ opacity: 0.5, side: THREE.DoubleSide, transparent: true,});
const spheresGeometry = new THREE.SphereBufferGeometry(0.25, 15, 15);
const ref = useRef();
const prevRef = useRef();
const [hovered, set] = useState();
useEffect(() => {
points.map(function (val, row) {
tempSphere.position.set(val[0], val[1], val[2]);
tempSphere.updateMatrix();
ref.current.setMatrixAt(row, tempSphere.matrix);
ref.current.setColorAt(row, new THREE.Color(`hsl(${colors[row]*100}, 100%, 50%)`));
});
if (hovered !== prevRef.current) {
ref.current.setColorAt(hovered, new THREE.Color("hsl(43, 100%, 50%)"));
ref.current.instanceColor.needsUpdate = true;
}
ref.current.instanceMatrix.needsUpdate = true;
},[hovered]);
return (
<instancedMesh
onPointerOver={(e) => (e.stopPropagation(), set(e.instanceId))}
onPointerOut={(e) => set(undefined)}
ref={ref} rotation={[0,30,0]} args={[spheresGeometry, material, 15]}>
<Html distanceFactor={5}>
<Tooltip title={data.title} story={data.story} author={data.author}></Tooltip>
</Html>
</instancedMesh>
);
};
export default Spheres;
Please take the story.json file from this gist.
What is the best way to ensure that I can also display custom HTML on mouseover and that the contents of the display are fully visible?
I have not been able to figure out why this happens but R3F for some reason adds overflow: hide to the divs. When I manually supply the style overflow: visible to the HTML overlay along with some predefined width, the tooltip works as expected. I found this out experimentally, I don't really know why things would work this way.
I will wait for a better answer for this.

react-leaflet WMSTileLayer 'params' option causes map layer to flicker when any unrelated state variable is updated

I am using react-leaflet:3.2.0 to display a WMS tile layer using the WMSTileLayer component. It works beautifully provided I don't include the params option. If I include anything via the params option then updating ANY state variable causes the layer to flicker.
In the example below, I hard code params={{hello:'world'}} into the WMSTileLayer options, and every time I press a button to update a completely unrelated state variable called dummy the map flickers. Since I potentially have a lot of state management going on it prevents me from using the params option with WMSTileLayer. And since I really need to use the params option to add arguments to the WMS query string, I'm stuck.
Can anyone tell me what I'm doing wrong? Presumably others use this option OK.
All help greatly appreciated, thanks!
Here is a link to a gif illustrating the problem and the code is show below.
import React, { useState } from 'react'
import { WMSTileLayer } from 'react-leaflet'
import * as L from 'leaflet'
import 'proj4leaflet'
import proj4 from 'proj4'
import { MapContainer, TileLayer } from 'react-leaflet'
import { Button } from 'react-bulma-components';
import 'leaflet/dist/leaflet.css'
export default function Dataset(props) {
const zoomLevel = 1
const center = [51.7, -1.5]
const projName = 'EPSG:27700'
const projParams = '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs'
const crs27700 = new L.Proj.CRS(projName,
projParams,
{
resolutions: [896, 448, 224, 112, 56, 28, 14, 7, 3.5, 1.75, 0.875, 0.4375, 0.21875, 0.109375],
origin: [-238375, 1376256]
}
)
proj4.defs(projName,projParams)
const [dummy, setDummy] = useState(true)
return (
<div>
<div className="dataset-container">
<MapContainer center={center} zoom={zoomLevel} crs={crs27700} attributionControl={false} >
<WMSTileLayer
layers={'pollutant'}
url={`/mapserver/mapserv?map=/maps/pollutant.map&year=05`}
params={{hello:'world'}} // <-- comment out this line to stop the map flickering when the button is pressed
maxZoom={6}
transparent={true}
format='image/png'
opacity={0.8}
/>
</MapContainer>
</div>
<Button onClick={e => setDummy(!dummy)}>Press me</Button>{dummy ? ' dummy=true' : ' dummy=false'}
</div>
)
}
I investigated this but could find no answer. I tried abstracting the WMS to a separate function component but the result was the same. I reproduced the problem using a publicly accessible WMS and default CRS with less code so posting here in case useful to someone else investigating:
import React, { useState } from 'react'
import { MapContainer, WMSTileLayer } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
export default function App() {
const [dummy, setDummy] = useState(true)
return (<>
<div>
<MapContainer style={{height:"400px", width:"600px"}} center={[51.7, -1.5]} zoom={13} >
<WMSTileLayer
layers={'TOPO-OSM-WMS'}
url={`http://ows.mundialis.de/services/service?`}
params={{hello:'world'}} // <-- comment out this line to stop the map flickering when the button is pressed
/>
</MapContainer>
</div>
<button onClick={e => setDummy(!dummy)}>Press me</button>
</>)
As pointed out in https://github.com/PaulLeCam/react-leaflet/issues/825#issuecomment-765349582:
This is the expected behavior that props comparison is made by reference, it's up to you to keep track of these references rather than creating new objects in every render as you need.
The actual issue in the example from this answer above is re-creation of the params object on every re-render. You can prevent that by using the useMemo hook, for example:
import React, { useState, useMemo } from "react";
import { MapContainer, WMSTileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
export default function App() {
const [dummy, setDummy] = useState(true);
const layerParams = useMemo(() => {
return {hello: 'world'};
}, []);
return (
<>
<div>
<MapContainer
style={{ height: "400px", width: "600px" }}
center={[51.7, -1.5]}
zoom={13}
>
<WMSTileLayer
layers={"TOPO-OSM-WMS"}
url={`http://ows.mundialis.de/services/service?`}
params={layerParams}
/>
</MapContainer>
</div>
<button onClick={(e) => setDummy(!dummy)}>Press me</button>
</>
);
}

React Context API & Lifecycle methods - How to use together

I'm struggling to understand how to proceed with a small React app I am making.
I have a budget tracker, where you can add costs (mortgage, bills etc.) and they have a cost value. Each time you add, edit or delete one of these, I want the global state to change, which is stored in a context.
I basically have a 'remaining balance' value, that I want to recalculate each time something changes.
I figured I'd use a life cycle method or useEffect, but when I use that in my App.js (so that it watches for changes in all subcomponents), I can't get it to work, because the life cycle method is calling a method from my Context, but because it's not wrapped in the provider, it can't access the method in the Context.
Is this a common problem and is there are recommended way to fix it? I can't seem to find a similar problem on the GoOgLe.
App.js:
import React, { useState, useContext, useEffect } from "react";
import "./css/main.css";
import Header from "./layout/Header";
import BudgetInfo from "./components/BudgetInfo";
import PaymentForm from "./components/PaymentForm";
import CostToolbar from "./components/CostToolbar";
import Costs from "./components/Costs";
import BudgetContext from "./context/budgetContext";
import BudgetState from "./context/BudgetState";
const App = () => {
const budgetContext = useContext(BudgetContext);
const { updateBalance } = budgetContext;
useEffect(() => {
updateBalance();
});
return (
<BudgetState>
<Header darkModeToggle={toggleDarkMode} />
<main
className={"main-content" + (darkMode.darkMode ? " dm-active" : "")}
>
<div className="wrap content-wrap">
<BudgetInfo />
<PaymentForm />
<CostToolbar />
<Costs />
</div>
</main>
</BudgetState>
);
};
export default App;
You need to wrap the App component. Try the simple example.
import React, { useEffect, useContext } from 'react';
import ThemeContext from './../context/context';
const Sample = () => {
const context = useContext(ThemeContext);
useEffect(() => {
console.log(context,'--')
},[])
return(
<ThemeContext.Consumer>
{color => (
<p style={{ color }}>
Hello World
</p>
)}
</ThemeContext.Consumer>
)
}
export default Sample;

Get react component from library using a list

I have a list:
var coins =["BTC", "ETH","LTC"]
And there is a react library where I can get Icons for these coins using the following schema:
<Icon.Eth /> or <Icon.Btc /> or <Icon.ltc />
But the list is retrieved using an API and it can change at any moment.
Is there a way to do something like this:
<Icon.coins[0] /> or <Icon.coins[1] /> or <Icon.coins[2] />
Use square brackets like this
let IconA = Icon[coins[0]];
let IconB = Icon[coins[1]];
// Then use them like this
<IconA /> or <IconB />
If you want to look up dynamically based on coin name, you can import all coins and look them up by string.
import * as Icons from 'react-cryptocoins';
const coins = ['Eth'];
class MyClass extends React.Component {
render() {
return (
<div>
{ React.createElement(Icons[coins[0]]) }
</div>
);
}
}
Note this requires your array to be title cased, not all capitals. Putting it all together, I would make a component for this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as Icons from 'react-cryptocoins'
const Coin = ({ icon }) => (
React.createElement(Icons[coins[0]]) }
);
Coin.propTypes = {
icon: PropTypes.string.isRequired
};
export default Coin;
Usage:
import React, { Component } from 'react';
import Coin from './Coin';
const coins = ["BTC", "ETH", "LTC"];
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
}
class MyElement extends Component {
render() {
return (
<div>
{ coins.map( coin => (
<Coin icon={capitalize(coin)} />
)}
</div>
)
}
}

Categories