How to configure using Styleguidist with Apollo/GraphQL - javascript

I'm trying to use GraphQL to populate fake data for Styleguidist. I'm using Express to make my GraphQL server but I'm unsure how to connect Apollo into Styleguidist? The examples use the index.js file and wrap the root component in an tag for Apollo.
I am unsure how Styleguidist works, I don't know where the index.js file is.
There are ways to configure Styleguidist through webpack, but I don't know how to use webpack to use Apollo.

Each example in Styleguidist is rendered as an independent React tree, and the Wrapper component is the root component, so you need to override it as show in the Redux example like this:
// lib/styleguide/Wrapper.js
import React, { Component } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
const client = new ApolloClient({ /* ... */ });
export default class Wrapper extends Component {
render() {
return (
<ApolloProvider client={client}>
{this.props.children}
</ApolloProvider>
);
}
}
// styleguide.config.js
const path = require('path');
module.exports = {
styleguideComponents: {
Wrapper: path.join(__dirname, 'lib/styleguide/Wrapper')
}
};

So you can use Styleguidist in two ways, one by using Create React App then installing an NPM Styleguidist package. Then the other method that I found is starting from an example github registry and replacing the components as you go. I had done the first: where I used Create React App so Webpack was not installed in my main folder but was being used in the NPM module.
With that method I was getting the error:
"Module parse failed: Unexpected token (16:6)
You may need an appropriate loader to handle this file type."
Which means that I needed to configure Webpack. I didn't solve this, but it may just need to have styleguide.config.js file configured to work with Babel. (just a guess)
So, could not (so far), successfully use the Wrapper that solves the problem. So instead I downloaded an example of Styleguidist at https://github.com/styleguidist/example and started fresh. I'm not sure what the difference is, but when I used a wrapper it worked well to add an ApolloProvider wrapper to every component on my page.
To get Apollo 2 to work though you also need to use HttpLink and InMemoryCache. The have a general setup about this at: https://www.apollographql.com/docs/react/basics/setup.html. Under creating a client.
I was using a different port for my GraphQL server because it was using a GraphQL/Express server at port 4000 and Styleguidist by default is at port 6060. So I did two things: passed a uri to the new HttpLink and added a line to the express server to allow cors.
The ref for cors in Express GraphQl and Apollo server:
https://blog.graph.cool/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
So my wrapper file looks like:
import React, { Component } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const link = new HttpLink({
uri: 'http://localhost:4000/graphql'
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
export default class Wrapper extends Component {
render() {
return (
<ApolloProvider client={client}>
{this.props.children}
</ApolloProvider>
);
}
}
and my server file looks like:
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema/schema');
const cors = require('cors');
const app = express();
app.use(cors());
app.use('/graphql', expressGraphQL({
schema: schema
, graphiql: true
}));
app.listen(4000, () => {
console.log('..listening');
});

Related

Set a module export before the file finishes in Node.js module (`import`/`export` syntax)

I am creating an API using express and I want to have a file for each endpoint. Ideally, I'd want to create the router in it's file, have it be exported and then import the endpoints files so they're run. The endpoint files would import the router again and then use .get, or .post or whatever to create the actual endpoint on the router.
So basically, my goal is to have one file run another file that uses a value the first file exports before running the different file.
I've done this before in a project using the require() syntax, but now I am trying to translate this to import/export syntax for a different project.
Before, I've (successfully) used something like this in another project:
Router.js
const express = require("express");
// Create and export a new router
const TestRouter = express.Router();
module.exports = TestRouter;
// Make the endpoints register themselves
require("./getdata.js");
require("./createdata.js");
And then in the endpoints (getdata.js and createdata.js):
const TestRouter = require("./TestRouter.js");
TestRouter.get("/:id", async (req, res) => {
// ...
});
Now, I am trying to do the same with package syntax (import/export):
TestRouter.js
import { Router } from "express";
// Create and export our new router
const TestRouter = Router();
export default TestRouter;
// Import endpoints so they can register themselves
import "./getdata.js";
import "./createdata.js";
Endpoints (getdata.js and createdata.js):
import TestRouter from "./TestRouter.js";
TestRouter.get("/:id", async (req, res) => {
// ...
});
But this direct translation doesn't work.
When I now try to run this project, I get an error like this:
file:///.../src/routes/getdata.js:3
TestRouter.get("/:id", async (req, res) => {
^
ReferenceError: Cannot access 'SessionRouter' before initialization
at file:///.../src/routes/getdata.js:3:1
...
My most likely guess for the problem at the moment would be that exports in modules aren't actually set at the time of the export statement, but rather when their respective file reaches the end after processing everything else.
So my question would be if there was a way to set the export at the time of the export statement, or perhaps an easy way (about 1-2 lines in the endpoint files) to make the endpoints wait for the TestRouter to actually be set before calling .get, .post or whatever on it.
I know I could have the endpoint files just export the actual endpoint handlers:
const getdata = async (req, res) => {
// ...
}
export default getdata;
and then have the .get calls in the router file:
import { Router } from "express";
// Import endpoint handlers
import getdata from "./getdata.js";
// Create our new router
const TestRouter = Router();
// Register endpints
TestRouter.get("/:id", getdata);
But I would like to avoid this - if at all possible - because every endpoint will need different middleware configurations, meaning that I have to specify all the middleware in the TestRouter.get (etc.) calls, which I don't really want to cram into the router file due to readability.
You can solve this by splitting TestRouter.js into two parts:
The part that creates the router (let's leave that in TestRouter.js)
The part that loads the endpoints (let's call that main.js)
...and make the second part your entry point. That breaks the cycle between TestRouter.js and getdata.js/createdata.js that causes the latter to try to access the exported binding from TestRouter.js before it's initialized.
main.js:
// Create and export our new router
import TestRouter from "./TestRouter.js";
// Or, actually, just: import "./TestRouter.js";
// Import endpoints so they can register themselves
import "./getdata.js";
import "./createdata.js";
TestRouter.js:
import { Router } from "express";
// Create and export our new router
const TestRouter = Router();
export default TestRouter;
getdata.js / createdata.js remain unchanged.

How to mount quasar app to dom after firebase connection is initialized?

Hi how do I initialize my quasar app once after I have established a connection to firebase. I need to do this because my route guards checks whether a user is logged in, but if a user refreshes this causes firebase to check whether a current user exists but since it's a async operation it initially returns null. I have previously achieved this using vue where I have done this in my main.js file, but I'm unsure how to do this is quasar boot file.
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/main.css'
import { authService } from './firebase/config'
let app
authService.onAuthStateChanged(() => {
if(!app){
app = createApp(App).use(router).mount('#app')
}
})
Here inside main.js standard vue app, we wait for Firebase Auth to initialize and only after .onAuthStateChanged() method fires, we create our Vue App.
In an quasar app there is no main.js but instead boot files which run some code before main app starts.
Below is what a standard boot files looks like but I am unsure how to convert my main.js to a executable boot file, seems like some properties are missing like mount, createApp & use.
// import something here
// "async" is optional!
// remove it if you don't need it
export default async ({ /* app, router, store */ }) => {
// something to do
}
Ideally you create a boot file for initializing Firebase in a Quasar project.
Boot files fulfill one special purpose: they run code before the App’s Vue root component is instantiated while giving you access to certain variables, which is required if you need to initialize a library, interfere with Vue Router, inject Vue prototype or inject the root instance of the Vue app.
Create a boot file using the following command:
quasar new boot firebase
This will create a boot file /src/boot/firebase.js
Install Firebase using npm (or yarn) and then add the following code in your boot file:
import firebase from 'firebase/app';
import 'firebase/auth';
const firebaseConfig = {...};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
export const auth = firebase.auth();
As mentioned earlier, boot file runs before Vue component is instantiated. Now you can access this instance of Firebase Auth in any component you want.
import {auth} from "#/boot/firebase"
auth.onAuthStateChanged((user) => {
//...
})
This got it working.
export default async ({ app, router, store }) => {
return new Promise(resolve => {
const unsubscribe = authService.onAuthStateChanged(() => {
resolve()
unsubscribe()
})
})
}
Here is also a useful article on quasar boot flow https://quasar.dev/quasar-cli/boot-files#quasar-app-flow

React server side rendering is not updating after client side route change

For the first time, the server side is refreshed, but the next time, only the client side is changed as usual, and the server does not change.
For example, with each refresh or typing of the address in the browser, the server also changes and works, but if I switch between pages on the client side with react router, the server does not change.
what is the problem?
#server/server.js
import path from 'path';
import fs from 'fs';
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import App from '../src/App';
const PORT = 5000;
const app = express();
const router = express.Router();
app.use('/build', express.static('build'));
app.use((req, res, next) => {
if (/\.js|\.css|\.png|\.jpg|\.jpeg/.test(req.path)) {
res.redirect('/build' + req.path);
} else {
next();
}
})
app.get('*', (req, res) => {
const context = {};
const app = ReactDOMServer.renderToString(
<StaticRouter location={req.path} context={context}>
<App />
</StaticRouter>
);
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf-8', (err, data) => {
if (err) {
console.log("Something went wrong:", err);
return res.status(500).send("Oops, better luck next time!");
}
return res.send(data.replace('<div id="root"></div>', `<div id="root">${app}</div>`));
});
});
router.use(express.static(path.resolve(__dirname, '..', 'build'), { maxAge: '10d' }));
app.use(router);
app.listen(PORT, () => {
console.log(`SSR running on ${PORT}`);
});
#server/index.js
require('ignore-styles');
require('#babel/register')({
ignore: [/(node_module)/],
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: ['#babel/transform-runtime'],
});
require('./server');
#index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import * as serviceWorker from './serviceWorker';
import App from './App';
ReactDOM.hydrate(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
There is nothing wrong with your Server Side Rendering setup. This is what's going on under the hood.
When you first type and enter the URL for a route of the application or you refresh the browser tab it hits the server and loads the index.html file rendered on the server-side with the help of renderToString.
ReactDOMServer.renderToString(...)`
Then index.html is viewed and is hydrated (attaching event handlers...etc) to this skeleton HTML file.
Note that you load the index.html from the build folder and replace only the div with root as the id. After building the app it adds js resources need to change the DOM (these resources are actually your frontend logic you wrote for your app) and this is required for client-side rendering. If you check the index.html it has the following script tags to load them.
...
<script src="/static/js/xxxxxxxxxxxxxxx.chunk.js">
...
When you go to another route by clicking a link inside your app. It does not hit the server again and it starts to execute js attached from the client bundle as I said above. Client-side js does the routing of the app properly. That's why it's not hitting your server. This is the expected nature of an isomorphic web application(Behaves the same on the server-side or client-side). And after that, if you refresh the browser that again loads the index.html from the server.

Exception while using a router object in a typescript class in nodejs

I have a nodejs service in typescript. I'm trying to set a separate routing layer in the application.
I have a app.js file where in
let IndividualRoute= require('./routing/IndividualRoute');
app.use('/users', IndividualRoute);
I have create a IndividualRoute.ts file where im trying to use router object like below
import express = require('express');
import individualValidation from '../users/IndividualValidation';
let router = express.Router();
class IndividualRoute {
router.get('/individualList', individualValidation.list);
}
export = IndividualRoute;
When i try to do the above, i get the below exception
I tried the below things
router.get('/individualList', (request, response) => {
response.send('Hello world!');
});
But i get the same issue. Also if i remove the class and have a simple file without class then i don't get this exception.
Basically what im trying to do is, if the route matches it should hit the respective function in the IndividualValidation file like in below case
router.get('/individualList', individualValidation.list);
If the route is users/individualList it should hit individualValidation.list function with the request data. Basically im trying to do what is marked as answer in the below link
express.Router() vs. app.get
Any suggestion what i'm doing wrong.
You're writing a class. Inside a class, put the constructor and methods. You can't write code directly here. The line you wrote needs to be in the constructor or in a method.
class IndividualRoute {
constructor(){
router.get('/individualList', individualValidation.list);
}
}
In IndividualRoute.ts, please make the following changes:
import { Router } from 'express';
import individualValidation from '../users/IndividualValidation';
let router = Router();
router.get('/individualList', individualValidation.list);
export default router;
Also, if using typescript, why you have app.js? I'll suggest using typescript for that file also. One more suggestion would be to have index.ts in your routing folder with code:
import { Router } from express;
import IndividualRoute from "./IndividualRoute";
let router = Router();
router.use("/users", IndividualRoute); // set this for each individual route
/*
* e.g.
* router.use("/auth", auth);
*/
export default router;
So in your app.ts, all you have to do is:
import * as express from "express";
import routes from "./routing";
let app = express();
app.use(routes);
// other code
I hope you find this helpful!

Receiving "Uncaught Error: Unable to find element with ID 15" when requesting a non-index page of an isomorphic app

The stack I'm working with consists of node, express, webpack, redux, react-router, and react. My express server looks as follows:
import Express from 'express';
import { Server } from 'http';
import path from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom';
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import App from './src/App';
import routes from './src/routes';
import reducer from './src/reducer';
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'src'));
app.use(Express.static(path.join(__dirname, 'clients/Sample/bundle')));
const store = createStore(
reducer,
{},
applyMiddleware(thunkMiddleware),
);
app.get('*', (req, res) => {
const context = {};
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App routes={routes} />
</StaticRouter>
</Provider>,
);
return res.render('index', { markup });
});
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, (err) => { // eslint-disable-line consistent-return
if (err) {
return console.error(err);
}
console.info(`Server running on http://localhost:${port} [${env}]`);
});
The application works perfectly if I attempt to access it directly on http://localhost:3000/. From there, I can navigate to other pages without issues. However, if I try to visit one of those other pages directly in the URL (e.g. http://localhost:3000/user) the console displays the following stack trace:
Uncaught Error: Unable to find element with ID 15.
at invariant (vendor.js:1)
at precacheChildNodes (vendor.js:1)
at Object.getNodeFromInstance (vendor.js:1)
at Object.didPutListener (vendor.js:1)
at Object.putListener (vendor.js:1)
at Object.putListener (vendor.js:1)
at CallbackQueue.notifyAll (vendor.js:1)
at ReactReconcileTransaction.close (vendor.js:1)
at ReactReconcileTransaction.closeAll (vendor.js:1)
at ReactReconcileTransaction.perform (vendor.js:1)
This then breaks navigation on the site, and redux state changes too. I've identified the element with this ID as being a imported from react-router-dom. I did find GitHub issues describing similar errors (one and two), sadly though the proposed solutions haven't worked for me. Any suggestions on what might be causing this for me?
I found this was actually related to a DOM validation error which was occurring as a result of having react-router-dom's <Link> within react-bootstrap's <NavItem>. In effect, the application was trying to render the following:
<a role="button" href="#" data-reactid="14">
Welcome, User
</a>
Of course, nested anchor tags are invalid markup. See the question here for further detail and some proposed methods of fixing the issue: React-Bootstrap link item in a navitem

Categories