Creating custom component with <img> for markdown gatsbyjs - javascript

I'm trying to create a custom component for my markdown that accepts an image source. I am unable to display the image via the custom component because the image is not found because it doesn't exist
I also realised the image path is generated by GatsbyJS and I have no idea how to retrieve the path of the image in markdown.
I do have a custom component that holds some text but I couldn't do the same thing for images.
Here is a simple markdown with a title and a few words.
index.md
---
title: ToDoApp
---
Hi this is my todoapp app. Below is a bunch of screens
<imageholder src='./screen1.png'></imageholder>
![Image from Gyazo](./screen1.png) <!-- it displays... -->
I've created a custom component named imageholder where it holds some logic (in a near future...) in displaying the image
ImageHolder.js
import React from "react"
export default class ImageHolder extends React.Component {
render() {
return (
<img src={this.props.src} alt="Logo"/>
)
}
}
project-post.js
const renderAst = new rehypeReact({
createElement: React.createElement,
components: {
"imageholder": ImageHolder
},
}).Compiler
And I received this...

This is really tricky since (AFAIK) you can't pass props from page component to custom component with rehype-react. I think you'd need to do something similar to gatsby-remark-images, which locates the images' paths and set them.
I wrote this plugin that mimics gatsby-remark-images, but for custom components like in your case.
Here's the default setting, you can override the component name and pass in additional image transformation options.
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-custom-image-component`,
options: {
// plugin options
componentName: 'image-wrapper',
imagePropName: 'src',
sharpMethod: 'fluid',
// fluid's arguments, see gatsby-plugin-sharp docs
quality: 50,
maxWidth: 800,
}
},
],
},
},
Then use it in your markdown:
<image-wrapper src='./hero.jpg'></image-wrapper>
And get the image props in your custom component.
//src/components/ImageWrapper.js
import React from 'react'
// the result of sharp's image transformation will be passed directly to this component.
// so if you use `fluid` as `sharpMethod`, you'll get
// src, srcSet, base64, aspectRatio, srcSetType, sizes, density, originalImage.
// Please refer to `gatsby-plugin-sharp` docs.
const ImageWrapper = ({ src, srcSet }) => <img src={src} srcSet={srcSet} />
export { ImageWrapper }

The issue is that props are passed as strings to rehype - the component doesn't receive the asset hashed value when the markdown is processed and built by Gatsby. So, the prop isn't the same as the image tag's src once you build the site, and it's not finding the asset hashed file.
This plugin, Gatsby Remark Copy Linked Files, moves your referenced asset files to a public folder, and passes the correctly hashed asset path, but by default only for img, a, audio, and video tags (not for custom components).
To solve for this, move the plugin from node_modules into a /plugin folder in the project root, and add the desired custom components and props at this line. In your case, it looks like it would be:
// Handle a tags.
extractUrlAttributeAndElement($(`a[href]`), `href`).forEach(processUrl)
// Manually added custom tags
extractUrlAttributeAndElement($(`imageholder[src]`), `src`).forEach(processUrl)
Obviously this would be better served as an option for the plugin in a configuration block in gatsby-config, but this worked for me in a pinch.

Related

How to create a global string in a vue (webpack) project

In my vue project I have a default image that should show up in places around the app when an image is missing or hasn't been uploaded yet. I have various class files and component calls that want to use the same image.
What is the best way to create a global variable that I can access from anywhere? I can't put it in the store because my class files (I'm using vuex-orm) are loaded before the store is declared. I would prefer not to put it in the window because I want to have a single word variable that I can call (defaultImg as opposed to window.my_project.globals.defaultImg). I could put it in .env but I want this to be checked into the repository and the same across environments so that doesn't seem right either.
What is the best way to provide a simple global string to all files in my vue project?
Are you using Vue 2 or 3? TL;DR best approach is to create a component.
Quick Fix
You could take the approach described in the documentation for creating plugins:
https://vuejs.org/guide/reusability/plugins.html#introduction
If you didn't want to go down the convoluted route of creating a separate plugin file just to store a single string you could probably just do something like this in your entry file where you initialize the app:
Vue 3:
app.config.globalProperties.$defaultImg = '/path/to/image.png'
Vue 2:
Vue.prototype.$defaultImg = '/path/to/image.png'
And use this in your templates like
<img :src="$defaultImage">
Best Solution
However, I think the best and the most 'Vue' way would be to create an image component which displays a given image, or the default image if src is nullish:
Vue 2 & 3
<template>
<img :src="srcOrDefault">
<template>
Vue 3 Composition API:
<script>
const defaultImg = '/path/to/image.png'
const props = defineProps({
src: String
});
const srcOrDefault = computed(() => {
return props.src || defaultImg
})
</script>
Vue 2 & Vue 3 Options API:
<script>
const defaultImg = '/path/to/image.png'
export default {
props: {
src: String
},
computed: {
srcOrDefault() {
return this.src || defaultImg
}
}
}
</script>

How to export static images on Nextjs?

when I want to export my nextjs app, it says that I cannot export my images on static websites.
Error: Image Optimization using Next.js' default loader is not compatible with next export.
Possible solutions:
- Use next start to run a server, which includes the Image Optimization API.
- Use any provider which supports Image Optimization (like Vercel).
- Configure a third-party loader in next.config.js.
- Use the loader prop for next/image.
How can I make it so that it does ?
Is there a way for me to simply tell it to render images statically ? I dont want to go throught other onlines images loaders..
I created a npm module so that we can use the Next.js <Image/> component with optimized images while using the static export functionality.
https://www.npmjs.com/package/next-image-export-optimizer
The library wraps the <Image /> component of Next.js and automatically creates optimized images using sharp.js at export time.
It uses a custom loader to create a srcset for the <img /> that the <Image /> component of Next.js creates. Then at build/export time, the images inside the public/images folder (as an example) are optimized with sharp.js and copied into the build folder.
You need to set up a custom image loader in Next.js
In your next.config.js file, add this property to the export:
images: {
loader: "custom"
}
And make a script called loader.js that exports this:
function imageLoader({ src }) {
return `/images/${src}`; // REPLACE WITH YOUR IMAGE DIRECTORY
}
module.exports = imageLoader;
For each Image component, set the loader prop manually:
const imageLoader = require("PATH TO loader.js");
<Image loader={imageLoader} />
I'm going to add onto skara9's answer because it didn't quite work for me. I found a thread on github discussing it and the answer there worked for me. It just wraps around the NextJS image component and works pretty flawlessly for me.
// components/Image.js
import NextImage from "next/image";
// opt-out of image optimization, no-op
const customLoader = ({ src }) => {
return src
}
export default function Image(props) {
return (
<NextImage
{...props}
loader={customLoader}
/>
);
}
Make sure you change your imports and update your next.config.js
import Image from '../components/Image.js'

Gatsby Would Seem to Require an 10x the Effort of Any Other Framework to Add One Image: What Am I Not Understanding?

This is the code for a single image in any other Javascript framework in existence:
<img src="images/someFile.png" />
... possibly with some styling.
This is the code for a single image in Gatsby (basically copied from the Gatsby Image documentation page, https://www.gatsbyjs.org/docs/gatsby-image/ ... and this is just a simple example):
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
export default function Image() {
const data = useStaticQuery(graphql`
query {
file(relativePath: { eq: "images/someFile.png" }) {
childImageSharp {
fixed {
...GatsbyImageSharpFixed
}
}
}
}
`)
return (
<Img fixed={data.file.childImageSharp.fixed} />
)
}
Holy hell, I'm supposed to write all that code (ok, I don't have to repeat the imports, but still) for every image in my site?!?
That can't be right, so clearly I'm not understanding something. Can someone please clarify what I'm not getting about Gatsby?
If it truly takes that much code to just to add one image to a site, Gatsby must be the slowest framework ever to develop in (but from it's popularity I can't believe that's true). Or do most Gatsby sites just use very few images?
See Importing Assets Directly Into Files and Using the Static Folder
import React from "react"
import importedImage from "../images/gatsby-icon.png"
const IndexPage = () => (
<>
<img src={importedImage} alt="Description of my imported image" />
<img
src="staticfolderimage.jpeg"
alt="Description of my image in the ./static folder"
/>
</>
)
export default IndexPage
At the very start of the page which you mention there is a bit of explanation
gatsby-image is a React component designed to work
seamlessly with Gatsby’s native image processing capabilities powered
by GraphQL and gatsby-plugin-sharp to easily and completely optimize
image loading for your sites.
gatsby-plugin-sharp can compress jpeg/png images
Holy hell, I'm supposed to write all that code (ok, I don't have to
repeat the imports, but still) for every image in my site?!?
I think you can extract some things as params like eq: "images/someFile.png" and make it reusable
Example
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
export default function Image({source}) {
const { file: {childImageSharp: {fixed} } } = graphql(`
query($source: String!) {
file(relativePath: { eq: $source }) {
childImageSharp {
fixed {
...GatsbyImageSharpFixed
}
}
}
}
`, {source})
return <Img fixed={fixed} />
}
It's a nice topic to discuss and share. It's always up to you to use a <img> tag (importing assets directly in the component) or <Img> React's Component from Gatsby, adding and extending all support it has. From Gatsby Image documentation:
gatsby-image is a React component specially designed to work
seamlessly with Gatsby’s GraphQL queries. It combines Gatsby’s native
image processing capabilities with advanced image loading techniques
to easily and completely optimize image loading for your sites.
gatsby-image uses gatsby-plugin-sharp to power its image
transformations.
Note: gatsby-image is not a drop-in replacement for <img />. It’s
optimized for fixed width/height images and images that stretch the
full-width of a container. Some ways you can use <img /> won’t work
with gatsby-image.
So, without going into detail of all the benefits of using <Img> from Gatsby (compression, lazy loading, fluid sizes, etc), it's not that hard to implement for every single image if you take into account a few details.
Gatsby is a CMS/data-sourced based framework so, by default, all data comes from a CMS or somewhere outsourced, meaning that it has to be queried via GraphQL. In those queries, ideally, you'll prepare your data, images included. That will avoid you to use the staticQuery you showed. For example, retrieving all images once you've set up your filesystem should look like:
{
allImageSharp {
edges {
node {
fluid(maxWidth: 800) {
...GatsbyImageSharpFluid
}
}
}
}
}
Note: this is an example query, the idea is to fetch and gather all data needed from an image to pass it to <Img> component
Then, in your component you simply need to:
//...other imports
import Img from "gatsby-image"
const yourComponent= ({ data }) => {
return <Layout>
{data.edges.map(({ node })=> <Img fluid={node.childImageSharp.fluid} />)}
</Layout>;
};
Gatsby's example from using <Img> maybe it's not the most accurate use-case to do it because it involves a staticQuery, another way of importing data that is not as usual as it seems. You can easily avoid it by using a standard GraphQL query, saving you a lot of lines of code.
What I'm trying to say is that if you set your data properly, using Gatsby's image it's almost like using the common HTML <img> tag.
The sample code you showed (from Gatsby's documentation) will always show the astronaut image, but of course, it's completely up to you to use a single <Img> component and remove the <Image> one or re-use it as you wish.

No response even if img path is specified on React APP

I Launch project with create-react-app. No response even if img path is specified on React APP.
Image is displayed when import statement is used
// not working (1)
<img src={"../img/hoge.png"} />;
//working (2)
import Hoge from "../img/hoge.png";
<img src={Hoge} />
I want to user pattern (1).
Please tell me the solution orz......
Use require for dynamic imports, path like "../img/hoge.png" is not available in runtime because Webpack generates data URI in build time.
import hoge from '../img/hoge.png'; // Tell webpack this JS file uses this image
console.log(hoge); // /hoge.84287d09.png
// For dynamic imports
<img src={require("../img/hoge.png")} />
See Adding images in CRA docs.
If you have a list of potential images at build time, you can use the dynamic import function to load them, although this will return a promise like so:
export class ImageLoader extends React.Component {
constructor(props: any){
super(props);
this.state = {
imagePath: undefined
};
}
render() {
if(this.state.imagePath)
return <img src={this.state.imagePath} />; // Render the image
import(/* webpackMode: "eager" */`../imgs/${this.props.imageName}.png`) // Bundle all paths of images into the bundle
.then((imagePath: string) => {
this.setState({ imagePath}); // Set the new state with the loaded image path
});
return null; // Don't render anything right now since the image hasn't loaded yet.
}
}
This will bundle ALL png images under the imgs directory, use a switch statement with all possible names if that is too much.
See Webpack Documentation and How does Dynamic Import in webpack works when used with an expression?

React Native: How to make modular components using local image and data assets?

create-react-native-app v1.0.0
react-native-cli 2.0.1
react-native 0.52.0
Android API 25
I have several JSON files in ./src/data (aliased as data) and images in ./src/assets/images (aliased as images).
I am making a list of images that are filterable and sortable based on the corresponding data. Additionally, I have different types of images the user can toggle between for compact and expanded view.
From my searching it looks like my plan to reference the image path dynamically from a data object property is not possible and I need to do something like:
import image1 from 'images/'image1.png';
import image2 from 'images/'image2.png';
<Image source={some_expression ? image1 : image2} />
or
<Image source={some_expression ?
require('images/image1.png') : require('images/image2.png')} />
That works for the toggle, but I still have the original problem where I can't actually assign image1 and image2 from my data objects such as:
var image1 = "images/" + data.image1.img
where data.image1.img is the filename.
Is there a way around this where I can dynamically reference the local images for the corresponding data in a reusable component?
Short of hardcoding individual components with the images for each data object, I'm at a loss.
I'm new to React and mobile in general so I may have overlooked something in the documentation or online searching.
You can import your local images in parent of your component and use it as a prop.
<Image source={this.props.localImage} />
Where 'localImage' is imported in parent, like in code below and passed as a prop to child (your component).
import localImage from 'images/some-image.png';
This will maintain reusability and works perfectly.
For anyone with the same issue, here is what I did in order to use a string from my JSON data to reference the corresponding image to change what is shown dynamically.
In Images.js import all static images:
const images = {
img1: require('images/img1.png'),
imgNth: require('images/imgNth.png')
}
export default images;
or alternatively
import _img1 from 'images/img1.png';
import _imgNth from 'images/imgNth.png';
const images = {
img1: _img1,
imgNth: _imgNth
}
export default images;
Then where you want to use them i.e. App.js:
import images from 'images/Images';
import data from 'data/Data';
<Image source = {images[data[index].imgName]} />
Where imgName is a property that contains the exact same string value as one of the properties in the imported images i.e. img1 or imgNth in this example.
Regardless of how many images you have, I would suggest writing a simple script to run through your image folder and auto-generate an Images.js type file. It isn't hard to do and will save you a lot of work, especially if you add or remove images regularly during development so you don't have to manually update it each time.

Categories

Resources