I am working on a nextJS/Strapi online store and I want to use the react-responsive-carousel I followed all the steps on [ https://www.npmjs.com/package/react-responsive-carousel ].
(I am working on localhost)
The Carousel works fine on the desktop using the dynamic images from Strapi API (Single type collection) but the images don't show on my phone when I try to access the localhost from there. All other images from Strapi work just fine except the ones inside the Carousel.
Carousel code:
import { imgToUrl } from "../../utils/urls";
import { Carousel } from "react-responsive-carousel";
const index = ({ data }) => {
const images = data.slider.map((slide) => imgToUrl(slide.image)); //imgToUrl is a function that takes the image URL and concatinate it with HTTP://localhost:1337/
return (
<>
<Carousel
autoPlay={true}
emulateTouch={true}
infiniteLoop={true}
showThumbs={false}
width="100%"
>
{images.map((image) => (
<div className="h-full w-full">
<img className="w-full h-full" src={image} />
{/* <p className="legend">Legend 1</p> */}
</div>
))}
</Carousel>
</>
);
};
export default index;
imgToUrl Code:
export const API_URL =
process.env.NEXT_PUBLIC_API_URL || "http://localhost:1337";
/**
*
* #param {any} image
*
*/
export const imgToUrl = (image) => {
if (!image) {
return "/Products/3.jpg"; //default image when there is not image uploaded
}
if (image.url.indexOf("/") === 0) {
return `${API_URL}${image.url}`; // Concatinates http://localhost:1337 with the image url
}
return image.url;
};
Output of imToUrl :
http://localhost:1337/uploads/banner_3_d93516ad90.jpg
I would appreciate any help.
Thank you.
I am also using Strapi "headless" CMS as my backend and GraphQL API to retrieve data from frontend, and below is how I have currently implemented my "Strapi Media" Image component:
frontend/utils/media.js
export function getStrapiMedia(url) {
if (url == null) {
return null
}
// Return the full URL if the media is hosted on an external provider
if (url.startsWith("http") || url.startsWith("//")) {
return url
}
// Otherwise prepend the URL path with the Strapi URL
return `${
process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://localhost:1337"
}${url}`
}
frontend/utils/types.js
import PropTypes from 'prop-types'
export const linkPropTypes = PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
newTab: PropTypes.bool,
})
export const mediaPropTypes = PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
alternativeText: PropTypes.string,
mime: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
})
export const buttonLinkPropTypes = PropTypes.shape({
theme: PropTypes.string,
text: PropTypes.string.isRequired,
newTab: PropTypes.bool,
})
frontend/components/elements/image.js
import { getStrapiMedia } from "utils/media"
import Image from "next/image"
import PropTypes from "prop-types"
import { mediaPropTypes } from "utils/types"
const NextImage = ({ media, ...props }) => {
const { url, alternativeText } = media
const loader = ({ src }) => {
return getStrapiMedia(src)
}
// The image has a fixed width and height
if (props.width && props.height) {
return (
<Image loader={loader} src={url} alt={alternativeText || ""} {...props} />
)
}
// The image is responsive
return (
<Image
loader={loader}
layout="responsive"
width={media.width}
height={media.height}
objectFit="contain"
src={url}
alt={alternativeText || ""}
/>
)
}
Image.propTypes = {
media: mediaPropTypes.isRequired,
className: PropTypes.string,
}
export default NextImage
And below is an example use case from another component's context:
...
import NextImage from "./image"
...
<div className="flex flex-row items-center">
<Link href="/">
<a className="h-8 w-32">
<NextImage layout="fill" className="image" media={navbar.logo} />
</a>
</Link>
</div>
...
I hope this helps - let me know if you could use further source share here.
I am just learning the Strapi Framework myself, opting to bootstrap my software engineering portfolio, profile, and blog site from Strapi Next Corporate Starter. Are you also using a Strapi bootstrapped project, or custom using their SDK?
While working on my own custom Carousel I also discovered that if you are implementing your custom component as a section component, you will need to add the import statement and array member to the Strapi defaults in:
frontend/components/sections.js
Example from my use case:
...
import CardCarouselGroup from "#/components/sections/card-carousel-group";
...
// Map Strapi sections to section components
const sectionComponents = {
"sections.hero": Hero,
"sections.large-video": LargeVideo,
"sections.feature-columns-group": FeatureColumnsGroup,
...
"sections.card-carousel-group": CardCarouselGroup
}
...
Related
Following Gatsby's doc on Creating Dynamic Navigation in Gatsby I created a barebones menu and wanted to see if I can add React Icons' components to it:
gatsby-config.js (stripped down)
menuLinks:[
{
name:'home',
icon: 'AiFillHome',
link:'/'
},
{
name:'contact',
icon: 'AiFillHome',
link:'/contact'
}
]
after finding out that Gatsby errors out when I tried creating an external menuLinks file as a module, example:
failed approach as module:
import React from 'react'
// React Icons
import { AiFillHome } from 'react-icons/ai'
const Menu = [
{
name:'home',
icon: <AiFillHome />,
link:'/'
},
{
name:'contact',
icon: <AiFillHome />,
link:'/contact'
}
]
export default Menu
I dont have an issue in my query:
const data = useStaticQuery(
graphql`
query {
site {
siteMetadata {
menuLinks {
name
icon
link
}
}
}
}
`,
)
in a file I've passed down menu props to from my query and then map.
(stripped down file):
{menu.map((menuItem, key) => {
return (
<Link key={key} to={menuItem.link}>
<div>
<span className="icon">{`<${menuItem.icon} />`}</span>
{menuItem.name}
</div>
</Link>
)
})}
my icon doesn't render. I've also tried:
<span className="icon" component={menuItem.icon} />
doesn't work. I've also tried:
<span className="icon"><menuItem.icon /></span>
and:
<span className="icon">{React.createElement(menuItem.icon)}</span>
Research:
Making an HTML string from a React component in background, how to use the string by dangerouslySetInnerHTML in another React component
React render components from string
How to render string as custom React component?
React / JSX Dynamic Component Name
In Gatsby how can I pass an icon's component name to menuLinks and later render it?
Edit
After the answer the implementation of:
{menu.map(({link, name, icon: Icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><Icon /></span>
{name}
</div>
</Link>
)
})}
the browser doesn't render the React Icon Component and when examining the Elements panel I get:
<span class="icon">
<aifillhome></aifillhome>
</span>
Also note I do get a terminal error of:
warning 'AiFillHome' is defined but never used no-unused-vars
which was expected but that leads to me wondering how do I bring in:
import { AiFillHome } from 'react-icons/ai'
Try something like this:
{menu.map(({link, name, icon: Icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><Icon /></span>
{name}
</div>
</Link>
)
})}
Since you are looping through the menu elements, icon is not interpreted as a React element because of the capitalization. In the destructuring above you are parsing icon as Icon so rendering it as a React component in <Icon /> should do the trick because it's the element itself.
There's another workaround that seems a little bit overkill but will work: you can always use a Map to hold the asset component, something like:
const AssetComponentsTuple = new Map([
[`home`, <AiFillHome />],
[`otherAsset`, <AiFillHome2 />],
]);
export default AssetComponentsTuple;
Your menu will become:
const Menu = [
{
name:'home',
icon: 'home',
link:'/'
},
{
name:'contact',
icon: 'otherAsset',
link:'/contact'
}
]
Then in the loop:
{menu.map(({link, name, icon}) => {
let IconComponent = AssetComponentsTuple.get(icon);
return (
<Link key={name} to={link}>
<div>
<span className="icon"><IconComponent /></span>
{name}
</div>
</Link>
)
})}
You can even get rid of icon property and use directly the name in the AssetComponentsTuple, in that case: AssetComponentsTuple.get(name)
Implementation of:
const AssetComponentsTuple = new Map([
[`home`, <AiFillHome />],
[`otherAsset`, <AiFillHome2 />],
])
export default AssetComponentsTuple
would throw an error of:
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: object.
but the approach gave me an idea for this workaround of:
{menu.map(({link, name, icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><IconRetrieve icon={icon} /></span>
{name}
</div>
</Link>
)
})}
and that allowed me to pass the icon string and ternary for the component.
IconRetrieve component:
import React from 'react'
import PropTypes from 'prop-types'
// React Icons
import { AiFillHome } from 'react-icons/ai'
import { MdCall } from 'react-icons/md'
import { BiErrorCircle } from 'react-icons/bi'
const IconRetrieve = ({ icon }) => {
const mapIcons = new Map([
[`home`, <AiFillHome />],
[`contact`, <MdCall />],
[`default`, <BiErrorCircle />],
])
return mapIcons.has(icon) ? mapIcons.get(icon) : mapIcons.get('default')
}
IconRetrieve.propTypes = {
icon: PropTypes.string.isRequired,
}
export default IconRetrieve
The positive of this approach is I can create SVG components or any component in that matter and return it based on the icon string.
I am making a simple website and I would like to fetch data from an API and display it on my component.
The problem is that the getStaticProps() method is never called.
Here is the code of the component :
import React from "react";
import {GetStaticProps, InferGetStaticPropsType} from "next";
type RawProject = {
owner: string;
repo: string;
link: string;
description: string;
language: string;
stars: number;
forks: number;
}
function Projects({projects}: InferGetStaticPropsType<typeof getStaticProps>) {
console.log(projects);
return (
<section id="projects" className="bg-white p-6 lg:p-20">
<h1 className="sm:text-4xl text-2xl font-medium title-font mb-4 text-gray-900 pb-6 text-center">
Quelques de mes projets
</h1>
{/*
<div className="container px-5 mx-auto">
<div className="flex flex-wrap">
{rawProjects.map((project: RawProject) => (
<ProjectCard
title={project.repo}
language={project.language}
description={project.description}
imageUrl="https://dummyimage.com/720x400"
repoUrl={project.link}
/>
))}
</div>
</div>
*/}
</section>
);
}
export const getStaticProps: GetStaticProps = async () => {
console.log("getStaticProps()");
const res = await fetch("https://gh-pinned-repos-5l2i19um3.vercel.app/?username=ythepaut");
const projects: RawProject[] = await res.json();
return !projects ? {notFound: true} : {
props: {projects: projects},
revalidate: 3600
};
}
export default Projects;
The full code can be found here : https://github.com/ythepaut/webpage/tree/project-section
I am not sure if the problem is caused by the fact that I use typescript, or that I use a custom _app.tsx
I tried the solutions from :
https://github.com/vercel/next.js/issues/11328
How to make Next.js getStaticProps work with typescript
but I couldn't make it work.
Could someone help me please ?
Thanks in advance.
getStaticProps() is only allowed in pages.
Your code at the moment is :
import Hero from "../sections/Hero";
import Contact from "../sections/Contact";
import Projects from "../sections/Projects"; // you cannot call getStaticProps() in this componenet
function HomePage(): JSX.Element {
return (
<div className="bg-gray-50">
<Hero />
<Projects />
<Contact />
</div>
);
}
export default HomePage;
Instead call getStaticProps() inside index.tsx and pass the props to the component something like this ::
import Hero from "../sections/Hero";
import Contact from "../sections/Contact";
import Projects from "../sections/Projects";
function HomePage({data}): JSX.Element {
return (
<div className="bg-gray-50">
<Hero />
<Projects data={data} />
<Contact />
</div>
);
}
export const getStaticProps: GetStaticProps = async () => {
console.log("getStaticProps()");
const res = await fetch("https://gh-pinned-repos-5l2i19um3.vercel.app/?username=ythepaut");
const projects: RawProject[] = await res.json();
return !projects ? {notFound: true} : {
props: {projects: projects},
revalidate: 3600
};
}
export default HomePage;
Data fetching methods in NextJs like getStaticProps runs only on the server. Hence it works only in pages, not in a regular react component
Please Check their docs
For data fetching in Normal Components, You can only do client-side Rendering. NextJS recommends using this library SWR
According to their docs
SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally, come with the up-to-date data.
You can only use getInitialProps, getServerSideProps, getStaticProps in Next.js pages
I checked your project and saw that your Project.tsx was in a component folder, but it needs to be in pages folder for those functions to work.
I got a similar bad experience because of bad service worker implementation, if you found it works with "Empty Cache and Hard Reload", you should check your service-worker code, you may don't want to cache any pages.
In pages folder you must export getStaticProps too
Example
export { default } from '../../games/tebak-kata/game';
export { getStaticPaths } from '../../games/tebak-kata/game';
export { getStaticProps } from '../../games/tebak-kata/game';
I'm using react to create a web-app that has to be static so no API calls and I need to store some data client-side (I'm currently just using a single JSON file as it's really not large) but I'm struggling to pass images down, I'm storing the references as strings in the JSON file and passing them to the props but I'm getting all sorts of errors. Is there a better or more practical way to do this?
Code (I've added a variable to imitate the JSON structure):
const HomePage = () => {
let projectInfo: ProjectInfo = {
title: "Project",
description:
"A progressive web app.",
imgURL: "./TODO", //<- HERE
mainColour: "#323232",
secondaryColour: "#464646",
};
return (
<div id="carousel-container">
<Carousel labels={["1"]}>
<ProjectPanel info={projectInfo} />
</Carousel>
</div>
);
};
export default HomePage;
interface IProjectPanelProps {
info: ProjectInfo;
}
const ProjectPanel = (props: IProjectPanelProps) => {
return (
<div className="project-panel">
<h2>{props.info.title}</h2>
<p>{props.info.description}</p>
<img src={/* ?? */} alt=""></img>
</div>
);
};
You can use base64 images to store them.
See this post https://stackoverflow.com/a/42399865/1356340 for examples of how to use binary images in React.
You can import your image in your home page as a variable and pass that variable to your child component.
import myImage from 'your_image_path';
const HomePage = () => {
let projectInfo: ProjectInfo = {
title: "Project",
description:
"A progressive web app.",
imgURL: myImage
mainColour: "#323232",
secondaryColour: "#464646",
};
return (
<div id="carousel-container">
<Carousel labels={["1"]}>
<ProjectPanel info={projectInfo} />
</Carousel>
</div>
);
};
export default HomePage;
interface IProjectPanelProps {
info: ProjectInfo;
}
const ProjectPanel = (props: IProjectPanelProps) => {
return (
<div className="project-panel">
<h2>{props.info.title}</h2>
<p>{props.info.description}</p>
<img src={info.imgURL} alt=""></img>
</div>
);
};
My Home page sends data from my strapi cms to my PostSlider component via props
import React from "react";
import styles from './index.module.scss'
import { AxiosService } from '../utils/axios-service'
import PostSlider from '../components/postSlider/postSlider'
const Home = ({ posts }) => {
return (
<div id='contentsWrap' className={styles.dohandsWrap}>
<PostSlider home={true} posts={posts} />
</div>
)
}
export default Home
export async function getStaticProps() {
const axios = AxiosService.create()
const res = await axios.get('/archives', {
params: {
category: 'news',
display: true,
showDoson: true,
_limit: 5,
_sort: 'id:DESC'
}
})
return {
props: {
posts: res.data,
},
}
}
My postSlider component then maps over the data to fill my slider
import React from "react";
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import styles from './postSlider.module.scss'
import Link from 'next/link'
import Image from 'next/image'
export default function PostSlider({ home, posts }) {
var settings = {
infinite: posts.length > 2 ? true : false,
autoplay: false,
speed: 500,
autoplaySpeed: 3000,
slidesToShow: 3,
slidesToScroll: 1,
};
return (
<section className={`${styles.postSlider} postSlider ${home ? styles.postSliderHome : 'postSliderNotHome'} ${posts.length > 2 ? 'postSliderPadding' : ''}`}>
<Slider {...settings}>
{posts.map((post) => {
const date = new Date(post.displayDate);
return (
<Link key={post.id} href={`/news/${post.id}`}>
<a className={styles.postSliderLink}>
<article>
<Image src={post.images[0]?.url} alt={post.images[0]?.alternativeText} width={376} height={190} layout="fixed" />
</article>
</a>
</Link>
)
})}
</Slider>
</section>
);
}
I made sure to include my cdn address in module.exports in next.config.js but I get the following error
Error: Image is missing required "src" property. Make sure you pass
"src" in props to the next/image component. Received:
{"width":376,"height":190}
If I remove the next/image component for the normal img tag, everything works fine.
What am I doing wrong?
Well, it seems like one of your posts have empty images array?
Image component is required to have src property and you pass undefined instead.
You can check if there is at least one image and then render it, like that:
<article>
{post.images.length > 0 && (
<Image src={post.images[0].url} alt={post.images[0].alternativeText} width={376} height={190} layout="fixed" />
)}
</article>
Try before the return:
const src = {src: post.images[0]?.url}
Then inside the return:
<Image {...src} //etc...
Sometimes, the <Image /> tag doesn't work like it should and doesn't accept the src . Try defining the URL before return and then pass the URL in the src property.
Just before return:
const url = post.images[0]?.url;
And then you can add:
<Image src={url} alt={post.images[0]?.alternativeText} width={376} height={190} layout="fixed" />
Strapi has an option to make an image required or not, but regardless of whether you select the required button, it makes it required and renders a null value in gatsby. How can I programmatically tell gatsby to render just the rich text if that is selected and ignore the null image value if not picture is uploaded? I wanted to do something like {image && ()}, but not sure how to map that..
import React from "react"
import { graphql } from "gatsby"
import ReactMarkdown from "react-markdown"
import Image from "gatsby-image"
//import Layout from "../components/layout"
const ComponentName = ({ data }) => {
const { title, layout } = data.strapiBasics;
return (
<div>
<h1>{title}</h1>
{layout.map(item => {
return (
<span key={item.id}><ReactMarkdown source={item.rich_text} />
<Image fluid={item.image.childImageSharp.fluid} />
</span>
)
})}
</div>
)
}
export const query = graphql`
query BasicQuery($slug: String!) {
strapiBasics(Slug: {eq: $slug}) {
title: Title
slug: Slug
layout: Layout {
id
rich_text
image: Image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
export default ComponentName
Something like this should do the trick:
return (
<div>
<h1>{title}</h1>
{layout.map(item => {
return (
<span key={item.id}><ReactMarkdown source={item.rich_text} />
{item.image && <Image fluid={item.image.childImageSharp.fluid} />}
</span>
)
})}
</div>
);
Note: you may need to change the item.Image to check if it meets the condition.
Since you are aliasing your GraphQL query:
image: Image {...}
Your object will Image, not image, and so on with the rest.
You don't need to add the && logical operator inside, you just can return an image if it exists or return a markdown if don't.