Add Custom components in #react-three/fiber scene - reactjs

So my issue is that I want to add my custom components into my scene. But these custom components will be defined in a different file. So when I define my custom component all three-fiber components come across as
Cannot find name 'planeGeometry'. Did you mean 'PlaneGeometry'?
My app is written in Typescript. I tried it in sandbox for Javascript it was working fine. Maybe I didn't set up #react-three/fiber properly since even adding props to the component give error.
Say I want to add a plane mesh into my scene, Plane Mesh defined in Ground.ts and My Scene defined in Scene.ts
Scene.ts:
import {Ground} from './Ground'
const Scene =()=>{
return (
<Canvas>
<Ground />
</Canvas>
);
};
Ground.ts
import {useTexture} from '#react-three/drei';
export const Ground = () => {
const groundUrl = "<url_here>";
const texture = useTexture(groundUrl);
return (
<mesh>
<planeGeometry args={[120, 120]} />
<meshStandardMaterial map={texture} />
</mesh>
);
};
Here the components mesh, planeGeometry, meshStandardMaterial all give give errors as not found component. Is there any other step that I'm missing. I'm not very familiar with Typescript so I probably missed a few things.

Related

Mobile Safari Not Showing My Video Texture

I have a question similar to this one, but in my case, it's iOS causing troubles (not macOS, which I haven't tried yet), so I hope it's OK to post this as well. I tried to create a video texture in Three.js and can't bring it to work on mobile Safari (iOS 15.4). Here is my code, which I tried to tidy up as much as possible:
import React, { useEffect, useRef } from "react";
import * as THREE from "three";
import { Canvas } from "#react-three/fiber";
import "./styles.css";
const Screen = () => {
const meshRef = useRef();
useEffect(() => {
const vid = document.createElement("video");
vid.src = "/test.mp4";
vid.crossOrigin = "Anonymous";
vid.loop = vid.muted = vid.playsInline = true;
vid.play();
meshRef.current.material.map = new THREE.VideoTexture(vid);
});
return (
<mesh ref={meshRef}>
<planeGeometry attach="geometry" />
</mesh>
);
};
const App = () => {
return (
<Canvas camera={{ fov: 25 }}>
<Screen />
</Canvas>
);
};
export default App;
Please tell me if I'm doing something wrong here. The test.mp4 is from this URL. I also tried to place the video as HTML element, instead of creating it dynamically, then the video itself plays fine, but not the video texture.
Also, just curious, but why isn't meshRef.current available in a useEffect in the main component, but useEffect inside of Screen, which is placed inside of Canvas, is OK?
Apparently it's a problem with video file formats. Tried an example video from Three.js and it worked.
To those of you looking for the solution , you need to add
vid.playsInline=true;
for mobile ios devices.
I had the same problem. I had to set the 'playsinline' attribute in a very specific way.
video.playsinline= true did not work but video.setAttribute('playsinline', true)
did work.
Hope this helps

How to animate react-native-svg Polygon element?

