Run async code in nextjs component without using UseEffect - javascript

In my nextjs app, I have a StrapiImage component that accepts an image object from my strapi backend api as a prop. It assigns the width, height and url + any aditional props. (It's basically a shortcut for a regular next/image)
import Image from "next/image";
import { getStrapiMedia } from "../../lib/media";
export default function StrapiImage({ image, ...props })
return <Image src={getStrapiMedia(image)} // the getStrapiMedia function basically just returns the url to the image.
width={image.attributes.width}
height={image.attributes.height}
alt=""
{...props} />
}
Now, I wanted to add a blur placeholder for this image using placeholder="blur" but since this is an external image, I have to provided a base64 image in the blurDataUrl prop.
I wanted to generate an image like this with the plaiceholder library similarly to this sollution I found.
There's a problem with this though, to generate the image, I need to use an await statement. In getStaticProps, this wouldn't be a problem as I could just make the function async, but I'm doing this inside a component and components must regular non-async functions.
The 'obvious' sollution would be to use the useEffect hook like so:
import Image from "next/image";
import { getStrapiMedia } from "../../lib/media";
import { useEffect, useState } from "react";
import { getPlaiceholder } from "plaiceholder";
export default function StrapiImage({ image, ...props })
const [blur64, setBlur64]
useEffect(() => {
async function generatePlaceholder() {
// generate base64 image here
}
generatePlaceholder();
}, [])
return <Image src={getStrapiMedia(image)}
width={image.attributes.width}
height={image.attributes.height}
alt=""
placeholder="blur"
blurDataURL={blur64}
{...props} />
}
This would technically work, but it would only run only on the client-side, ultimately defeating the purpose of ssr and the whole image optimisation. (Especially considering plaiceholder's huge js bundle)
Is there any other way that I could do this without useEffect but still in the component file? Ideally, I'd like it to run only on the server-side.
I'm using nextjs#13

I fixed the issue by using a strapi plugin to generate the placeholder image on the backend rather than on the frontend.
The issue in the title still remains unsolved though. Running pre-renderable async code at component-level doesn't seem to be officialy supported by nextjs. (with the pages directory)
Here's some "sollutions" I've found: (mostly workarounds)
Generate the placeholders on page-level. For me, that would mean looping through all elements in my api data looking for images and generating placeholders for them. It's pretty inconvenient but it does work.
Switch to the app directory. (provided you're using next 13) It uses react server components so all of this is possible and simple to do there.
Try to work with useSSE. I haven't looked into this too much but it looks like you can use the useSSE library to run server-side at component-level. Here's an article covering this.

Related

too much render with react markdown

I am using React Markdown (https://www.npmjs.com/package/react-markdown) to render markdown content in my NextJS project.
When I refresh I have two "toto" & "titi" in my terminal... It is normal or what's wrong with this code?
import Head from 'next/head';
import ReactMarkdown from 'react-markdown';
function Section ({ data }) {
const content = JSON.parse(data.markdown);
const {
title,
sortContent
} = data;
console.log('toto');
return (
<>
<main>
<h1>{title}</h1>
<h1>{sortContent}</h1>
<ReactMarkdown source={content.default} escapeHtml={false} />
</main>
</>
)
}
export async function getServerSideProps (context) {
const json = await import('../../content/article1/data.json');
const content = await import('../../content/fr/article1/content.md');
console.log('titi');
return {
props: {
data: {
title: json.title_content,
sortContent: json.short_content,
markdown: JSON.stringify(content)
}
}
}
}
export default Section
It's part of Reacts development tooling, StrictMode. It is expected and only applies in development mode. You can remove the StrictMode to see it only render the expected number of times, but obviously you lose some development tooling. This tooling can warn you about certain unsafe or unwise practices you might want to avoid such as using legacy APIs.
More details here:
Reactjs Docs
A blog with a good overview
If this is truly the only code you have, then it looks like it's normal. You may have other code that uses these components and that's why in shows twice. But based off the code you have right there, there's no bug.
This is a known side-effect of using React.StrictMode, only in debug mode. You can read more about this here.
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState) Functions passed to useState, useMemo, or useReducer

How to pre-fetch a component for later in React?

I have a component that takes a long time to load, when the user loads my homepage and when there is noting else left for the browser to do, I want to pre-fetch this heavy component in the background so that when the user clicks the link to load it, it's there instantly.
Is there a way to do this eager pre-fetching for later in React?
Code splitting implementation when using mapbox-gl with react-app using React.lazy and Suspense
import React from 'react';
import mapboxgl from 'mapbox-gl';
// IMPORTANT Please include the css required for mapbox-gl in the same file
import 'path-to-vendor-css/_mapboxgl.external.css';
function MyMapbox() {
// your map implementation
}
Now lazy-load the component where you want to use it.
import {lazy, Suspense} from 'react';
const MyMapbox = lazy(() => import('path-to-the-mapbox-component'))
// usage
//fallback prop displays "loading..." content
<Suspense fallback={<div>Loading...</div>}>
<MyMapbox/>
</Suspense>

Conditionally import components in Gatsby for very dynamic template

I'm planning to build a very dynamic single page website template for Gatsby.
The theme will have many block-types componentes (30 or more) available, like:
Jumbotron
JumboTron2
JumbotronWithForm
MapWide
MapSquare
...
I intend to render the page based on an array of objects, each one having a type (like Jumbotron) to match a component and the necessary data to render it.
If I import all these componentes statically...
import Jumbotron from './../components/Jumbtron';
...they all will be included in the generated JS, which is bad because my JS will be too large.
If I use dynamic imports...
async componentDidMount(){
if(jumbotronTypeRequired){
this.jumbotron = await import('./../components/Jumbotron');
}
this.setState({ dynamicComponentsLoaded: true });
}
render(){
if(this.state.dynamicComponentsLoaded){
//render all
}
else{return (<div>Loading...</div>)}
}
...I can only fetch the component in a Promise, this means that the component will fully rendered after the ComponentDidMount, which is bad for SEO because my resulting HTML will not contain the fully rendered data.
Does Gatsby has a way to include only the necessary components to render the dynamic page while keeping a fully rendered HTML (not just render "Loading...")?
You can use the plugin gatsby-plugin-loadable-components-ssr to achieve this. There will however be a reference to the file path of each component in the bundle but at least the actual components do not get bundled.

Next.js: How to dynamically import external client-side only React components into Server-Side-Rendering apps developed?

I know this question has been asked multiple times before but none of the solution seems to work.
I'm trying to use the library 'react-chat-popup' which only renders on client side in a SSR app.(built using next.js framework) The normal way to use this library is to call import {Chat} from 'react-chat-popup' and then render it directly as <Chat/>.
The solution I have found for SSR apps is to check if typedef of window !=== 'undefined' in the componentDidMount method before dynamically importing the library as importing the library normally alone would already cause the window is not defined error. So I found the link https://github.com/zeit/next.js/issues/2940 which suggested the following:
Chat = dynamic(import('react-chat-popup').then(m => {
const {Foo} = m;
Foo.__webpackChunkName = m.__webpackChunkName;
return Foo;
}));
However, my foo object becomes null when I do this. When I print out the m object in the callback, i get {"__webpackChunkName":"react_chat_popup_6445a148970fe64a2d707d15c41abb03"} How do I properly import the library and start using the <Chat/> element in this case?
Next js now has its own way of doing dynamic imports with no SSR.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
Here is the link of their docs: next js
I've managed to resolve this by first declaring a variable at the top:
let Chat = ''
then doing the import this way in componentDidMount:
async componentDidMount(){
let result = await import('react-chat-popup')
Chat = result.Chat
this.setState({
appIsMounted: true
})
}
and finally render it like this:
<NoSSR>
{this.state.appIsMounted? <Chat/> : null}
</NoSSR>
You may not always want to include a module on server-side. For
example, when the module includes a library that only works in the
browser.
Import the library normally in child component and import that component dynamically on parent component.
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
This approach worked for me.

importing images locally in react

I am not understanding how import works in React. I'm currently using create-react-app. I have a folder inside components folder named images:
- src
- components
- images
Insides the components folder I also have a js file named ProductSquare.js.
I am trying to import a picture from images folder using this line
import bumper from './images/bumper1.png';
I have tried changing it to only bumper1.png and many other options. What am I doing wrong here? ( would appreciate a better way to do it in the future).
Is this how programmers import pictures in the real world?
Try using <img src={require('./images/bumper1.png')} />
P.S.
The similar question goes here
If you are using something like Webpack (which create-react-app does) then yes, you can just import the images (and most other file types). They end up getting converted into something (I believe images end up as a data URL).
If your folder structure looks like this:
src
components
ProductSquare.js
images
Then you should be able to use the image like this:
// in ProductSquare.js
import React from 'react';
import bumper from './images/bumper1.png';
class ProductSquare extends React.Component {
render() {
return <img src={ bumper } />;
}
}
If it isn't, double-check that all of the files are named what you think they should be (check for typos and what not).
If everything is correct and it isn't working, try just adding console.log(bumper) before the class. Then:
if it outputs a string that looks like data:blahblah it should work
if it outputs undefined, the image may not be named properly
if it doesn't output, you might not be using ProductSquare correctly
Using the public Folder
Note: this feature is available with react-scripts#0.5.0 and higher.
Create a folder in public folder, for example, 'image', add your photos there.
After that wherever you want you can use image address like this:
<img className="img1" src="/image/pic.jpg" alt=""/>
You Need to use .default after require
<img src={require('./images/bumper1.png').default} />

Categories