Render Remote Component on Server Side Next.js - node.js

I am attempting to build a React component library which can be independently deployed from the apps which use it. I'm doing this by loading the components over the network, and then rendering them in a Next.js app. I know I can achieve this using react-umd-loader, but that only works client side because it relies on scriptjs. I was able to get this working using the vm npm package. This is what my page in Next.js looks like.
const Index = (props) => {
let sandbox = {'React': React, 'ReactDOM': ReactDOM, 'MyComponent': null, 'self': {}};
vm.runInNewContext(props.MyComponent, sandbox);
const MyComponent = sandbox.MyComponent.default;
return (<div><p>Hello from Next.js</p><MyComponent /></div>)
}
Index.getInitialProps = async function() {
const res = await fetch('http://localhost:3001/MyComponent.bundle.js')
const script = await res.text()
return { MyComponent: script }
}
MyComponent is a react component built with the following webpack.config.js
entry: {
MyComponent: "./src/components/MyComponent",
},
output: {
path: path.resolve(__dirname, 'build/static/js'),
filename: "[name].bundle.js",
libraryTarget: "umd",
library: "[name]",
umdNamedDefine: true,
globalObject: 'this'
},
This actually works fine, but, I want the component to fetch data and then display that data, which is not working.
class MyComponent extends Component {
constructor() {
super();
this.state = {data: {msg: 'data not loaded'}};
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => this.setState({data: json}))
.catch(err => console.log(err));
}
render() {
return (
<div style={{border: '1px solid black'}}>
<h1>My Component</h1>
This data was fetched from an API:
<pre>
{JSON.stringify(this.state.data)}
</pre>
</div>
);
}
}
I think the reason this is not working is because the fetch is asynchronous, and the page is rendered and returned to the client before the fetch completes. I tried using fetch-suspense but this did not work. I see Promise { <pending> } on the server console, so maybe the fetch promise just isn't completing?
Is it possible to make the server wait to respond until the fetch from the API has completed?
My full repo is at https://github.com/bernardwolff/ReactRemoteComponentSsr
Thanks!

Related

download large file using streams from nodeJs to angular app

i am struggling to get this stream downloading file works
if i try the same request using curl the stream works fine and streaming the data.
in my angular app the file is completely download before the client see the download file tab it seems like the subscribe only happened after all stream body fully downloaded
what i am trying to achive is after first chunk of data send to angular app
i want the file to start downloading.
instead after observing the network only after all file downloaded from the backend
the downLoadFile methood is called and the ui expirence is stuck
this is a minimal example of what i am trying todo
at the backend i have a genrator that genreate a huge file
and pipe the request
Node JS
const FILE = './HUGE_FILE.csv'
const lines = await fs.readFileSync(FILE, 'utf8').split('\n')
function* generator() {
for (const i of files) {
console.log(i)
yield i
}
}
app.get('/', async function (req, res) {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename=\"' + 'download-' + Date.now() + '.csv\"');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Pragma', 'no-cache');
const readable = Readable.from(generator());
readable.pipe(res);
});
at the client side calling the endpoint and waiting for the resonse
Angular code
#Component({
selector: 'is-optimizer-csv',
templateUrl: './optimizer-csv.component.html',
styleUrls: ['./optimizer-csv.component.scss']
})
export class OptimizerCsvComponent implements OnInit {
private onDownloadButtonClicked() {
const data = {...this.form.get('download').value, ...(this.advertiserId ? {advertiserId: this.advertiserId} : null)};
this.loading$.next(true);
this.optimizerService
.downloadOptimizerCsvData(data)
.pipe(
take(1),
tap(_ => this.loading$.next(false))
)
.subscribe();
}
}
#Injectable({
providedIn: 'root'
})
export class OptimizerService {
constructor(private readonly http: Ht, private datePipe: DatePipe) {}
downloadOptimizerCsvData(data: any) {
this.http.get(`${environment.apiUrl}`,{
responseType: 'arraybuffer',headers:headers}
).subscribe(response => this.downLoadFile(response, "text/csv"));
}
downLoadFile(data: any, type: string) {
let blob = new Blob([data], { type: type});
let url = window.URL.createObjectURL(blob);
let pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
alert( 'Please disable your Pop-up blocker and try again.');
}
}
}
it look like angular common http client does not support streaming out of the box .
see this github issue by #prabh-62
https://github.com/angular/angular/issues/44143
from this thread the why to solve your issue is by implementing the fetch stream logic using native fetch

Remix js localstorage alternative for server side

I have a remix application to act like frontend.
I load data from my backend and for some data I need to load it only once and reuse it on different pages.
In previous frontend we used localstorage but here is server side which returns me ReferenceError: window is not defined
import {LoaderFunction} from "#remix-run/node";
import authenticator from "~/services/auth.server";
import Layout from "~/src/Layout";
import {fetchData} from "~/services/fetch.service";
export let loader: LoaderFunction = async ({request}) => {
const user = await authenticator.isAuthenticated(request, {failureRedirect: "/login",});
const configs = await fetchData('GET', request, 'api/configs/all')
.then((response) => {
return response;
})
.catch(async error => {
await authenticator.logout(request, {redirectTo: "/login"});
});
try {
localStorage.setItem('parameters', configs);
} catch (e) {
console.log(e);
}
return {
user: user,
request: request
};
};
export default function DashboardPage() {
const data = useLoaderData();
return (
<Layout user={data?.user} request={data.request}>
</Layout>
);
}
I need the config to be accessable at any time, it's not usefull if I need to load it all the time.
You can't use localStorage on the server-side, since it is first set on the client. You could use cookies since they are accessible on the server-side.