Is there a simple way to animate the Polygon element from the react-native-svg library?
I need to animate his shape by animating the points.
I found few examples on how to animate Path element or Circle, but couldn't find anything regarding the Polygon. Thanks in advance.
Bit late to the party, but I've found a solution if you're still interested. It's not exactly 'simple', but it works. There's a library called React Native Reanimated, and it extends the functionality of Animated components
substantially. Here's what I was able to achieve:
The reason animating Polygons isn't available out of the box is because the standard Animated API only handles simple values, namely individual numbers. The Polygon component in react-native-svg takes props of points, which is an array of each of the points, themselves array of x and y. For example:
<Polygon
strokeWidth={1}
stroke={strokeColour}
fill={fillColour}
fillOpacity={1}
points={[[firstPointX, firstPointY],[secondPointX, secondPointY]}
/>
React Native Reanimated allows you to animate even complex data types. In this case, there is useSharedValue, which functions almost identical to new Animated.value(), and a function called useAnimatedProps, where you can create your points (or whatever else you want to animate) and pass them to the component.
// import from the library
import Animated, {
useSharedValue,
useAnimatedProps,
} from 'react-native-reanimated';
// creates the animated component
const AnimatedPolygon = Animated.createAnimatedComponent(Polygon);
const animatedPointsValues = [
{x: useSharedValue(firstXValue), y: useSharedValue(firstYValue)},
{x: useSharedValue(secondXValue), y: useSharedValue(secondYValue)},
];
const animatedProps = useAnimatedProps(() => ({
points: data.map((_, i) => {
return [
animatedPointValues[i].x.value,
animatedPointValues[i].y.value,
];
}),
})),
Then in your render/return:
<AnimatedPolygon
strokeWidth={1}
stroke={strokeColour}
fill={fillColour}
fillOpacity={1}
animatedProps={animatedProps}
/>
Then whenever you update one of those shared values, the component will animate.
I'd recommend reading their docs and becoming familiar with the library, as it will open up a whole world of possibilities:
https://docs.swmansion.com/react-native-reanimated/
Also, the animations are handled in the native UI thread, and easily hit 60fps, yet you can write them in JS.
Good luck!
react-native-reanimated also supports flat arrays for the Polygon points prop, so we can simplify the animation setup even more.
Full example which will animate the react-native-svg's Polygon when the points prop changes looks like this:
import React, { useEffect } from 'react'
import Animated, { useAnimatedProps, useSharedValue, withTiming } from 'react-native-reanimated'
import { Polygon } from 'react-native-svg'
interface Props {
points: number[]
}
const AnimatedPolygonInternal = Animated.createAnimatedComponent(Polygon)
export const AnimatedPolygon: React.FC<Props> = ({ points }: Props) => {
const sharedPoints = useSharedValue(points)
useEffect(() => {
sharedPoints.value = withTiming(points)
}, [points, sharedPoints])
const animatedProps = useAnimatedProps(() => ({
points: sharedPoints.value,
}))
return <AnimatedPolygonInternal fill="lime" animatedProps={animatedProps} />
}

Using Styled Components in functional React components

(I hope and assume that there is a simple answer to this question, but I could not find it in the docs)
I've just converted a React project to use Styled Components, and love the DRYness and reusability. But, I've not yet figured out the syntax for using styled components in existing functional components that do other work too.
Here's one example:
const StyledSearchBarPane = styled(Pane)`
grid-area: search-bar;
`;
const SearchBarPane = () => {
const {query} = useContext(panelContext);
let [newQuery, setNewQuery] = useState(query);
return (
<StyledSearchBarPane>
<Bar>
<SearchInput newQuery={newQuery} setNewQuery={setNewQuery}/>
</Bar>
</StyledSearchBarPane>
);
};
How can I avoid naming StyledSearchBarPane? It's only used once -- in SearchBarPane -- and I'd rather it were simply part of the latter's definition.
Yes you can.
Basically what you need to do is use your component as you would normally do and if you want to use a local style on the component without creating one you can use a Wrapper like you did.
But the change is, instead of exporting your component you export the wrapper. Then your component will receive as props className and you need to put it where you want to apply thoses style, in the example since it's globally you put in the root element.
If you don't put the className props it won't style anything.
const SearchBarPane = ({className}) => {
const {query} = useContext(panelContext);
let [newQuery, setNewQuery] = useState(query);
return (
<div className={className}>
<Bar>
<SearchInput newQuery={newQuery} setNewQuery={setNewQuery}/>
</Bar>
</div>
);
};
const StyledSearchBarPane = styled(SearchBarPane)`
grid-area: search-bar;
`;
export default StyledSearchBarPane;
https://styled-components.com/docs/advanced

Gatsby application that is SPA on mobile and has different routes on desktop

📖 Summary
Recently my team started a project of a landing page and we chose to use Gatsby in order to have good SEO.
At a point in our project, the designers changed the mobile layouts to be a SPA, and the desktop ones still having different routes and pages.
Refer to that example:
Since Gatsby creates pages in build time, we don't know if the environment is mobile or desktop, it's difficult to think in a way to deal with that behavior.
🐛 Workaround
One quick way that our team thought to temporarily resolve that problem was to map between sections and hide than in desktop screens.
And the biggest problem is: On the first load of the page the content takes almost a second to load because it's not static anymore.
<div>
{
breakpoints.md
? pages.map((page) => renderPage(page))
: renderPage(selectedPageRef.current)
}
</div>
🚀 Goals
I would like to discuss about a solution that will change the behavior of the pages in desktop and mobile without killing the SEO of the application.
If you can't solve by using mediaqueries and you must display two different components rather than the same styled. The workaround to solve this is to check what is the window size at the rendered time and show one layout or another. It would create a minimum delay (insignificant if cached) before the header is shown but this is the only way I am able to guess.
So, using Gatsby's default structure in <Layout> component you should have something like that:
return (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<div>
<main>{children}</main>
<footer>
© {new Date().getFullYear()}, Built with
{` `}
Gatsby
</footer>
</div>
</>
)
So, in your <Header> component you should check your window size and render one component or another:
export const Header = (props) => {
let currentWidth;
if (typeof window !== 'undefined') currentWidth = useWindowWidth();
return typeof window !== 'undefined' ? currentWidth >= 768 ? <DesktopPane /> : <MobilePane /> : null;
};
As you can see, in the return I check if window is defined in a ternary chained condition. If the window is not defined (i.e: is undefined) it returns a null. If it's defined, it checks the current window's width (currentWidth) and there's another ternary condition that displays the mobile or the desktop menu.
As a best practice, chained ternaries are not the cleanest solution, they are difficult to read and maintain but for now, the solution works (and of course it must be refactored).
In this case, useWindowWidth() is a custom hook that calculates in every window the size but you can use whatever you like. It looks like:
import {useEffect, useState} from 'react';
const getWidth = () => window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
export const useWindowWidth = () => {
let [width, setWidth] = useState(getWidth());
useEffect(() => {
let timeoutId = null;
const resizeListener = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setWidth(getWidth()), 150);
};
window.addEventListener('resize', resizeListener);
return () => {
window.removeEventListener('resize', resizeListener);
};
}, []);
return width;
};
Code provided by: https://usehooks.com/useWindowSize/
Note that it's common in Gatsby's projects to check if the window is !== than undefined due to the point you argued. At the compilation/build point isn't defined yet. You can check for further information about Debugging HTML Builds in their documentation.
First, you can achieve the following with the help of CSS media query (hide the sidebar or transform it to be the top nav):
Then, you can set up a useMediaQuery hook (for example here is an implementation) to conditionally render the About and Events components in one of the two ways shown below:
A) If most users are using desktop, you can defer the import of the other two components (About and Events) in mobile view with loadable-components:
The Index page will look something like this:
import React from "react"
import Loadable from "#loadable/component"
import useMediaQuery from "use-media-query-hook"
import Sidebar from "../components/Sidebar"
import Home from "../components/Home"
const LoadableAbout = Loadable(() => import("../components/About"))
const LoadableEvents = Loadable(() => import("../components/Events"))-
const IndexPage = () => {
const isMobile = useMediaQuery("(max-width: 425px)")
return (
<div>
<Sidebar />
<Home />
{isMobile && <LoadableAbout />}
{isMobile && <LoadableEvents />}
</div>
)
}
export default IndexPage
B) If most users are using mobile, you can include the two components in the main bundle at build time:
The Index page will look something like this:
import React from "react"
import useMediaQuery from "use-media-query-hook"
import Sidebar from "../components/Sidebar"
import Home from "../components/Home"
import About from "../components/About"
import Events from "../components/Events"
const IndexPage = () => {
const isMobile = useMediaQuery("(max-width: 425px)")
return (
<div>
<Sidebar />
<Home />
{isMobile && <About />}
{isMobile && <Events />}
</div>
)
}
export default IndexPage
Regarding SEO, the search engine will only see the main component (Home in route "/", About in "/about", etc) since isMobile defaults to null at build time in the above implementations.
Regarding speed, the main components are statically rendered in the HTML. Only in mobile SPA view, other sections are needed to be loaded.

