Sails Typeerror: Cannot Read Property 'attributes' of Undefined

In a rush? Skip to technical tutorial or live demo.

A few months ago, we summoned our in-house Node.js good to craft a piece near the land of its ecosystem.

In information technology, he never mentioned Strapi.js.

What a fool. 🤦‍♂️

Sure enough, our readers put him back on track by suggesting this powerful Node.js API framework in the comments.

Today, to prove we took this feedback to center, I'll make skilful use of Strapi. I'll show you how to handle content management in Node.js with a React single-page application.

Steps:

  1. Setting up a Node.js backend for Strapi with MongoDB.

  2. Creating appropriate content types in Strapi for the SPA.

  3. Building a React SPA (a basic online shop) with create-react-app.

  4. Bundling & deploying the Universal JS app!

In the process, I reflected a lot about this kind of Universal JavaScript stack and its place in the programmer's space.

So permit'southward get philosophical a flake before jumping into technical stuff.

Node.js with React: Why go the Universal JavaScript way?

But a few years back, edifice a full stack JavaScript app was pure fantasy. But things have changed.

Some might call it Isomorphic JavaScript, but there seems to be a consensus around the term Universal JavaScript. Then I'chiliad sticking with the latter here.

It all started with the Mean stack (MongoDB/Express/Athwart/Node)—the outset go-to JS full stack. Y'all can all the same opt for it today, simply information technology would be a mistake to call up it's the only bachelor stack.

Once yous've found your ideal stack, benefits of Universal JS abound:

  • Lawmaking universality: Uniting your stack instead of dividing information technology. Sharing code between the frontend and backend reduces code duplication to the bare minimum—hence, easier maintenance.

  • Performance & SEO boost: with Universal JavaScript, you tin build a spider web app rendering seamlessly on both server and browser.

  • npm install: npm, the largest software registry, lets you hands install everything on the backend and frontend.

  • Overall great UX: Important parts of a page are rendered on the server and shown to users rapidly. Other elements tin be rendered client-side, after these essential parts are loaded.

Plus, the choice of (excellent) tools to work with is wider than ever.

Options for your React frontend ⚛️

Here'southward what the React ecosystem has to offer for building a great SPA:

Gatsby

What started as a simple static site generator has evolved way beyond that. You can use it to create progressive web apps, or fetch data with GraphQL. Its founder wants to alter the "static" game, so keep an center on it!

Next.js

Next.js is a lightweight framework for static and server‑rendered applications. Its cool set of features include automatic code splitting, simple client-side routing, webpack-based dev environment and piece of cake implementation with any Node.js server. You can besides use it as a static site generator.

For this demo's bones needs, I'll generate the app using create-react-app. It enables app creation with no build configuration, allowing to focus on relevant code.

Options for your Node.js backend 🖥️

Many server-side frameworks also spawned with the rising of Node.js:

Express

Limited remains the most widely used of them. It's a fast, unopinionated, minimalist spider web framework for Node.js. Its straightforward arroyo is probably what comes closer to Node.js' basic idea of a lightweight system with a modularity arroyo.

Koa.js

What happens when you accept Express and strip information technology down to its nigh simple form? Koa.js. The aforementioned squad behind the onetime congenital it equally a smaller, more expressive and robust foundation for web apps and APIs. It's marketed as the side by side generation spider web framework for Node.js and y'all should cheque it out.

Sails.js

Sails is a real-time, MVC framework. It was designed to emulate the MVC pattern of Crimson on Rail only with support for modern apps. It does this through data-driven APIs with a scalable, service-oriented architecture.

Then there's Nest.js, Socket.io, Hateful.js and, of course, the youngest of the bunch: Strapi.

What is Strapi?

Strapi is a Node.js API framework with headless CMS capabilities.

It's said to be the most advanced Node.js content management framework, saving developers weeks of API configurations. We'll see nearly that today!

It comes with an impressive fix of features (including an admin panel, powerful CLI, fast & secure) and useful plugins.

More importantly for my utilise case is that it's frontend agnostic. Information technology ways that it connects to the frontend framework of your choice, as well as mobile apps and even IoT (Internet-of-Things).

Looks like it checks all the boxes for a not bad Node.js with React stack.

Information technology made quite a splash in its brusque lifetime, and the word-of-mouth is pretty skillful effectually it, then I'm excited to try it!


