How to set up the api-platform react-admin graphql stack? - javascript

I'm setting up an Api-platform (graphql enabled) server and a react-admin client.
I create a resource on api-platform (name Domain). If i query GraphQL Playground app i get expected results.
After react-admin installed with the ra-data-graphql-simple package, the client try to connect and the client return an error "Unknown resource domains. Make sure it has been declared on your server side schema"
Here is my App.js code
import React, { Component } from 'react';
import { Admin, Resource, Delete } from 'react-admin';
import buildGraphQLProvider from 'ra-data-graphql-simple';
import myBuildQuery from './dataProvider';
import { DomainShow, DomainEdit, DomainCreate, DomainList } from './domain';
class App extends Component {
constructor() {
super();
this.state = { dataProvider: null };
}
componentDidMount() {
const introspectionOptions = {
include: ['Domains', 'Purchases'],
};
buildGraphQLProvider({
//buildQuery: myBuildQuery,
clientOptions: { uri: 'http://127.0.0.1:8000/api/graphql' },
introspection: introspectionOptions
})
.then(dataProvider => this.setState({ dataProvider }));
}
render() {
const { dataProvider } = this.state;
if (!dataProvider) {
return <div>Loading</div>;
}
return (
<Admin dataProvider={dataProvider}>
<Resource name="domains" list={ DomainList } create={ DomainCreate } show={ DomainShow } edit={ DomainEdit } title="Domains"/>
</Admin>
);
}
}
export default App;

Related

Environment Variables are not showing up in my Context component, on Nextjs. Would I need to configure Nextjs? Or set up Context to use the variables?