How to add an SVG/d3 map to React Native?

Use Case
I want to create a React Native app that displays coordinates stored in a PostGIS database on a world map with a special map projection.
What I have done so far
I successfully loaded the map to a React environment for my web version of the app. This is the code I implemented (only including the component that holds the map):
import React, { useEffect, useRef } from 'react';
import Aux from '../../../../hoc/Auxiliary';
import styles from './Backmap.module.css';
import * as d3 from "d3";
import { feature } from "topojson-client";
import landmass from "./../../../../land-50m";
const Backmap = (props) => {
const mapContainer = useRef(null);
let width = props.windowSize.windowWidth;
let height = props.windowSize.windowHeight;
useEffect(() => {
const svg = d3.select(mapContainer.current)
.attr("width", width)
.attr("height", height)
const g = svg.append("g")
let features = feature(landmass, landmass.objects.land).features
let projection = d3.geoAzimuthalEqualArea()
.center([180, -180])
.rotate([0, -90])
.fitSize([width, height], { type: "FeatureCollection", features: features })
.clipAngle(150)
let path = d3.geoPath().projection(projection)
g.selectAll("#landmass")
.data(features)
.enter().append("path")
.attr("id", "landmass")
.attr("d", path);
}, [])
return (
<Aux>
<svg
className={styles.Backmap}
width={props.windowSize.windowWidth}
height={props.windowSize.windowHeight}
ref={mapContainer}
>
</svg>
</Aux>
)
}
export default Backmap;
This is the resulting map from above code:
I also researched how to implement SVG shapes and even maps to React Native and came across several ways of doing so:
react-native-svg(SVG)
React Native ART(SVG)
react-native-simple-maps(map)
Problem
However, I could not implement any map using these solutions. The former two in the above list don't mention any map implementation and the latter one seems to be a very early beta version. So I am wondering if this is even currently possible in React Native. Does anybody know an approach to inserting an SVG map in React native?
It is possible to create d3 maps in react native. The thing is you cant use Dom selectors like d3.select().
You need to use react-native-svg.
Read the documentation here learn how to install and use it. Its implementaion is really close to the browser SVG API.
You import the Svg, G, and Path components
import Svg, { G, Path } from "react-native-svg";
You create a projection and path in the usual d3 manner.
let projection = d3.geoAzimuthalEqualArea()
.center([180, -180]).rotate([0, -90])
.fitSize([width, height], { type: "FeatureCollection", features: features })
.clipAngle(150)
let path = d3.geoPath().projection(projection)
The features are also extracted the same way.
let features = feature(landmass, landmass.objects.land).features
But we dont append components using d3. We append the Svg and G components directly.
return (
<Svg width='100%' height='100%'>
<G>
</G>
</Svg> )
The width and height can be styled as required.
Next the paths are appended but mapping the featurers Array to create path components.
return (
<Svg width='100%' height='100%'>
<G>
{features.map((feature, index) => {
return (<Path d={path(feature)} key={index} stroke="black" fill="grey"></Path>)
})}
</G>
</Svg> )
If you need help in rotating the svg ask in the comments to this answer.

Resources