How to check if asset was added from public/ dir in React? - reactjs

Is it possible to check if a file exists within the /public/ directory?
I have a set of images that correspond to some objects. When available, I would like to display them using <img> tag. However not all of the objects have a corresponding image, in which case I would like to perform a REST request to our server.
I could create a list of files as part of build process, but I would like to avoid that if possible.
I am using create-react-app if it matters (if I understand correctly fs doesn't work in client-side React apps).
EDIT: I guess I should have been more exact in my question - I know client-side JS can't access this information (except through HTTP requests), I was just hoping something saves information (during build) about the files available in a way that is accessible to client-side Javascript... Maybe Webpack or some extension can do this?

You can do this with your axios by setting relative path to the corresponding images folder. I have done this for getting a json file. You can try the same method for an image file, you may refer these examples
Note: if you have already set an axios instance with baseurl as a server in different domain, you will have to use the full path of the static file server where you deploy the web application.
axios.get('http://localhost:3000/assets/samplepic.png').then((response) => {
console.log(response)
}).catch((error) => {
console.log(error)
})
If the image is found the response will be 200 and if not, it will be 404.
Edit: Also, if the image file is present in assets folder inside src, you can do a require, get the path and do the above call with that path.
var SampleImagePath = require('./assets/samplepic.png');
axios.get(SampleImagePath).then(...)

First of all you should remember about client-server architecture of any web app. If you are using create-react-app you are serving your app via webpack-dev-server. So you should think about how you will host your files for production. Most common ways are:
apache2 / nginx
nodejs
but there is a lot of other ways depending on your stack.
With webpack-dev-server and in case you will use apache2 / nginx and if they would be configured to allow direct file access - it is possible to make direct requests to files. For example your files in public path so
class MyImage extends React.Component {
constructor (props) {
super(props);
this.state = {
isExist: null
}
}
componentDidMount() {
fetch(MY_HOST + '/public/' + this.props.MY_IMAGE_NAME)
.then(
() => {
// request status is 200
this.setState({ isExist: true })
},
() => {
// request is failed
this.setState({ isExist: false })
}
);
}
render() {
if (this.state.isExist === true) {
return <img src={ MY_HOST + "/public/" + this.props.MY_IMAGE_NAME }/>
}
return <img src="/public/no-image.jpg"/>
}
}

Related

NextJS dynamimc routing question about slugs

I have a doubt with nextjs..
I'm building my site like this
pages
[slug]
index.jsx
index.jsx
so in my slug/index I'm doing this
export async function getStaticPaths() {
const resProducts = await fetch(`${process.env.PRIVATE_ENDPOINT}/products`);
const products = await resProducts.json();
const paths = products.data.map((p) => ({
params: {
slugProduct: p.slug,
},
}));
return {
// this should be dynamic
paths,
fallback: true,
};
}
My question is what happend if I add a new product in my back office?
Do I have to rebuild with next build?
My question is what happend if I add a new product in my back office?
Do I have to rebuild with next build?
The short answer is NO. If the requested page have not been genereted at build time, Next.js will serve a "fallback" version of the page and will statically generate the requested path HTML and JSON on the background. When the statically generation completed, the browser receives the JSON for the generated path. Subsequent requests to the same path will serve the generated page, just like other pages pre-rendered at build time.
Don't forget to use router.isFallback to detect that request is on fallback.
You can see the good document here.
https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation

Accessing file in public folder through capacitor

I'm using ionic-v5, capactior and react to build an app.
I have a file (data.json) stored inside of my public/ folder.
I simply want to be able to load that file in and store it as an object.
So far I have tried:
import { Filesystem, FilesystemEncoding } from '#capacitor/core'
let contents = await Filesystem.readFile({
path: "data.json",
encoding: FilesystemEncoding.UTF8,
})
import { HTTP } from '#ionic-native/http';
let response = await HTTP.get("file://data.json", {}, {});
ret = response.data;
return ret;
I have also looked at https://ionicframework.com/docs/native/file but the documentation is poor to say the least.
Along with this I have tried pre-pending /android_asset/public to all of the paths but no luck (I know it would only work on Android, I just wanted to get something).
If you're using Ionic React (v5) and you just want to access a file in /myapp/public, you don't need Capacitor.
You can use axios (or fetch).
Folder structure:
/myapp
/assets
/json
/myFile.json
Sample code:
// https://stackoverflow.com/a/52570060
export const fetchJsonFile = (fileName: string): Promise<any> => (
axios.get(`./json/${fileName}`)
.then((response) => response).catch((error) => {
Promise.reject(error);
})
);

How to force Gatsby to redirect a specific URL path to an external site?

I have a Gatsby site and due to some specific requirements, I need to redirect anyone who attempts to hit a specific URL path, for which there is no page, to an external site. This URL path is not a page within the site, but it's something that a user may be inclined to type due to documentation that is out of my control.
Here's an example: Let's say the site is located at https://www.example.com. A user may visit https://www.example.com/puppies, which does not exist. My file structure does not contain a src/pages/puppies.js file. However, when that URL is entered, I need to redirect the user to another site altogether, such as https://www.stackoverflow.com.
I haven't used Gatsby to that extent to know it has a configuration for this, so someone else may correct me. The way I would handle this is through the hosting provider where your app is.
For example, if you are using Netlify:
Create a _redirects file with the following content:
/* /index.html 200
Or
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
This will cause all https://yourwebsite.com/IDontHaveThisRoute to fallback to /index.html where your .js is loaded.
I provided the Netlify example only to give you the basic idea of how it can be done through the hosting provider of your choice. I would look into configurations I can put into redirects where my domain is deployed.
Thanks to Paul Scanlon he mentioned using onRouteUpdate in Gatsby and it works like a charm
import { navigate } from 'gatsby';
export const onRouteUpdate = ({ location }) => {
if (location.pathname === '/dashboard') {
navigate('/dashboard/reports');
}
};
This question helped point me in the right direction. I was able to get it to work using Gatsby's componentDidMount() to force a redirect as shown below, using a new file called puppies.js to "catch" the path typed by the user:
// puppies.js
import React, { Component } from 'react'
class Puppies extends Component {
componentDidMount() {
window.location.replace("https://www.stackoverflow.com");
}
render() {
return <div />
}
}
export default Puppies

React/Next.js recommended way to set constants such as backend API URLs

I am looking through next.js documentation and trying to understand what the suggested approach is for setting URLs that change in different environments. Mostly, I want to ensure that I'm pointing backend URLs correctly in development versus production.
I suppose you can create a constants configuration file, but is there a supported, best practice for this?
Open next.config.js and add publicRuntimeConfig config with your constants:
module.exports = {
publicRuntimeConfig: {
// Will be available on both server and client
yourKey: 'your-value'
},
}
you can call it from another .js file like this
import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()
console.log(publicRuntimeConfig.yourKey)
or even call it from view like this
${publicRuntimeConfig.yourKey}
You can configure your next app using next-runtime-dotenv, it allows you to specify serverOnly / clientOnly values using next's runtime config.
Then in some component
import getConfig from 'next/config'
const {
publicRuntimeConfig: {MY_API_URL}, // Available both client and server side
serverRuntimeConfig: {GITHUB_TOKEN} // Only available server side
} = getConfig()
function HomePage() {
// Will display the variable on the server’s console
// Will display undefined into the browser’s console
console.log(GITHUB_TOKEN)
return (
<div>
My API URL is {MY_API_URL}
</div>
)
}
export default HomePage
If you don't need this separation, you can use dotenv lib to load your .env file, and configure Next's env property with it.
// next.config.js
require('dotenv').config()
module.exports = {
env: {
// Reference a variable that was defined in the .env file and make it available at Build Time
TEST_VAR: process.env.TEST_VAR,
},
}
Check this with-dotenv example.

Integration testing a fetch calls in a node module

Goal: Call a function that invokes a fetch call to validate it works with my backend rest-api (end to end testing basically).
Project: node module built to be imported into several react web application. The module contains only fetch calls and minimal logic. Its basically a glorified wrapper for URLs and settings. Created to cut down work required to implement common end points used in applications.
Setup: I have a docker compose building a docker test container and pulling in my rest-api docker image (built in a different system). The test container pulls in the packed module and installs it with dependencies. Then it brings up the tests alongside the backend + other images needed for the backend (DB, login system, etc).
Problem: How to implement the tests to handle the calls.
Currently I've tried calling the fetch methods directly. This works for my login fetch but any additional call fails to send the cookie. As far as I understand the code I have depends on the browser for the cookie. I've tried several solutions to get said cookie but i've been unable to get fetch of node-fetch to send it properly. My best guess is each test was creating a new cookie but I lack the knowledge to full debug this solution path.
my send solution path was to attempt to use puppeteer to load a fake page and then evaluate the function in page following examples like:
https://github.com/puppeteer/puppeteer/issues/2579
How to use imported function inside page.evaluate in Puppeteer with Jest?
Problem with this is the tests kept failing to load libraries required or some other problem.
Code:
Here is the call I'm trying to test for the most part. Each function I have wraps around this providing {url: "api/endpoint/path", method: "GET"}. With some passing in a body for larger data posts.
export function request(options) {
//Build options
options = {
headers: {
'Content-Type': 'application/json'
},
...options
};
return fetch(options.url, options)
.then(response => {
//...
//Handle errors
if (!response.ok) {
return Promise.reject(`${response.status} - ${response.statusText}`);
}
try {
return response.json();
} catch (e) {
if (e.name === 'SyntaxError') {
return Promise.reject(response.text());
}
}
});
}
Test example i've tried:
import puppeteer from "puppeteer";
import {myCall} from "my-rest-bridge";
it('Check response', async () => {
//Load browser
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
});
const page = await browser.newPage();
//Load page
await page.goto('http://docker:8888/index.html');
//Do login
expect(await page.evaluate(() => login('user', 'password')).toBe(expectedUserResponseJson);
//Make call
expect(await page.evaluate(() => myCall(input1, input2)).toBe(expectedCallResponseJson);
//Close page
await page.close();
})
Took me a while but I built a solution to my own question. Its not perfect so if anyone has a better idea please answer.
So my solution works as follows. I built an addition git project to create a shell reactjs application inside a docker image. This application pulls in my node module, iterates through all the exports, and then generates a component per function.
import React from 'react';
import * as magicNodeModule from "magic-node-module"; //obviously not the real name
import CallRest from "./CallRest";
/** Core component for the application */
export default class App extends React.Component {
/** Renders the core application */
render() {
const restCalls = Object.keys(magicNodeModule);
return (
<div id={"App"}>
<div>
Functions found:
<ul id={"function-list"}>
{restCalls.map(restCall => <li>{restCall}</li>)}
</ul>
<hr/>
</div>
{
restCalls.map(restCall => {
return (
<CallRest restName={restCall} restCall={magicNodeModule[restCall]}/>
);
})
}
</div>
)
}
}
This component (CallRest) contains an input box, submit button, and output div. A user, or in my use case puppeteer, can input data into the input. Then by clicking submit it will run the fetch call and insert the resulting output into the div. Works very much like swagger 2 for those that are familiar with the system.
The solution is still built up as a series of docker images inside of a compose. Though it did require setting up a reverse proxy to allow the react app to communicate with backend API. As well it pulls in a fresh copy of the node module as a pack zip and installs it into the docker. This way I only have to build the shell react docker once in a blue moon.

Resources