Image upload from React to NodeJS, port connection refused

Hello Dear Community :)
I'm trying to code an image upload on my website, from React to Backend in NodeJS.
I have this error with this port connection.
This path http://localhost:5000/api/plants with post method works fine in PostMan (with uploading picture). The problem occurs when I want to upload to the backend server.
Here is the problem logged in the console
xhr.js:177 POST http://localhost:5000/api/plants
net::ERR_CONNECTION_REFUSED
Here is my code :
import React, { Component, useState } from "react";
import axios from 'axios';
class UploadImg extends Component {
state = {
image: null,
}
handleChange = event => {
this.setState({
image: event.target.files[0],
loaded: 0,
})
}
handleFileUpload = event => {
console.log('the new file upload is :', event.target.files[0]);
}
onClickHandler = (event) => {
const data = new FormData()
data.append('file', this.state.image)
axios.post('http://localhost:5000/api/plants', data)
.then(res => {
console.log(res)
})
.catch((error) => {
console.log(error)
})
event.preventDefault();
}
render() {
return (
<div>
<h2>New Thing</h2>
<form onSubmit={this.handleSubmit}>
<input type="file" onChange={this.handleFileUpload} />
<button onClick={this.onClickHandler} type="submit">Upload</button>
</form>
</div>
)
}
}
export default UploadImg
This link might help you: https://www.digitalocean.com/community/questions/why-can-i-call-api-port-from-react-app-using-ip-address-but-not-when-i-use-localhost
TL;DR: replace localhost in the axios request to whatever your machine's IP address is.

Make http request before displaying page in React

ComponentWillUnmount is no longer recommended in React. I want to send a request to the server to see if the user is logged and then update the state before actually displaying the page. I don't know how to do this with React hooks or a way that is recommended in React.
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
The following demonstrates componentDidMount , componentDidUpdate, and componentWillUnmount
useEffect(() => {
// function to run to check if user is log in
},[]) //<-- similar to componentDidMount , empty array, run once.
useEffect(() => {
}, [state]) //<-- similar to componentDidUpdate. Run when state changes.
useEffect(() => {
return () => cleanUp //<-- similar to componentWillUnMount, clean up functions.
}, [])
Recommended reading for useEffect Hook
Recommended reading for setting state with hook
Example of checking user login
const Component = () => {
const [ loggedIn, setLoggedIn ] = useState(false) //set login initial state to false
useEffect(() => {
const checkLogin = async() => {
const checking = await fetch(api);
if (checking) {
setLoggedIn(true) //set login state to true
}
}
checkLogin()
},[]) //<-- run once when component mounted
return (<div>{ loggedIn ? 'Logged In' : 'Not Logged In' }</div>)
}

Axios 'GET' request is pending but it never reaches the server

I am using axios on my React app to get data from my server (Node). My GET request stays pending in chrome developer tools and does not reach the server if I refresh the app on the provided route (e.g., http://localhost:3000/category/5d68936732d1180004e516cb). However, if I redirect from the home page, it will work.
I have tried different variations of making sure I end my responses on the server end of things.
Several posts have had related problems (e.g., request not reaching the server, POST request does not reach the server) but unfortunately not been helpful in my situation.
Here is my main call in my react app:
componentDidMount() {
console.log('I am here!'); // this gets executed even on page refresh
axios.get(`/api/categories/${this.props.id}`)
.then( (res) => {
this.setState({
title: res.data.category.title,
module: res.data.category.module ? true : false,
...res.data
})
}, (err) => console.log(err))
.catch(err => console.log(err));
}
On my back end I call this function after going through user verification:
module.exports.publishedCategories = async function(req, res) {
try {
// some code that I removed for clarity
res.json({
category,
children,
flagged: flaggedCategories
});
} catch(err) {
console.log(err);
res.sendStatus(500).end();
}
}
Some more code regarding my routing:
index.js
<Route
path='/category/:id'
render={ (props) => {
return <Category id={props.match.params.id} />
}}
/>
I do not get any error messages...
I was overzealous with my initial solution of switching to componentDidUpdate(). This only worked for page refreshes but not for redirects (i.e., I had the reverse problem). My final solution is as follows:
componentDidMount = async () => {
setTimeout( async () => {
this.loadCategory();
}, 10)
}
componentDidUpdate = async (props, state) => {
if(props.match.params.id !== this.props.match.params.id) {
this.loadCategory();
return;
}
return false;
}
loadCategory = async () => {
let result = await axios.get(`/api/categories/${this.props.match.params.id}`);
this.setState({
title: result.data.category.title,
module: result.data.category.module ? true : false,
...result.data
});
}
I am not sure why adding a setTimeout() works for componentDidMount().
As per the documentation componentDidUpdate(), it is good for netork updates however you need to compare prior props with current props.
Unforunately I am not sure how to get around the setTimeout() hack but it seems to work.

Resources