Environment Variables are working on every component inside /pages but not in my Context component, in Nextjs. I'm wondering would Nextjs need some configuration?
(Note: Shopcontext.tsx is using a class component, that I got from a tutorial and I'm not familiar enough with context api to change it to a functional component. I tried.)
/* Below Shopcontext.tsx */
import React, { Component } from "react";
import Client from "shopify-buy";
const ShopContext = React.createContext({});
const client = Client.buildClient({
domain: "benson-bracelets.myshopify.com",
storefrontAccessToken: process.env.SHOPIFY_ACCESS_TOKEN as string,
});
interface State {
product: {};
products: Array<any>;
checkout: any;
}
export class ShopProvider extends Component {
state: State = {
product: {},
products: [],
checkout: {},
};
componentDidMount() {
if (localStorage.checkout_id) {
this.fetchCheckout(localStorage.checkout_id);
} else {
this.createCheckout();
}
}
/**
* Local storage will hold the checkoutid.
* Shopify will handle the check eachtime a checkout is started.
* #memberOf ShopProvider
*/
createCheckout = async () => {
const checkout = await client.checkout.create();
localStorage.setItem("checkout_id", checkout.id as any);
this.setState({ checkout: checkout });
};
fetchCheckout = (checkoutId: any) => {
client.checkout
.fetch(checkoutId)
.then((checkout) => {
this.setState({ checkout: checkout });
})
.catch((err) => {
console.log("Error Message, in ShopContext fetchCheckout: ", err);
});
};
fetchAllProducts = async () => {
await client.product.fetchAll().then((products) => {
this.setState({ products });
});
};
render() {
return (
<ShopContext.Provider
value={{
...this.state,
fetchAllProducts: this.fetchAllProducts,
fetchCheckout: this.fetchCheckout,
}}
>
{this.props.children}
</ShopContext.Provider>
);
}
}
const ShopConsumer = ShopContext.Consumer;
export { ShopConsumer, ShopContext };
export default ShopProvider;
/* .env file below */
SHOPIFY_DOMAIN=MY_DOMAIN_WOULD_BE_HERE
SHOPIFY_ACCESS_TOKEN=MY_API_WOULD_BE_HERE
/* _app.tsx below */
import React from "react";
import { AppProps } from "next/app";
import { ThemeProvider } from "#material-ui/core/styles";
import Theme from "../src/ui/Theme";
import { AnimatePresence } from "framer-motion";
import ShopContext from "../src/context/ShopContext";
import { wrapper } from "../src/store/store";
function MyApp(props: AppProps) {
const { Component, pageProps } = props;
return (
<React.Fragment>
<ThemeProvider theme={Theme}>
<ShopContext>
<AnimatePresence exitBeforeEnter>
<Component
{...pageProps}
{...props}
/>
</AnimatePresence>
</ShopContext>
</ThemeProvider>
</React.Fragment>
);
}
export default wrapper.withRedux(MyApp);
I would like to add on to #Mohammad Shaban's answer.
Ever since NextJS 9.4 there is support for loading environment variables through .env.local file .
By default all environment variables loaded through .env.local are only available in the Node.js environment, meaning they won't be exposed to the browser.
In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. For example:
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
So don't forget to prefix the env variables with NEXT_PUBLIC_ in case you are using them in browser.
For more information you can check this link
You should define your environment variable inside the next.config.js at the route of your project then you will get the env variable in every component
module.exports = {
env: {
customKey: 'my-value',
},
}
it is clearly mentioned in the nextjs document that Trying to destructure process.env variables won't work due to the nature of webpack
For more detail please visit the below link
https://nextjs.org/docs/api-reference/next.config.js/environment-variables

set cookie before running fetch in getInitialProps

I have a Next.js app, I'm using getInitialProps in my _app.js in order to be able to have persistent header and footer. However, I'm also needing to set data in a Context, and I need to be able to fetch the data based off of a cookie value. I've got the basics working just fine, however my _app sets the cookie on the first load, and then when I refresh the page it pulls in the appropriate data. I'm wondering if there's a way to be able to set the cookie first before fetching the data, ensuring that, if there's a cookie present, it will always pull in that data on the first load? Here is my _app.js, and, while I'm still working on the dynamic cookie value in my cookies.set method, I'm able to fetch the right data from my Prismic repo by hard-coding sacramento-ca for now, as you'll see. All I'm really needing is the logic to ensure that the cookie sets, and then the data fetches.
_app.js
import React from 'react';
import { AppLayout } from 'components/app-layout/AppLayout';
import { Footer } from 'components/footer/Footer';
import { Header } from 'components/header/Header';
import { LocationContext } from 'contexts/Contexts';
import Cookies from 'cookies';
import { Client } from 'lib/prismic';
import NextApp, { AppProps } from 'next/app';
import 'styles/base.scss';
import { AppProvider } from 'providers/app-provider/AppProvider';
interface WithNavProps extends AppProps {
navigation: any;
location: string;
dealer?: any;
cookie: string;
}
const App = ({ Component, pageProps, navigation, dealer }: WithNavProps) => {
const { Provider: LocationProvider } = LocationContext;
const locationData = dealer ? dealer : null;
return (
<LocationProvider value={{ locationData }}>
<AppProvider>
<AppLayout>
<Header navigation={navigation} location={dealer} />
<Component {...pageProps} />
<Footer navigation={navigation} />
</AppLayout>
</AppProvider>
</LocationProvider>
);
};
export default App;
App.getInitialProps = async (appContext: any) => {
const appProps = await NextApp.getInitialProps(appContext);
const cookies = new Cookies(appContext.ctx.req, appContext.ctx.res);
try {
cookies.set('dealerLocation', 'sacramento-ca', {
httpOnly: true,
});
const { data: navigation } = await Client.getByUID('navigation', 'main-navigation', {
lang: 'en-us',
});
const results = await Client.getByUID('dealer', cookies.get('dealerLocation'), {
lang: 'en-us',
});
return {
...appProps,
navigation,
dealer: results,
};
} catch {
const { data: navigation } = await Client.getByUID('navigation', 'main-navigation', {
lang: 'en-us',
});
return {
...appProps,
navigation,
};
}
};

Calling actions from instance of third party component in my React component

I have the following code where inside my React component I'm using a third party component -- FineUploader in my case.
Upon uploading files, it calls its onComplete function. From here, I'm trying to call my action creators to handle post-upload processes but I'm unable to access my props or actions from there because this is all outside of my component.
Up to that point, everything is working. I'm able to call uploader instance from my component and upload the file to my Azure Blob Storage and get the fileName and blobName once the upload is completed.
It's funny that I'm stuck at the easier part!
Here's my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import FineUploaderAzure from 'fine-uploader-wrappers/azure'
// Components
import Gallery from './gallery/index';
// Actions
import * as myActions from '../myActions';
// Instantiate FineUploader
const uploader = new FineUploaderAzure({
options: {
cors: {
expected: true,
sendCredentials: false
},
signature: {
endpoint: 'http://localhost:123/getsas'
},
request: {
endpoint: 'https://myaccount.blob.core.windows.net/my-container'
},
callbacks: {
onComplete: function (id, name, responseJSON, xhr) {
const fileName = uploader.methods.getName(id);
const blobName = uploader.methods.getBlobName(id);
// I now need to call my action creator to handle backend stuff
// Or I can call the handleFileUpload function inside my component.
// How do I access either my action creator or handleFileUpload function from here?
}
}
}
})
class FileUploader extends Component {
constructor(props) {
super(props);
this.handleFileUpload = this.handleFileUpload.bind(this);
}
handleFileUpload(fileName, blobName) {
debugger;
}
render() {
return (
<Gallery uploader={uploader} />
)
}
}
function mapStateToProps(state, ownProps) {
return {
}
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(myActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader)
I came up with the following approach that works. I'm not sure if this is the best way or there's a more elegant approach. I won't accept my answer as the correct one and let everyone post their comments and votes.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import FineUploaderAzure from 'fine-uploader-wrappers/azure'
// Components
import Gallery from './gallery/index';
// Actions
import * as myActions from '../myActions';
// Instantiate FineUploader
const uploader = new FineUploaderAzure({
options: {
cors: {
expected: true,
sendCredentials: false
},
signature: {
endpoint: 'http://localhost:123/getsas'
},
request: {
endpoint: 'https://myaccount.blob.core.windows.net/my-container'
}
}
})
class FileUploader extends Component {
constructor(props) {
super(props);
this.handleFileUpload = this.handleFileUpload.bind(this);
}
componentDidMount() {
uploader.on('complete', (id, name, responseJSON, xhr) => {
const originalName = uploader.methods.getName(id);
const blobName = uploader.methods.getBlobName(id);
this.handleFileUpload(originalName, blobName);
}
}
handleFileUpload(fileName, blobName) {
// Received fileName and blobName. We can call our actions creators here.
this.props.actions.someAction(fileName, blobName);
}
render() {
return (
<Gallery uploader={uploader} />
)
}
}
function mapStateToProps(state, ownProps) {
return {
}
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(myActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader)

Meteor JS ReactMeteorData - CreateContainer - Super expression must either be null or a function

After upgrading to Meteor 1.5 from 1.4, createContainer function from react-meteor-data gives the following error:
Uncaught TypeError: Super expression must either be null or a function, not undefined
at exports.default (modules.js?hash=fb99b6a…:1144)
at ReactMeteorData.jsx:6
at ReactMeteorData.jsx:6
at createContainer (createContainer.jsx:16)
at AppContainer.jsx (AppContainer.jsx:8)
AppContainer.jsx:
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { createContainer } from 'meteor/react-meteor-data';
import App from '../layouts/App.jsx';
export default AppContainer = createContainer(props => {
return {
currentUser: Meteor.user(),
};
}, App);
App file below, in constructor i am performing super(props) however error is still thrown
App.jsx:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
menuOpen: false,
showConnectionIssue: false,
headerTitle: null,
};
this.setHeaderTitle = this.setHeaderTitle.bind(this);
this.logout = this.logout.bind(this);
}
logout() {
Meteor.logout();
this.context.router.replace(`/home`);
}
render() {
... omitted render function
}
}
App.propTypes = {
user: React.PropTypes.object, // current meteor user
connected: React.PropTypes.bool, // server connection status
loading: React.PropTypes.bool, // subscription status
menuOpen: React.PropTypes.bool, // is side menu open?
children: React.PropTypes.element, // matched child route component
location: React.PropTypes.object, // current router location
params: React.PropTypes.object, // parameters of the current route
};
App.contextTypes = {
router: React.PropTypes.object,
};
export default App;
Personnally, for a container data i do like this (from masterchef Base, updated and working as well) :
/* AppContainer.js */
// import react and proptypes ...
import { Meteor } from 'meteor/meteor';
import container from '../../../modules/container';
// Some code for app layout and proptypes...
export default container((props, onData) => {
const user= Meteor.user(); // adapted for your case
onData(null, {
currentUser:user,
// and others data ...
});
}, App);
/* container.js */
import { compose } from 'react-komposer';
import getTrackerLoader from './get-tracker-loader';
export default function container(composer, Component, options = {}) {
return compose(getTrackerLoader(composer), options)(Component);
}
/* get-tracker-loader.js */
import { Tracker } from 'meteor/tracker';
export default function getTrackerLoader(reactiveMapper) {
return (props, onData, env) => {
let trackerCleanup = null;
const handler = Tracker.nonreactive(() => Tracker.autorun(() => {
trackerCleanup = reactiveMapper(props, onData, env);
}));
return () => {
if (typeof trackerCleanup === 'function') trackerCleanup();
return handler.stop();
};
};
}
Hope it ll be useful.
Try following snippet:
export default AppContainer = createContainer((props) => {
// do subscriptions if you have any
return {
currentUser: Meteor.user(),
};
}, App);
You might be missing super() in the App Component.

Passing variables to components via a Route in React

I've got a basic admin app and I basically want to protect certain routes against the roles sent by the API when a user logs in via the Oauth2 protocol.
I have a route like...
<Route name="app" handler={App}>
<Route name="admin" path="/admin" roles={["admin", "superadmin"]} />
</Route>
Then I have an authentication component...
import React from 'react';
import SessionStore from '../stores/auth/SessionStore';
export default (ComposedComponent) => {
return class AuthenticatedComponent extends React.Component {
static willTransitionTo(transition) {
// If user isn't logged in, send 'em back to the login page
if (!SessionStore.isLoggedIn()) {
transition.redirect('/login', {}, {'nextPath' : transition.path});
} else if (this.rolesRequired) {
// Get all current users roles from session store.
let userRoles = SessionStore.roles;
// Iterate through user roles, if none match the required roles, send 'em away ta.
if (!this.rolesRequired.every(role => userRoles.indexOf(role) >= 0)) {
transition.redirect('/login', {}, { 'nextPath' : transition.path });
}
}
}
constructor(props) {
super(props);
this.state = this._getLoginState();
}
_getLoginState() {
return {
userLoggedIn: SessionStore.isLoggedIn(),
user: SessionStore.user,
token: SessionStore.token,
roles: SessionStore.roles
};
}
componentDidMount() {
this.changeListener = this._onChange.bind(this);
SessionStore.addChangeListener(this.changeListener);
}
_onChange() {
this.setState(this._getLoginState());
}
componentsWillUnmount() {
SessionStore.removeChangeListener(this.changeListener);
}
render() {
return (
<ComposedComponent
{...this.props}
user={this.state.user}
token={this.state.token}
roles={this.state.roles}
userLoggedIn={this.state.userLoggedIn} />
);
}
}
}
Then any components which need authenticating are passed into an instance of the AuthenticatedComponent, for example...
import React from 'react';
import RoleStore from '../../stores/user/RoleStore';
i
mport AdminActions from '../../actions/admin/AdminActions';
import AuthenticatedComponent from '../../components/AuthenticatedComponent';
import AdminMenu from '../../components/admin/AdminMenu';
import Template from '../template/BaseTemplate.react';
import RoleActions from '../../actions/user/RoleActions';
/**
* Admin
*
* #author Ewan Valentine
* #copyright 65twenty 2015
*/
export default AuthenticatedComponent(class Admin extends React.Component {
constructor() {
super();
this.state = {
users: [],
roles: []
};
this.onChange = this.onChange.bind(this);
}
onChange() {
this.setState({
roles: RoleStore.data,
users: UserListStore.data
});
}
render() {
return(
<Template>
<main>
<AdminMenu />
<h2>Admin Home</h2>
</main>
</Template>
);
}
});
I basically can't figure out the best approach for defining the required roles and there doesn't seem to be any way of accessing props on the Route component.
I had a similar issue, where I wanted to only show "Billing" link on top navbar if user belonged to 'admin' group.
I also had an Authentication component like you did, then I created an Authorization component, and several authorization policies depending on the required roles. Here is the code:
var Authorized = require('../auth/Authorized');
var AdminComponentPolicy = require('../auth/policies/AdminComponentPolicy');
<Authorized policy={AdminComponentPolicy} action="show" user= {this.props.user}>
...protected stuff
</Authorized>
Here is the code for the Authorized component:
//Authorized.jsx
import React from 'react';
var Authorized = React.createClass({
render: function() {
//checks if the informed policy returns true for given action and user.
if (this.props.policy.authorized(this.props.action, this.props.user)) {
return this.props.children;
} else {
return null;
}
}
});
module.exports = Authorized;
Here is the code for AdminComponentPolicy
//AdminComponentPolicy.js
class AdminComponentPolicy {
authorized(action, user) {
//return user.role === 'admin';
let _policies = {
//the 'show' action in this policy returns true for 'admin' users
show: function(record) {
return record.role === 'admin';
},
destroy: function(record) {
return this.show(record) || record.role === 'check if owner here';
},
};
return _policies[action](user);
}
}
export default new AdminComponentPolicy()

Categories