Strapi tutorial: Node.js content management for React.js app

Prerequisites

  • A database for your project (Strapi strongly recommends MongoDB)

  • Bones understanding of single-page applications (SPA)

  • If you want to enable e-comm. functionalities, a Snipcart business relationship (forever free in Examination mode)

1. Getting Started with Strapi

Permit's ready your development environment. For Strapi, this is pretty easy, install it using the new yarn command:

            yarn create strapi-app strapi-project --quickstart          

It will launch your strapi server automatically and open up the admin on your browser.

You'll now be able to attain the administration panel through the post-obit URL: http://localhost:1337/admin.

In case you lot stopped your server dive into your folder, the control to launch your strapi server is the following:

            cd strapi-project strapi dev          

one.1 Creating a Strapi user

On the assistants folio, you'll be prompted with a welcome message and a form to create a root user. Once again, follow the onscreen instructions.

1.2 Crafting new content types

At present that you lot have access to a root user you tin proceed to make a new content type. In Strapi, the API is built aslope the creation of new content types.

Since this demo is nearly building a simple e-commerce app, I'll create a production and add fields matching Snipcart's production definition.

Strapi acts every bit an interface between your database and API. This is why a new collection called product will announced in the database.

1.3 Making an entry for your content type

Now that your content type is set up, make a new entry for your product. Strapi will insert a new document in the product drove in your database.

1.4 Grant admission

Past default, Strapi restricts the access of new content types actions to the administrator just.

To address this, you can get into "Roles & Permission" console and add together ane permission of your product content type into the public role.

There you lot go!

With Strapi, that's all information technology takes to create an underlying API that your frontend will exist able to admission and load data from. If you visit http://localhost:1337/product you should exist greeted with a listing of all your products.

You can too visit http://localhost:1337/:_id (where :_id is the id of your product) and should receive a JSON containing only the specified production.

2. Setting upwards the React awarding

Now that your backend is nearly finished, allow'south create a single-folio app.

            npx create-react-app react-app cd react-app npm start          

Once completed, you can visit the app at the following URL: http://localhost:3000/

two.one Adding SCSS

If you wish to utilise traditional CSS or whatsoever other styling method, this part is non required. However, since I wanted to use SCSS over CSS, I needed to add a new loader to my project.

To practice so, you lot'll need to decouple the project from the create-react-app tooling and install the sass-loader package.

Keep in listen that this is a permanent and non-reversible operation.

Eject the projection and install sass loader:

            npm run eject npm install sass-loader node-sass --relieve-dev          

This action volition notably expose webpack'south configuration files inside a directory called config.

