Component Render Issues (Long Read) - reactjs

I am dynamically getting data from my database to display on my website but I've encountered a problem and don't know how to fix it.
I have a main Home page which loads in a bunch of tiles that you can click on and take you to another page on my SPA. Some of the tiles you can click on can render STL objects from a file or some tiles will not when clicked on.
I have encountered an issue with my components that render STL files.
Issues?
Clickable Component is Multi Rendering my Model Component (or something like that?)
I have unmount and remount <Model/> for each page view?
General Issue with Project Setup?
Issue with STL (Model) component can be found at the bottom
Provided
Home.js (main component)
ClickableImage (component that returns [])
Layout.js (dynamic content pages)
Model.js (Component that loads my model)
Reproducible Code Current Issue CodeSandBox
Updates
11:03pm Feb 23 : Added in CodeSandBox
codesandbox code explanation. 2 Images are displayed, clicking on first image will take you to a layout that doesn't load in the STL file. The second image does. That's when everything breaks.
In Model.js under `/src/components/Layout/
Home.js
class Home extends Component {
render() {
const {projects} = this.props;
return (
<section className="max-container">
<div className="home-layout">
<div className="grid pl-4 pr-4">
<ClickableImage projects={projects}/>
</div>
</div>
</section>
);
}
}
ClickableImage
const ClickableImage = ({projects}) => {
const mappedData = projects && projects.map((project, index) => {
return (
<Link to={'/project3D/' + project.projectId} key={index}>
<img
src={project.banner}
alt="img" className="clickImage"/>
</Link>
);
})
return (
mappedData
);
}
Layout.js (/project3D/)
...
<div className="content-div">
<Canvas camera={{ position: [0, 10, 100] }}>
<Suspense fallback={null}>
<Model url={"./RaspberryPiCase.stl"} />
</Suspense>
<OrbitControls />
</Canvas>
</div>
...
So that is my general layout of everything on my page.
Home -> Click Image -> Layout Page.
Now this is where things get a little weird.
The Canvas portion on my layout page gives me this error when trying to load it.
Uncaught invalid array length react-reconciler.development.js:7648
The above error occurred in the <Model> component:
Model#http://localhost:3000/static/js/main.chunk.js:2430:15
Suspense
Canvas#http://localhost:3000/static/js/0.chunk.js:137042:66
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
But when I copy the Canvas section to my Home Component it will render my STL file with no problem and then it will also load my STL file on my other page as well.
Here are images to show you the problem without pasting <Canvas> in my home component.
PreHome Paste
After Pasting Canvas in Home Component
Model.js
import React, {useEffect, useRef} from "react";
import {STLLoader} from "three/examples/jsm/loaders/STLLoader";
import {useLoader, useThree} from "react-three-fiber";
export const Model = ({url}) => {
const geom = useLoader(STLLoader, url);
const ref = useRef();
const {camera} = useThree();
useEffect(() => {
camera.lookAt(ref.current.position);
}, [camera]);
return (
<>
<mesh ref={ref}>
<primitive object={geom} attach="geometry"/>
<meshStandardMaterial color={"orange"}/>
</mesh>
<ambientLight/>
<pointLight position={[10, 10, 10]}/>
</>
);
};
I just looked into it some more but apparently useLoader is what's most likely throwing my error, and I don't know why. (Only know this because when I comment it out the errors go away)
lmk if more info is required.

The problem is that useLoader() is having issues fetching the file you are providing in the url variable.
In your example, the value you end up providing as "url" is "./RaspberryPiCase.stl".
To fix your issue, simply provide the full absolute URL where your STL file can be fetched from.
Based on your provided Codesandbox, it's on your project /public folder, so a simple way to fix your issue is doing:
export const Model = ({url}) => {
const fullUrl = `${window.href.origin}${url.replace(".", "")}`;
const geom = useLoader(STLLoader, fullUrl);
// ...
See it live on a fork of your Codesandbox.
Additionally, you can follow this discussion in case your actual resource URL was not actually public (ie. required some kind of authentication). In that scenario, you can fetch the resource .gtl file first as an arrayBuffer, and then pass it to your loader.

Related

Use <a> tag to scroll down to a differnet component in single page react app

I'm creating a portfolio website using React.
It is a single page website and each section of the website is its own separate component.
My navbar component has links at the top that when clicked I want the page to scroll to the corresponding section further down the page. A traditional About is not working. Do I need to use react-router-dom or am I just missing something?
const App = () => {
return (
<div className='app'>
<Navbar />
<Hero />
<About />
<Projects />
<Footer />
</div>
)
}
export default App
You can use the refs to scroll the page to a section . Refs in react have many applications , you can read details on react official page. Here is an example to use ref for scrolling
const Scroll = () => {
const scrollTo = useRef();
return (
<div>
<button onClick = {()=>{scrollTo.current.scrollIntoView()}></button>
.
.
// Your Other divs
.
.
<div ref={scrollTo}>Scrolled</div>
</div>
);
}
You can use the window.scroll() for scrolling page to a coordinate
window.scroll({top:600,left:50,behavior:'smooth'})
Don't know what I was doing wrong but as Drew suggested in his answer, the classic <a> tag does work as expected.
Go forth and use those tags fellow React devs!

React - How to show another page on clicking a React Material Datagrid

I am a complete newbie in React. Any help will be greatly appreacited.
I have a React MAterial Datagrid which list of users. When I click a row in the datagrid, it should show the in depth details of the user in another window. I should be able to edit the data, save the data and come back to the data grid again.
Edit: Here the application has a header and a sidebar which should also be displayed when routing to other page happens. The new page should open in the same window with the header and sidebar. In the component I have added in the element={User} I have implemented the logic to display the header and sidebar. The routing does not happen with this code. I find it difficult to identify the mistake I did.
I did implement the code like this and did debugging. The code flows through my function onRowClick, but no component is displayed. Please help me with the mistake I do.
Posting only the part of the code I work:
const Users = () =>{
const handleUserClicked: GridEventListener<"rowClick"> = async ()
..... // Here all the logic to retrieve the data for the Grid is implemented.
}
const openUserDetails = () => {
return (
<div>
<Routes>
<Route path="/" element={<User />} />
</Routes>
</div>
);
};
return (
<React.Fragment>
{selectedCustomer && (
<p> Ausgewählter Nutzer: {selectedCustomer.Username}</p>
)}
<DataGrid
rows={users}
columns={columns}
pageSize={16}
rowsPerPageOptions={[5]}
getRowId={(row: any) => row.name}
autoPageSize={true}
rowHeight={45}
onRowClick={handleUserClicked} {...openUserDetails} // I did try even adding on the onRowClick={openUserDetails}
hideFooterSelectedRowCount={true}
/>
</React.Fragment>
);
}
enter code here
if the result of getRowId is id and this id is equal to your record primary key id you can do like this:
for example, your back-end server for getting post detail is: https://exampl.com/posts/{id}
if you want to see detail in a new tab, you can put the MAU datagird given id into this URL and then open this in a new tab by:
window.open('/yourUrl with post id','_blank')
if this description is not your answer, you should ask your questions better
You can navigate to another path by using useNavigate
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
const openUserDetails = () => {
navigate("/<your path here>");
};
<DataGrid
rows={users}
columns={columns}
pageSize={16}
rowsPerPageOptions={[5]}
getRowId={(row: any) => row.name}
autoPageSize={true}
rowHeight={45}
onRowClick={openUserDetails}
hideFooterSelectedRowCount={true}
/>
useNavigate documentation

React file upload behaviour duplicated across components

I have created an image upload thingamajig called ImageSelector as a reusable component. It's for a custom CMS where the user first selects a header image, then selects several images for a gallery.
So the top part of the form uses the <ImageSelector /> component and the bottom part of the form uses an array of the same component. But for some reason whenever I select an image using the second ImageSelector at the bottom of the form, it only changes the image for the first ImageSelector.
It surely can't be the components' state that is bound together as I've never experienced that to be a problem, so it must be something to do with the way the browser caches files right? My question is, how can I make a reusable image upload component in React and avoid this duplication behaviour?
ImageSelector component:
(styles removed for brevity)
import { useState } from "react";
import { AiFillCamera } from "react-icons/ai";
import styled from "#emotion/styled";
export default function ImageSelector({ placeholder, shape }) {
const [currentImage, setCurrentImage] = useState();
const [currentImageUrl, setCurrentImageUrl] = useState();
function handleChangeImage(e) {
setCurrentImage(e.target.files[0]);
setCurrentImageUrl(URL.createObjectURL(e.target.files[0]));
}
return (
<ProfileImage $shape={shape}>
<div>
<ProfileImageOverlay $image={!!currentImage}>
<Label htmlFor="selector-image" $image={!!currentImage}>
{placeholder ? placeholder : <AiFillCamera />}
</Label>
<input
id="selector-image"
type="file"
name="selector-image"
onChange={handleChangeImage}
/>
</ProfileImageOverlay>
{currentImage && (
<img src={currentImageUrl} alt="selected image" width="170px" height="170px" />
)}
</div>
</ProfileImage>
);
}
Turns out this duplicate behaviour is what happens when you forget to change the html id attribute of subsequent instances of the input field!
Derp.

How should I implement lazy loading for my images in react?

I'm trying to add lazy loading to my react application as I have over 200 images that I don't need on the initial load. If I lazy load the images, does this mean, they won't be loaded until they're needed on the screen?
I'm currently importing 4 groups of about 50 images into .js files and then adding them to objects so they can be dynamically used in my components. It looks like this...
// SportImages.js file
import Football from './images/football.png
import Cricket from './images/cricket.png
import Soccer from './images/soccer.png
... // another 50 or so imports
export default {
Football,
Cricket,
Soccer,
... // another 50 or so files
}
// and then in my component
cards.map(x => {
return (
<img src={SportImages[x.image_ref]} />
)
})
So my question is, should I be using lazy loading in the component file or in the main images file if I want to lazy load every image?
You can add the loading attribute to your image element to lazy-load the images. A complete explainer guide on this attribute can be found on web.dev.
In your case it would probably look as follows:
cards.map(card => (
<img src={SportImages[card.image_ref]} loading="lazy" />
))
You should also make sure to specify the width and height attributes so the browser can place the element on the page with the correct dimensions.
You can use this library called react-lazy-load-image-component
Import the LazyLoadImage component then use it the render you images
cards.map(card => (
<LazyLoadImage src={SportImages[card.image_ref]} {...props}/>
))
You can also add an effect when lazy loading the images. You can check the documentation of the library here
https://www.npmjs.com/package/react-lazy-load-image-component
loading='lazy' attribute Safari not supported
<img loading=lazy src="image.jpg" />
Try this package:
npm i react-lazy-load-image-component
Example:
import React from 'react';
import { LazyLoadImage, trackWindowScroll } from 'react-lazy-load-image-component';
const Gallery = ({ images, scrollPosition }) => (
<div>
{images.map((image) =>
<LazyLoadImage scrollPosition={scrollPosition}
width="100%"
height="auto"
src={image.url} />
)}
</div>
);
export default trackWindowScroll(Gallery);

Gatsby page in page

I'm using gatsby js and trying to figure out how to have a page level side bar with Gatsby links that render a new component inside a div in the same page I can do this using react-router-dom but in Gatsby all I can find is how to create blog posts which is driving me nuts as every tutorial I find is the same blog post.
Here is my layout page /layouts/index.js
export default ({ children }) => (
<div id="layout">
<header>
<h3>Header</h3>
<MainNav />
</header>
{children()}
</div>
)
About Page
/pages/about.js
export default ({ location, match }) => {
console.log('location = ', location, 'match = ', match );
return (
<div id="about">
<SideBar />
<div id="content">
// . add child template or component for link clicked in sidebar
</div>
</div>
);
};
What I'm trying to do is when a user clicks on a link in the side bar stay on about but render a new component or template based on the gatsby-link clicked in the about sidebar.
The About SideBar component
/components/about/side-bar.js
const SideBar = () => {
return (
<div id="side-bar">
{/* <li><Link to='/about?sort=name'>work</Link></li> */}
<li><Link to="/about/work">work</Link></li>
<li><Link to='/about/hobbies'>hobbies</Link></li>
<li><Link to='/about/buildings'>buildings</Link></li>
</div>
)
}
Problem with the links above, they are trying to go to a new page called.
/about/work
This is not what I'm trying to do. Again I'm trying to make it stay on about but render a new component inside the content div.
Please help gatsby is so all over the place as far as docs goes. ok maybe its just me and not getting the docs clearly.
Thanks
UPDATE:
I tried adding a page suing createPage which works for me kind of but it doesn't pass the match.params id
gatsby-node.js
exports.createPages = ({ boundActionCreators }) => {
const { createPage } = boundActionCreators;
const myComponent = path.resolve('src/pages/about/index.js');
createPage({
path: '/about/:id',
component: myComponent
})
}
After a long time of trying to understand Gatsby and I can say I still don't as its docs are vast and not very clear. But once I started to look at the node-apis and onCreatePage it gave me some ideas. This is what the docs literally say.
onCreatePage
Called when a new page is created. This extension API is
useful for programmatically manipulating pages created by other
plugins e.g. if you want paths without trailing slashes.
So the only part in here that gives me a hint of this might be the key to helping me is this line. useful for programmatically manipulating pages created by other
plugins
Anyway this got me writing some code at least. Then about 3 hours later I found a plugin that was doing exactly what I was trying to do with this method. The plugin is called gatsby-plugin-create-client-paths key here is client-paths!!!!!
This makes life worth living! So in my case above I just wanted to be able to use Gatsby's router ( which is just react-router-dom behind the scenes), to pass me and id or value to routers match.params object. It still doesn't but what it does do is checks for any path after a prefix like /folder/ in my case '/about/work and recreate the page with a template component (in my case keep using pages/about/index.js), which is my template. Now that we have about/index.js rendering for ever link after /about/ then we can use some internal switch statement to handle the location that is been passed to /about/index.js. Still don't get match.params update but I do get props.location.pathname; which allows me to extract everything after the prefix to use in a switch statement to render my specific components based on the routes pathname. Enough rabbiting on here is a rough solution to show as an example.
So add the plugin as an npm install.
open up gatsby.config.js and add the below code to the exports.
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/about/*`] },
},
]
}
Then in my main about page pages/about/index
import React from "react";
import SideBar from '../../components/about/side-nav';
export default (props) => {
const { pathname } = props.location;
var n = pathname.lastIndexOf('/');
var pageId = pathname.substring(n + 1);
const page = () => {
switch(pageId){
case '':
return (
<div>Work Page</div>
);
case 'work':
return (
<div>Work Page</div>
);
case 'hobbies':
return (
<div>Hobbies Page</div>
);
case 'buildings':
return (
<div>buildings Page</div>
);
}
}
return (
<div id="about">
<SideBar />
<div id="content">
{page()}
</div>
</div>
);
};
Then in my sidebar I call it like this.
<li><Link to="/about/work">work</Link></li>
<li><Link to='/about/hobbies'>hobbies</Link></li>
<li><Link to='/about/buildings'>buildings</Link></li>
Hopefully this will help someone else out. After all this I'm starting to really question the bulk of gatsby especially with docs not been very clear. Based on the response to my question I guess not many people in stackoverflow's community are using Gatsby which is worrying when you need help. It does look like Gatsby's github community is very helpful but that should be for bug issues and not for questions like mine, but encouraging to see.
Hope this helps someone.

Resources