Open up webpack.config.dev.js, webpack.config.dev.js and add together the following code inside the module's rules array:

            examination: /\.scss$/, use: [     "style-loader", // creates manner nodes from JS strings     "css-loader", // translates CSS into CommonJS     "sass-loader" // compiles Sass to CSS     ] }          

This fashion, you can import any SCSS files inside a component just like you would with a regular CSS file. For instance, in my App.js component, I've imported its advisable stylesheet.

            import './styles/app.scss';          

Quick tip while you are configuring everything: in the earlier days of React, if you wanted to utilise React's preprocessor, every file required the .JSX extension. Nonetheless, this is not the case anymore since Babel and project files now use the regular .js extension.

The issue with this kind of setup is that your text editor or IDE volition most probable not be able to tell the difference between both syntaxes.

If you're using VSCode (and you should!), you can ready this by adding the following snippet in your workspace settings:

            "files.associations": {     "*.js": "javascriptreact" }          

It volition come in helpful when auto-indenting or auto-completing HTML within the JavaScript code.

2.2 Generating ProductList component

Similar almost modern frontend libraries or framework, React uses components. Here, you'll create ii components:

  • The ProductList component will list all components.

  • The Product component will act as a view for a detailed production description.

First, generate a ProductList.js file inside a components directory with the following code:

            import React, { Component } from 'react'; import { Link } from 'react-router-dom' import BuyButton from './BuyButton';  class ProductList extends Component {   constructor(props) {     super(props);      this.land = {       loading: true,       products: []     }   }    async componentDidMount() {     let response = await fetch("https://snipcart-strapi.herokuapp.com/product");     if (!response.ok) {       return     }      let products = look response.json()     this.setState({ loading: false, products: products })   }    render() {     if (!this.state.loading) {       return (         <div className="ProductList">           <h2 className="ProductList-championship">Available Products ({this.state.products.length})</h2>           <div className="ProductList-container">             {this.state.products.map((product, alphabetize) => {               return (                 <div className="ProductList-product" key={product.id}>                   <Link to={`/product/${production.id}`}>                     <h3>{product.name}</h3>                     <img src={`https://snipcart-strapi.herokuapp.com${product.paradigm.url}`} alt={production.name} />                   </Link>                   <BuyButton product={product} />                 </div>               );             })}           </div>         </div>       );     }      render (<h2 className="ProductList-title">Waiting for API...</h2>);   } }  export default ProductList;          

In React, each component holds its ain state and takes creation parameters called props. This is why within your constructor, y'all'll need to pass your props to its parent class and prepare the land of your component as a JavaScript object with a loading primal and a products key.

Every time the state of a component is modified, the component will exist re-rendered using the return() method.

componentDidMount() is a function that'south called every time it's mounted. Fetch your products using the /product endpoint of the API inside this function.

Add together the response's information inside the state of your component likewise as update the loading key to false as y'all've completed the fetch.

Inside your Return() method, yous'll take ii potential returns. The first one is called if the loading value of the state is false, the other if it's true. This will foreclose rendering your products if you oasis't fetched the particular yet.

In case the loading is finished, you'll map the products from your API response to print out each product as well as rendering a buy push button. The BuyButton is a divide component that takes the product equally a prop to create its state.

            import React, { Component } from 'react';  course BuyButton extends Component {     constructor(props) {         super(props);          this.state = {             id: props.product.id,             proper noun: props.production.name,             price: props.production.price,             weight: props.production.weight,             clarification: props.product.description,             url: "https://snipcart-strapi.herokuapp.com/snipcartParser"         }     }      render() {         return (             <button                 className="snipcart-add-item BuyButton"                 data-item-id={this.land.id}                 data-detail-name={this.country.proper noun}                 data-particular-price={this.land.price}                 data-item-weight={this.state.weight}                 information-detail-url={this.state.url}                 data-item-description={this.state.clarification}>                 Add TO CART ({this.state.price}$)             </button>         );     } }  export default BuyButton;          

The link tag is imported into the react-router-dom package that will allow you to switch between different views. More about that later.

two.3 Creating Production component

The production component volition follow the aforementioned principles every bit the ProductList, but will be displayed when trying to view an individual production.

this.props.match.params.id is the value of the id that passed in the URL. I'll explain this in the next section.

            import React, { Component } from 'react'; import BuyButton from './BuyButton';  class Product extends Component {   constructor(props) {     super(props);      this.land = { loading: true, production: {} }   }    async componentDidMount() {     let response = wait fetch(`https://snipcart-strapi.herokuapp.com/production/${this.props.match.params.id}`)     let data = await response.json()     this.setState({       loading: false,       production: data     })   }    render() {     if (!this.state.loading) {       return (         <div className="product">           <div className="product__information">             <h2 className="Product-title">{this.state.production.name}</h2>             <img src={`https://snipcart-strapi.herokuapp.com/${this.country.product.image.url}`} />             <BuyButton {...this.state} />           </div>           <div className="product__description">             {this.country.product.description}           </div>         </div>       );     }      return (<h2>Waiting for API...</h2>);   } }  export default Product;          

two.4 Routing the app

Now, fourth dimension to plow the app into an SPA and leverage the new components. To do and then, install React Router, which is cleaved downwards into multiple packages.

You lot'll simply need the following:

            npm install --save react-router npm install --save react-router-dom          

react-router will provide the core functionalities for React Router while react-router-dom will contain all the browser related functionalities.

Inside the index.js file, wrap your app component within a BrowserRouter.

            import { BrowserRouter } from 'react-router-dom'  ReactDOM.return(<BrowserRouter><App /></BrowserRouter>, certificate.getElementById('root')); registerServiceWorker();          

Now, within the App component, import Road and Switch from react-router and link from react-router-dom:

            import { Road, Switch } from "react-router"; import { Link } from 'react-router-dom'          

This volition permit you to do the following inside the render of your App:

            <Switch>   <Route path="/" verbal component={ProductList} />   <Route path="/product/:id" component={Product} /> </Switch>          

Route renders the view of the appropriate component depending on its path.

Switch acts merely similar a regular switch instance and allows you to render a maximum of 1 of its road. The verbal attribute volition return the route only if the path is exact.

In this setup, the switch in unnecessary since I used the exact tag and I just accept ane more than route. But in case you add more routes, leave it wrapped inside a Switch.

:id is a placeholder that will be able to retrieve the real value within the product component using props.match.params similar you lot did earlier.

This should get out u.s.a. with something similar this:

            import React, { Component } from 'react'; import ProductList from './components/ProductList'; import { Road, Switch } from "react-router"; import { Link } from 'react-router-dom' import Product from './components/Product'; import './styles/app.scss';   class App extends Component {   return() {     return (       <div className="App">         <Header />         <main className="App-content">           <Switch>             <Route path="/" verbal component={ProductList} />             <Road path="/production/:id" component={Product} />           </Switch>         </primary>         <Footer />       </div >     );   } }          

In this snippet, Header and Footer are simple JavaScript functions defined below the app that return DOM tags.

            const Header = () => {   render (     <header className="App-header">       <Link to="/"><h1>🦐 Exotic Fish Supplies</h1></Link>       <div className="correct">         <push button className="snipcart-checkout snipcart-summary">           Checkout (<bridge className="snipcart-full-items"></bridge>)       </button>       </div>     </header>   ); }          

You can opt for this kind of syntax rather than apply ES6 classes when your components don't require lifecycle methods.

3. Integrating a shopping cart

To add Snipcart into your website simply include it inside the head tag of your index.html file located inside the public directory.

            <script src="https://ajax.googleapis.com/ajax/libs/jquery/two.2.2/jquery.min.js"></script> <script src="https://cdn.snipcart.com/scripts/2.0/snipcart.js" data-api-key="MDk5ZGFlMzEtZjVjMy00OTJkLThjNzEtZjdiOTUwNTQwYWMwNjM2Njg1ODA5NTQzMTIzMjc0" id="snipcart"></script> <link href="https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css" rel="stylesheet" blazon="text/css" />          

3.1 Writing a custom route using Strapi

Lastly, you'll need to create a route retrieving all products in a format valid with Snipcart'due south JSON crawler.

Although Strapi doesn't allow yous to filter specific fields from the response unless y'all accept GraphQL in your stack, y'all tin can create a custom route that will do this job for you.

Open routes.json in Strapi'due south product configuration directory (/api/product/config) and add the appropriate route.

            {     "method": "GET",     "path": "/snipcartParser",     "handler": "Product.snipcartParser",     "config": {     "policies": [] }          

Since Strapi uses an MVC approach, you'll also need to make the appropriate changes in the controller. Go in the controllers directory and add the following:

            /**  * Retrieves all the products with fields valid with Snipcart's JSON crawler.  *  * @return {Object}  */  snipcartParser: async (ctx) => {     allow products = await strapi.services.product.fetchAll(ctx.query);     return products.map(product => {         return {         id: product._id,         toll: product.cost,         url: "https://snipcart-strapi.herokuapp.com/snipcartParser"         }     }) }          

This office will fetch the products only similar the /product road would, but extract only the fields required by Snipcart.

Once this is done, y'all can restart your server and add the advisable permission to the new route inside the production content type.

GitHub repo & live demo

Endmost thoughts

I truly enjoyed edifice this demo. Strapi is a cakewalk to use, and I felt a little more at home using React as a frontend framework than I did with Angular.

Furthermore, I loved Strapi'due south approach. It's uncomplicated enough to exercise near of what you need to practice out of the box; withal, it'southward customizable enough to fit in most use cases as demonstrated when creating a custom road.

I spent less than two days building this demo application. Although Strapi is easy to use, you lot're not always a Google search away of solving your bug since it's still a relatively new tool. Thankfully, the creators are pretty agile in the community, and information technology's just a matter of time before most issues get adequately documented.

Even though putting together this integration gave me a skillful overview on what'southward bachelor in Strapi; there are a few concepts that have been untouched. For example, I would've liked to experiment with the policies concept built-in Strapi to create an authentification arrangement of some sorts.


If you've enjoyed this post, please take a second to share it on Twitter . Got comments, questions? Hit the section below!

collinsfess1990.blogspot.com

Source: https://snipcart.com/blog/node-js-react-strapi-tutorial

0 Response to "Sails Typeerror: Cannot Read Property 'attributes' of Undefined"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel