Categories
JavaScript Vue

Adding Modules to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add modules to separate a Vuex store into smaller parts.

Dividing a Store into Modules

Vuex uses a single state tree. This means the states are located in one big object. This will be bloated is our app grows big.

To make a Vuex store easier to scale, it can be separated into modules. Each module can have its own state, mutations, getters, and actions.

The state parameter in mutations and getters are the module’s local state.

By default, all actions, mutations, and getters inside modules are registered under a global namespace. This allows multiple modules to react to the same mutation or action type.

We can divide our store into module as in the following example:

index.js :

const moduleA = {  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const moduleB = {  
  state: {  
    count: 1  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const store = new Vuex.Store({  
  modules: {  
    a: moduleA,  
    b: moduleB  
  }  
});

console.log(store.state.a.count);  
console.log(store.state.b.count);

Then in the console.log output, we should see 0 and 1 since moduleA ‘s initial count state is 0 and moduleB ‘s initial count state is 1.

To make each module self-contained, we have to namespace it by setting the namespaced option to true .

We can namespace a module and then call dispatch on actions after namespacing the modules as follows:

index.js :

const moduleA = {  
  namespaced: true,  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const moduleB = {  
  namespaced: true,  
  state: {  
    count: 1  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const store = new Vuex.Store({  
  modules: {  
    a: moduleA,  
    b: moduleB  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    ...Vuex.mapState({  
      a: state => state.a,  
      b: state => state.b  
    })  
  },  
  methods: {  
    increaseA(payload) {  
      this.$store.dispatch("a/increase", payload);  
    },  
    increaseB(payload) {  
      this.$store.dispatch("b/increase", payload);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increaseA({amount: 10})">Increase</button>  
      <button @click="increaseB({amount: 10})">Increase</button>  
      <p>A Count: {{a.count}}</p>  
      <p>B Count: {{b.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

In the code above, we have namespaced: true set in each module, and then we added 2 methods to our Vue instance to dispatch the namespaced actions:

increaseA(payload) {  
  this.$store.dispatch("a/increase", payload);  
}

and:

increaseB(payload) {  
  this.$store.dispatch("b/increase", payload);  
}

Since we have namespaced set to true , we have to dispatch the actions by passing in “a/increase” and “b/increase” to dispatch .

Then once we clicked the buttons, our methods are called to dispatch the actions and the numbers will increase.

Register Global Action in Namespaced Modules

We can also register global actions in namespaced modules, by setting the root option to true and place the action definition in the function handler.

To do register a global action, we can write something like the following code:

index.js :

const moduleA = {  
  namespaced: true,  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const moduleB = {  
  namespaced: true,  
  state: {  
    count: 1  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const rootModule = {  
  actions: {  
    increaseAll: {  
      root: true,  
      handler(namespacedContext, payload) {  
        namespacedContext.commit("a/increase", payload);  
        namespacedContext.commit("b/increase", payload);  
      }  
    }  
  }  
};

const store = new Vuex.Store({  
  modules: {  
    a: moduleA,  
    b: moduleB,  
    root: rootModule  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    ...Vuex.mapState({  
      a: state => state.a,  
      b: state => state.b  
    })  
  },  
  methods: {  
    ...Vuex.mapActions(["increaseAll"])  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increaseAll({amount: 10})">Increase All</button>  
      <p>A Count: {{a.count}}</p>  
      <p>B Count: {{b.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

In the code above, we added the rootModule which has the global action as follows:

const rootModule = {  
  actions: {  
    increaseAll: {  
      root: true,  
      handler(namespacedContext, payload) {  
        namespacedContext.commit("a/increase", payload);  
        namespacedContext.commit("b/increase", payload);  
      }  
    }  
  }  
};

A global action has the root option set to true and a handler method which is used to dispatch actions from any module.

Then in the Vue instance, we have:

methods: {  
  ...Vuex.mapActions(["increaseAll"])  
}

to map the increaseAll action to a method in the Vue instance.

Then in the template, we have:

<button @click="increaseAll({amount: 10})">Increase All</button>

to call the increaseAll method returned from the mapActions method when the button is clicked.

Then we should both numbers increasing since we mapped both module’s state to the Vue instance’s data.

Dynamic Module Registration

We can also register a module dynamically by using the store.registerModule method as follows:

index.js :

const moduleA = {  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const store = new Vuex.Store({});

store.registerModule("a", moduleA);

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    ...Vuex.mapState({  
      a: state => state.a  
    })  
  },  
  methods: {  
    ...Vuex.mapActions(["increase"])  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increase({amount: 10})">Increase</button>  
      <p>A Count: {{a.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

The store.registerModule takes a string for the module name, and then an object for the module itself.

Then we can call the helpers to map getters to computed properties and actions/mutations to methods. In the code above, we have the increase action mapped to a method.

Then we can call it in our template as usual.

Conclusion

If our Vuex store is big, we can divide it into modules.

We can register modules when we create the store or dynamically with registerModule .

Then we can map actions/mutations by their name as usual, and we can map the state by accessing state.a.count , where a is the module name, and count is the state name. Replace it with our own module and state names if the code is different.

We can also namespace the modules. Then we dispatch the actions starting with the module name and a slash instead of just the name.

Categories
JavaScript Vue

Add Simple State Management with Vuex

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add Vuex to our app and add a simple store.

What is Vuex?

Vuex is for storing states that are needed by multiple components in one central location.

https://thewebdev.info/wp-content/uploads/2020/06/vuex.png

It lets us get and set shared state and propagate any changes made to the shared state automatically to all components.

In the workflow diagram above, we can see that Vuex mutations are committed by our code when we get something from the back end API.

The mutation will update the state with the back end API data and the state will be updated in our Vue components.

We can also dispatch mutations from Vue components to change the Vuex store state the change will be propagated to all components that have access to the store.

Getting Started

We can include Vuex with a script tag in our HTML code:

<script src="https://unpkg.com/vuex"></script>

Then we can create a simple store by using the Vuex.Store as follows:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

The code above is a store which stores the state count .

Then we can commit the increase mutation by running:

store.commit("increase");

Then we can get the state after the mutation is done by running:

console.log(store.state.count);

store.state.count is the count state from the Vuex store. Then we should see 1 logged.

Getting Vuex State into Vue Components

We can get the state into our store by adding a computed property.

Therefore, to add the state into our store, we can write the following:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  computed: {  
    count() {  
      return store.state.count;  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p>{{count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

Then we should see 0 displayed since count is 0 initially in the store .

Then whenever store.state.count updates, the computed property will be updated and the view will update with the new value.

A more convenient way to inject the store into all child components is to add the store in the root component as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p>{{$store.state.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

Then it’ll be available to all child components and we don’t have to worry about add computed properties for every value.

It works with child component without much changes:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Vue.component("counter", {  
  template: `<div>{{ count }}</div>`,  
  computed: {  
    count() {  
      return this.$store.state.count;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store  
});

this.$store is available to the counter component just by including it in the root Vue component.

The mapState Helper

To avoid adding a new computed property for every state that’s in the store, we can use the mapState helper to add it. For example, we can write the following code to do that:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Vue.component("counter", {  
  data() {  
    return {  
      localCount: 1  
    };  
  },  
  template: `  
    <div>  
      <div>{{ count }}</div>  
      <div>{{ countAlias }}</div>  
      <div>{{ countPlusLocal }}</div>  
    </div>  
  `,  
  computed: Vuex.mapState({  
    count: state => state.count,  
    countAlias: "count",  
    countPlusLocal(state) {  
      return state.count + this.localCount;  
    }  
  })  
});

new Vue({  
  el: "#app",  
  store  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <counter></counter>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

Then we see:

001

Because we called mapState as follows:

computed: Vuex.mapState({  
  count: state => state.count,  
  countAlias: "count",  
  countPlusLocal(state) {  
    return state.count + this.localCount;  
  }  
})

We have:

count: state => state.count,

which gets the count state from the store and returns it. The count is 0 so we get 0.

Then we have:

countAlias: "count"

which is a shorthand for:

count: state => state.count

And finally, we have:

countPlusLocal(state) {  
  return state.count + this.localCount;  
}

which adds state.count from the store to this.localCount , which we set to 1.

Object Spread Operator

We can combine local computed properties with mapState by applying the spread operator to mapState as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Vue.component("counter", {  
  data() {  
    return {  
      localCount: 1  
    };  
  },  
  template: `  
    <div>  
      <div>{{ count }}</div>  
      <div>{{ foo }}</div>        
    </div>  
  `,  
  computed: {  
    foo() {  
      return 2;  
    },  
    ...Vuex.mapState({  
      count: "count"  
    })  
  }  
});

new Vue({  
  el: "#app",  
  store  
});

Then we get:

02

displayed since foo always returns 2.

Components Can Still Have Local State

Components can still have their own local state, so we don’t have to put everything in the Vuex store.

Conclusion

We can add a Vuex store to our app to store the states of our app that are shared by multiple components.

To make getting state easy, we can include the store in the root Vue component.

Then we call the mapState helper to get the states we want in any component.

We can also combine it with the local states with the spread operator in the computed object.

Categories
Express JavaScript Mega-Guides Nodejs

Express Basics Mega-Guide

We can create back end apps running on the Node.js platform with Express.

In this article, we’ll look at how to create basic routes with Express.

Basic Routing

Routing is where an Express application response to a client request from a URL or path and a specific HTTP request method, like GET or POST.

Each route in Express can have one more handler function, which are executed when the route is matched.

The general definition of a route takes the following format:

app.METHOD(PATH, HANDLER);

app is the Express app instance.

The METHOD is an HTTP request method in lowercase. Possible methods include GET, POST, PUT, and DELETE.

PATH is the path for the route. HANDLER is the handler function that’s run when the route is matched.

For example, we can write:

app.get('/', (req, res) => {  
  res.send('Hello World!')  
})

To display 'Hello World!' on the screen.

If we want our app to accept a POST request, we can use app.post as follows:

app.post('/', (req, res) => {  
  res.send('Received POST request');  
})

We can test this with an HTTP client like Postman by send a POST request to the URL that our app is running on. Then we should get:

Received POST request

In the response body.

Likewise, we can do the same for PUT and DELETE requests as follows:

app.put('/', (req, res) => {  
  res.send('Got a PUT request at /user')  
})

app.delete('/', (req, res) => {  
  res.send('Got a DELETE request')  
})

Notice that in each route handler, we have a req and res parameter. The req has the request object which has the URL, headers and other fields.

The res object lets us render a response back to the client-side.

Request Object

The req parameter we have in the route handlers above is the req object.

It has some properties that we can use to get data about the request that’s made from the client-side. The more important ones are listed below.

req.baseUrl

The req.baseUrl property holds the base URL of the router instance that’s mounted.

For example, if we have:

const express = require('express');
const app = express();  
const greet = express.Router();  
greet.get('/', (req, res) => {  
  console.log(req.baseUrl);  
  res.send('Hello World');  
})

app.use('/greet', greet);

app.listen(3000, () => console.log('server started'));

Then we get /greet from the console.log .

req.body

req.body has the request body. We can parse JSON bodies with express.json() and URL encoded requests with express.urlencoded() .

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.post('/', (req, res) => {  
  res.json(req.body)  
})

app.listen(3000, () => console.log('server started'));

Then when we make a POST request with a JSON body, then we get back the same that we sent in the request.

req.cookies

We can get cookies that are sent by the request with the req.cookies property.

req.hostname

We can get the hostname from the HTTP header with req.hostname .

When the trust proxy setting doesn’t evaluate to false , then Express will get the value from the X-Forwarded-Host header field. The header can be set by the client or by the proxy.

If there’s more than one X-Forwarded-Host header, then the first one will be used.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.json(req.hostname)  
})

app.listen(3000, () => console.log('server started'));

Then we get the domain name that the app is hosted in if there’re no X-Forwarded-Host headers and trust proxy doesn’t evaluate to false .

req.ip

We can get the IP address that the request is made from with this property.

req.method

The method property has the request method of the request, like GET, POST, PUT or DELETE.

req.params

params property has the request parameters from the URL.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/:name/:age', (req, res) => {  
  res.json(req.params)  
})

app.listen(3000, () => console.log('server started'));

Then when we pass in /john/1 as the parameter part of the URL, then we get:

{  
    "name": "john",  
    "age": "1"  
}

as the response from the route above.

req.query

The query property gets us the query string from the request URL parsed into an object.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.json(req.query)  
})

app.listen(3000, () => console.log('server started'));

Then when we append ?name=john&age=1 to the end of the hostname, then we get back:

{  
    "name": "john",  
    "age": "1"  
}

from the response.

Response Object

The response object has some useful methods to let us return various kinds of responses.

res.append

The append method lets us attach response headers to our responses.

For example, if we have the following code:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.append('Link', 'http://localhost/', 'http://localhost:3000'])  
  res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly')  
  res.append('Warning', 'Alert')  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

Then when we go to Postman, we should see the same data returned in the Headers tab of the response when we look at the data.

Note that we have to run res.send() to actually send the response.

res.attachment

res.attachment let us add a file to the response. It doesn’t send the response.

For example, we can use it as follows:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.attachment('../public/foo.txt');  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

Then if we have a foo.txt in the public folder, then the file will be downloaded if we make the request to the route.

Note that again we have res.send() to actually send the response.

res.cookie

res.cookie lets us add a cookie to the response.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.cookie('name', 'foo', { domain: 'repl.it', path: '/', secure: true })  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

Then we send a cookie with name foo to the client. We can check in Postman under the Cookies link in the top right corner.

res.download

res.download sends a file response to the server.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.download('./public/foo.txt');  
})
app.listen(3000, () => console.log('server started'));

Then when a request is made to this route, then we’ll get a file downloaded.

res.json

res.json lets us send a JSON response to the client. The parameter can be any JSON type, including object, array, string, Boolean, number, or null.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.json({ message: 'hi' });  
})

app.listen(3000, () => console.log('server started'));

Then we get:

{"message":"hi"}

as the response.

res.redirect

We can use this to redirect to another URL with the string passed in. For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.redirect('http://medium.com'));  
})

app.listen(3000, () => console.log('server started'));

Then we’ll see the content of http://medium.com when we make the request to the route above.

res.status

res.status lets us send a status code response. We can use it with the end , send , or sendFile methods by calling them after calling status .

For example, if we have:

const express = require('express')  
const app = express()app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.status(403).end();  
})
app.listen(3000, () => console.log('server started'));

Then we get a 403 response.

Handling All Requests

We can use the app.all method to handle all request methods. To use it, we can write something like the following:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.all('*', (req, res, next) => {  
  console.log('route called');  
  next();  
})

app.get('/', (req, res) => {  
  res.send('hi');  
})

app.listen(3000, () => console.log('server started'));

The app.all call above will be invoked when any method or URL is sent. It’ll log 'route called' and then call next to call the specific route handler for that route.

Non-Letter Symbols

Express can match routes with non-letter symbols. For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/foo.bar', (req, res) => {  
  res.send('hi');  
})

app.listen(3000, () => console.log('server started'));

Then when we make a GET request to foo.bar then we get hi .

Special Characters

Optional Character

If the ? is in a URL path, then it’s considered an optional character. For example, ab?c matches ac or abc .

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/ab?c', (req, res) => {  
  res.send('hi');  
})

app.listen(3000, () => console.log('server started'));

Then when we make a request to /ac or /abc , we get hi .

One or More Character

If a URL has a + sign, then it’ll match one or more of the character preceding it.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/ab+c', (req, res) => {  
  res.send('hi');  
})

app.listen(3000, () => console.log('server started'));

Then when making a request to /abc , /abbc , /abbbc and so on, we’ll get hi .

Wildcard

The * in the URL is a wildcard character.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/ab\*c', (req, res) => {  
  res.send('hi');  
})

app.listen(3000, () => console.log('server started'));

Then we put anything between ab and c in the URL and get the hi response.

Optional Character Group

A character group is optional if it’s surrounded by parentheses and has a ? after it.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/ab(cd)?ef', (req, res) => {  
  res.send('hi');  
})
app.listen(3000, () => console.log('server started'));

Then when we go to /abcdef or /abef , we get hi .

Regular Expressions

We can also use regular expressions as route paths. For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get(/\/a/, (req, res) => {  
  res.send('hi');  
})
app.listen(3000, () => console.log('server started'));

Then when we make a request to /a , we get hi .

To get hi when we make any request with path that ends with foo , we can write:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get(/\/.\*foo$/, (req, res) => {  
  res.send('hi');  
})

app.listen(3000, () => console.log('server started'));

Route Parameters

We can designate parameters by writing a colon before the key name of the parameter.

For example, we can write:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/:name/:age', (req, res) => {  
  res.send(req.params);  
})

app.listen(3000, () => console.log('server started'));

Then we get the following response:

{"name":"Mary","age":"10"}

:name and :age are interpreted as route placeholders. req.params will use them as the keys without the colon. Then whatever is set in the place of the placeholders will be the corresponding values.

Route Handlers

The callback in the second argument of the app.get above are route handlers. The same can be done for POST, PUT and DELETE requests by replacing app.get with app.post , app.put and app.delete respectively.

We can chain multiple handlers together with the next function. For example, we can write:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/',  
  (req, res, next) => {  
    console.log('handler 1 called');  
    next()  
  },  
  (req, res) => {  
    console.log('handler 2 called');  
    res.send();  
  })

app.listen(3000, () => console.log('server started'));

Then we should see:

handler 1 called  
handler 2 called

in the console.log output.

We call the next function which originates from the route handler’s parameter to call the next route handler function.

Middlewares

Characteristics of Middleware

Middleware functions can run any code, make changes to the request and response object, end the request-response cycle, and call the next middleware in the stack.

A middleware may look something like the following code:

app.get('/', (req, res, next) => {  
  next();  
});

The code above has the request method in which the middleware will be called, which is the get in app.get .

Then we have the '/' which is the route path.

Finally, the middleware function that we pass in has the request and response objects as the first 2 parameters respectively, and the next function, which we call to run the next middleware.

Example

For example, we can write a middleware to log some output when we make a request to the / route.

We can write this as follows:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res, next) => {  
  console.log('middleware called');  
  next();  
});

app.get('/', (req, res) => {  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

next will call our route handler for the / route.

We should get middleware called from the console.log output and an empty response.

To load the middleware for all routes, we can use app.use instead of app.METHOD where METHOD is the request method is in lower case.

For example, we can write an app-wide middleware as follows:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.use((req, res, next) => {  
  console.log('middleware called');  
  next();  
});

app.get('/', (req, res) => {  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

We should get the same thing as before, but if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.use((req, res, next) => {  
  console.log('middleware called');  
  next();  
});

app.get('/', (req, res) => {  
  res.send();  
})

app.post('/foo', (req, res) => {  
  res.send('foo');  
})

app.listen(3000, () => console.log('server started'));

We get middleware called when we make a GET request to / and a POST request to /foo .

Modifying the Request and Response Objects

We can attach new properties and set values for the request and response objects.

For example, we can write:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.use((req, res, next) => {  
  req.requestTime = Date.now();  
  next();  
});

app.get('/', (req, res) => {  
  res.json(req.requestTime);  
})

app.post('/foo', (req, res) => {  
  res.json(req.requestTime);  
})

app.listen(3000, () => console.log('server started'));

Then we get the timestamp of when the request is made when we make a GET request to / and a POST request to /foo .

Likewise, we can do something similar with the response object:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.use((req, res, next) => {  
  res.responseTime = Date.now();  
  next();  
});

app.get('/', (req, res) => {  
  res.json(res.responseTime);  
})

app.post('/foo', (req, res) => {  
  res.json(res.responseTime);  
})

app.listen(3000, () => console.log('server started'));

We also will get the timestamp of when the response is made when we make a GET request to / and a POST request to /foo .

Configurable Middleware

We can make a function that has optional parameters and return a middleware function to create a configurable middleware function.

For example, we can write one as follows:

const express = require('express')  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

const configurableMiddleware = (options) => {  
  return (req, res, next) => {  
    req.options = options;  
    next();  
  }  
}

app.use(configurableMiddleware({ date: new Date(), foo: 'bar' }));

app.get('/', (req, res) => {  
  res.json(req.options);  
})

app.listen(3000, () => console.log('server started'));

The configurableMiddleware function takes an options object as a parameter and then return a middleware function with the req.options property set to the options parameter.

Then when we make a request to the / route then we get:

{"date":"2019-12-23T22:37:04.927Z","foo":"bar"}

as the response.

app.use([path,] callback [, callback…])

We can use the app.use method to mount middleware that’s run app-wide.

They can be run when requests for specific paths are made or for all paths.

It takes the following arguments:

  • path — it can be a string or regex representing paths or patterns of paths. The default is / .
  • callback — a function to handle requests. It can be a middleware function, a series of them, array of them, or a combination of all of the above

We can provide multiple callbacks that behave just like middleware, but they call next('route') to skip the remaining middleware functions.

We can use these to impose preconditions on some routes and pass control to the route if we don’t need to call the remaining middleware.

Since router and app implement the middleware interface, we can use them like any other middleware function.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));  
app.use((req, res, next) => {  
  console.log(`Request made at ${Date.now()}`)  
  next();  
})

app.get('/', (req, res) => {  
  res.send('hi');  
})

app.listen(3000);

Then we get the request time logged with every request.

We call next to call the route handler.

Middlewares are run sequentially, so the order they’re included in the code is important.

For example, if we have:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));  
app.use((req, res, next) => {  
  res.send('End with middleware');  
})

app.get('/', (req, res) => {  
  res.send('hi');  
})

app.listen(3000);

Then we get End with middleware instead of hi since we sent our response with the middleware, which we included first.

Error-Handling Middleware

We can create our own middleware for handling errors instead of using Express’s default error handler.

The error handler takes 4 arguments, which are the error object, request object, response object, and next function.

For instance, we can define one as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res, next) => {  
  try {  
    throw new Error('foo');  
  }  
  catch (ex) {  
    next(ex);  
  }  
})

app.use((err, req, res, next) => {  
  res.status(500).send('error');  
})

app.listen(3000);

The error should be displayed since we included the error handler after our GET request handler.

Paths

We can path in a string or regex path or path pattern as the first argument of the use method. This way, the middleware will only be called when the route matches the one specified in string or regex.

To match a constant path, we can write the following:

app.use('/abc', (err, req, res, next) => {  
  res.status(500).send('error');  
})

We can match patterns as follows. A ? will make the character preceding it optional:

app.use('/abc?d', (err, req, res, next) => {  
  res.status(500).send('error');  
})

The when requests made to /abcd or /abd has an error, the middleware above will be called.

The + sign means that one or more characters preceding it will be matched. For example, if we have:

app.use('/abc+d', (err, req, res, next) => {  
  res.status(500).send('error');  
})

The when requests made to /abccd,/abcccd, etc. has an error, the middleware above will be called.

* is a wildcard character. We can use it as follows:

app.use('/ab*cd', (err, req, res, next) => {  
  res.status(500).send('error');  
})

Then we get error when we make a request to /abcccd , /abFoocd , and so on if that has an error.

To match a group of optional characters, we can use parentheses and a question mark after it.

For instance, if we have:

app.use('/a(bcd)?e', (err, req, res, next) => {  
  res.status(500).send('error');  
})

Then we get error when we make a request to /abcde or/ae that has an error.

Passing in Middleware

We can pass in various combinations of middleware in addition to just one middleware.

We can pass in a router object as a middleware as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));const router = express.Router();  
router.get('/', (req, res, next) => {  
  res.send();  
});

app.use(router);app.listen(3000);

An Express app is also valid middleware:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
const subApp = express();
subApp.get('/', (req, res, next) => {  
  res.send();  
});

app.use(subApp);app.listen(3000);

We can also pass in a series of middleware as arguments:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));  
const r1 = express.Router();  
r1.get('/', (req, res, next) => {  
  console.log('r1 called');  
  next();  
});

const r2 = express.Router();  
r2.get('/', (req, res, next) => {  
  console.log('r2 called');  
  next();  
});  
app.use(r1, r2);
app.listen(3000);

Then they’ll be called in order, so we get:

r1 called  
r2 called

in the console.

Likewise, we can pass in an array of middlewares as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));  
const r1 = express.Router();  
r1.get('/', (req, res, next) => {  
  console.log('r1 called');  
  next();  
});

const r2 = express.Router();  
r2.get('/', (req, res, next) => {  
  console.log('r2 called');  
  next();  
});  
app.use([r1, r2]);
app.listen(3000);

Then we get the same output since they’re called in the same order that they’re listed.

A combination of them also works:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));  
const foo = (req, res, next) => {  
  console.log('foo called');  
  next();  
}

const r1 = express.Router();  
r1.get('/', (req, res, next) => {  
  console.log('r1 called');  
  next();  
});

const r2 = express.Router();  
r2.get('/', (req, res, next) => {  
  console.log('r2 called');  
  next();  
});

const subApp = express();  
subApp.get('/', (req, res, next) => {  
  console.log('subApp called');  
  next();  
})

app.use(foo, [r1, r2], subApp);app.listen(3000);

Then we get:

foo called  
r1 called  
r2 called  
subApp called

from the console. They’re still called in the order they’re listed.

Static Files

We can expose static folders with the express.static middleware. For example, if we want to expose the public folder in the project folder, we can write:

app.use(express.static(path.join(__dirname, 'public')));

Choosing Template Engines

We can use popular template engines with Express, like Pug, Mustache, and EJS. The default for the Express application generator is Jade, but we can use the other ones listed as well.

To render template files, we can set the application settings. To set the templates folder, we can set the views property by writing the following:

app.set('views', './views')

It’ll set the templates folder to the views directory of our app.

Also, we can set the view engine we want to use by setting the view engine property as follows:

app.set('view engine', 'pug')

To install the template engine library, we can run something like the following:

npm install pug --save

The command above will install pug view engine for us to use.

Rendering Items with Template Engines

We can use the res.render method to render our variables to our templates.

First, we create the views folder to store our templates as we set the ./views folder to be our templates folder.

Since we chose to use Pug as our template engine, the template files should have the .pug extension.

Inside the views folder, create an index.pug and then put:

html  
  head  
    title= title  
  body  
    h1= message

The title= and h1= are our tags and title and message are keys in which Express will use to get the values from in the object that we’ll pass to res.render .

Then we can create index.js in the same level as the views folder and add:

const express = require('express')  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))  
app.set('views', './views');  
app.set('view engine', 'pug');

app.get('/', (req, res, next) => {  
  res.render('index', { pageTitle: 'Hello', pageMessage: 'Hello there!' });  
})

app.listen(3000, () => console.log('server started'));

In the code above, we called res.render to render an HTML response. The object:

{ pageTitle: 'Hello', pageMessage: 'Hello there!' }

will be passed to our index.pug template.

title will be the title in:

html  
  head  
    title= pageTitle  
  body  
    h1= pageMessage

message will be replaced by the 'Hello there!' . Anything right of the tag and the equal sign will be replaced by the value with the key given above.

In other words, pageTitle and pageMessage properties’ values’ are the ones that’ll go in between the title and h1 tags respectively.

So we get

and the title of our page will be 'Hello' .

Pug Syntax

We can have dynamic expressions other than variables. Loops and conditional expressions are part of Pug.

HTML Tags

Other HTML tags can be used with Pug.

For example, a link can be written as follows:

a(href='http://medium.com') Medium

In the code above Medium goes between the a tags and href=’http://medium.com' is the href attribute and value respectively.

Loops

We can also write loops with Pug. For example, if we have:

const express = require('express')  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))  
app.set('views', './views');  
app.set('view engine', 'pug');

app.get('/', (req, res, next) => {  
  res.render('index', { pageTitle: 'Hello', pageMessages: ['Hi', 'Bye'] });  
})

app.listen(3000, () => console.log('server started'));

in index.js and:

html  
  head  
    title= pageTitle  
  body  
    ul  
      each pageMessage in pageMessages  
        li= pageMessage

in views/index.pug , then we would get:

Conditionals

We can also add conditional expressions to our Pug template. For example, if we have:

const express = require('express')  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))  
app.set('views', './views');  
app.set('view engine', 'pug');

app.get('/', (req, res, next) => {  
  res.render('index', { pageTitle: 'Hello', user: { type: 'member' }   });  
})  
app.listen(3000, () => console.log('server started'));

in index.js and:

html  
  head  
    title= pageTitle  
  body  
    if user.type === 'anonymous'  
      p User type is anonyomous  
    else if user.type === 'member'  
      p User type is member  
    else  
      p User type is admin

in views/index.pug , then we get:

'User type is member'

since user.type in res.render is set to 'member' .

The body-parser Package

By default, Express 4.x or later doesn’t come with anything to parse request bodies. Therefore, we need to add something to do this.

Adding the Body-Parser Package

body-parser is a Node package that we can add onto our Express app to parse request bodies.

It doesn’t support multipart bodies, so we have to use other middleware packages to parse those.

However, it can parse JSON bodies, raw request bodies, text, and URL-encoded bodies.

To install the package, we run:

npm install body-parser

Then we can include by adding:

const bodyParser = require('body-parser');

Parsing JSON Bodies

We can parse JSON bodies by calling the JSON method. It takes an optional object with a few properties.

The options include:

  • inflate — compressed request bodies will be inflated when this is set to true . Otherwise, they’ll be rejected.
  • limit — controls the maximum request body size. If it’s number, then it’s measured in bytes. If it’s a string then it can be parsed into a number of bytes.
  • reviver — this is a function that’s passed into JSON.parse as the second argument to map values to what we want
  • strict — only accepts objects and arrays when set to true . Otherwise, it’ll accept anything that JSON.parse accepts. Defaults to true .
  • type — this is used to determine what media type it’ll parse. It can be a string, array of strings or a function. If it’s not a function, then it’s directly passed into the type-is library. Otherwise, the request is parsed if the data type the function is called with returns a truthy value
  • verify — this is a function with signature (req, res, buf, encoding) , where buf is a Buffer object of the raw request body. The parsing can be aborted by throwing an error in the function.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();  
const options = {  
  inflate: true,  
  limit: 1000,  
  reviver: (key, value) => {  
    if (key === 'age') {  
      if (value < 50) {  
        return 'young'  
      }  
      else {  
        return 'old';  
      }  
    }  
    else {  
      return value;  
    }  
  }  
};  
app.use(bodyParser.json(options));
app.post('/', (req, res) => {  
  res.send(req.body);  
});
app.listen(3000);

Then when we make a POST request to / , we get back:

{  
    "name": "foo",  
    "age": "young"  
}

as the response since we check the age field in the reviver function and return 'young' or 'old' depending on the value . Otherwise, we return the value as-is.

The request body is parsed and set as the value of req.body .

Parsing Raw Bodies

We can parse raw bodies as a buffer. It supports the automatic inflation of gzip and deflate encodings.

The parsed body containing the parsed data is populated on the request object, i.e. it’ll be set as the value of req.body .

It takes an optional option object that can take the following properties:

  • inflate — compressed request bodies will be inflated when this is set to true . Otherwise, they’ll be rejected.
  • limit — controls the maximum request body size. If it’s number, then it’s measured in bytes. If it’s a string then it can be parsed into a number of bytes.
  • type — this is used to determine what media type it’ll parse. It can be a string, array of strings or a function. If it’s not a function, then it’s directly passed into the type-is library. Otherwise, the request is parsed if the data type the function is called with returns a truthy value
  • verify — this is a function with signature (req, res, buf, encoding) , where buf is a Buffer object of the raw request body. The parsing can be aborted by throwing an error in the function.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();  
const options = {  
  inflate: true,  
  limit: 1000,  
  type: 'text/plain'  
};  
app.use(bodyParser.raw(options));
app.post('/', (req, res) => {  
  res.send(req.body);  
});
app.listen(3000);

Then when we send a POST request to / with the body foo , we get back foo as the response.

This is because we specified text/plain as the type to parse the raw data.

It also smart enough to parse multiple body types. We can do that by passing in an array of type strings as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();  
const options = {  
  inflate: true,  
  limit: 1000,  
  type: ['text/plain', 'text/html']  
};  
app.use(bodyParser.raw(options));
app.post('/', (req, res) => {  
  res.send(req.body);  
});
app.listen(3000);

Then when we make a POST request to / with the body <b>foo</b> . Then we get back <b>foo</b> .

Upload Files with Multer

Multer is a middleware that lets us process file uploads with our Express app.

We can install it by running:

npm install --save multer

Once it’s installed we have to make a form that has the encoding type multipart/form-data .

It won’t process any form that’s not multipart, i.e. has the enctype set to multipart/form-data .

Simple Usage

The simplest usage is to allow upload of one file at a time. We can do this by calling the multer function and setting the path to store the uploaded file as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const upload = multer({ dest: './uploads/' });  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/upload', upload.single('upload'), (req, res) => {  
  res.send('file uploaded')  
});
app.listen(3000, () => console.log('server started'));

Then we can create index.html in the public folder and add:

<!DOCTYPE html>  
<html>
<head>  
  <meta charset="utf-8">  
  <meta name="viewport" content="width=device-width">  
  <title>Upload</title>  
  <link href="style.css" rel="stylesheet" type="text/css" />  
</head>
<body>  
  <form action="/upload" method="post" enctype="multipart/form-data">  
    <input type="file" name="upload" />      
    <input type='submit' value='Upload'>  
  </form>  
</body>
</html>

The code for index.html will make the form submit multipart form data.

Also, the value of thename attribute has to match with the name string that’s passed into the upload.single() method.

Once the file is upload, a random ID will be generated for the uploaded file’s name.

req.files will have the file objects and req.body will have the values of the text fields.

Uploading Multiple Files

We can also process the upload of multiple files with the upload.array method as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const upload = multer({ dest: './uploads/' });  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/upload', upload.array('uploads', 5), (req, res) => {  
  res.send('files uploaded')  
});
app.listen(3000, () => console.log('server started'));

Then we have to change index.html to:

<!DOCTYPE html>  
<html>
<head>  
  <meta charset="utf-8">  
  <meta name="viewport" content="width=device-width">  
  <title>Upload</title>  
  <link href="style.css" rel="stylesheet" type="text/css" />  
</head>
<body>  
  <form action="/upload" method="post" enctype="multipart/form-data">  
    <input type="file" name="uploads" multiple />  
    <input type='submit' value='Upload'>  
  </form>  
</body>
</html>

We add the multiple attribute to the form to enable multiple uploads in addition to using the upload.array method.

The 5 in the second argument of the upload.array call is the maximum number of files that we can upload.

Once the file is upload, a random ID will be generated for the uploaded file’s name.

Multiple File Fields

We can enable upload of multiple files as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const upload = multer({ dest: './uploads/' });  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
const multiUpload = upload.fields([  
  { name: 'photos', maxCount: 3 },  
  { name: 'texts', maxCount: 5 }  
])  
app.post('/upload', multiUpload, (req, res) => {  
  console.log(req.files);  
  res.send('files uploaded');  
});
app.listen(3000, () => console.log('server started'));

Then when we have the following in public/index.html :

<!DOCTYPE html>  
<html><head>  
  <meta charset="utf-8">  
  <meta name="viewport" content="width=device-width">  
  <title>Upload</title>  
  <link href="style.css" rel="stylesheet" type="text/css" />  
</head><body>  
  <form action="/upload" method="post" enctype="multipart/form-data">  
    <label>Photos</label>  
    <br>  
    <input type="file" name="photos" multiple />  
    <br>  
    <label>Texts</label>  
    <br>  
    <input type="file" name="texts" multiple />  
    <br>  
    <input type='submit' value='Upload'>  
  </form>  
</body></html>

We get the files uploaded after we select the files and submit them. The code:

const multiUpload = upload.fields([  
  { name: 'photos', maxCount: 3 },  
  { name: 'texts', maxCount: 5 }  
])

Gets the files from the fields photos and texts and upload to 3 and 5 files respectively.

In the console.log , we see the files of the POST /upload route that we uploaded.

Upload Nothing

To skip the processing of upload files, we can use the upload.none() method as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const upload = multer();  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/submit', upload.none(), (req, res) => {  
  res.send(req.body)  
});
app.listen(3000, () => console.log('server started'));

Then when have the following for public/index.html :

<!DOCTYPE html>  
<html><head>  
  <meta charset="utf-8">  
  <meta name="viewport" content="width=device-width">  
  <title>Upload</title>  
  <link href="style.css" rel="stylesheet" type="text/css" />  
</head><body>  
  <form action="/submit" method="post" enctype="multipart/form-data">  
    <input type="text" name="name" />      
    <input type='submit' value='Submit'>  
  </form>  
</body></html>

We get the submitted POST body in req.body as we can see from the response after typing in something and clicking Submit.

Checking File Information

We can check the uploaded files’ information by looking at the req.file object for single file upload and the array entries of req.files for multiple file uploads.

These fields are available for each file:

  • fieldname — field name specified in the form
  • originalname — name of the file on the user’s computer
  • encoding — encoding type of the file
  • mimetype — MIME-type of the file
  • size — size of the file in bytes
  • destination — the folder where the file was saved on the server
  • filename — name of the file stored in the destination
  • path — the full path of the uploaded file
  • bufferBuffer object of the whole file

Options for File Upload

The multer function takes an options object that takes a variety of options.

We can do things like set the destination of the files and rename the files.

The following properties can be in the options object:

  • dest or storage — where to store the files
  • fileFilter — controls which files are accepted
  • limits — limits of the uploaded data
  • preservePath — keep the full path of the files instead of just the base name

Storage Options

DiskStorage

We can store files on disk by using the diskStorage method.

There’re 2 options available, destination and filename . They’re both functions that determine the destination where the file is saved and what to rename the file to respectively.

Each function takes the requestion object, file object and callback function. The callback function is called at the end of each function with the first argument being null .

The second argument is the destination that we want to save the file for the destination function and the filename that we want to rename the file to for the filename function.

For example, we can rename a file by keeping the original name and adding a timestamp to the end of the file as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const storage = multer.diskStorage({  
  destination: (req, file, cb) => {  
    cb(null, './uploads/')  
  },  
  filename: (req, file, cb) => {  
    cb(null, `${file.originalname}-${+Date.now()}`)  
  }  
})
const upload = multer({ storage });  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/upload', upload.single('upload'), (req, res) => {  
  res.send('file uploaded')  
});
app.listen(3000, () => console.log('server started'));

MemoryStorage

memoryStorage stores files in memory as a Buffer object and doesn’t take any option.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const storage = multer.memoryStorage();  
const upload = multer({ storage })  
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/upload', upload.single('upload'), (req, res) => {  
  console.log(req.file);  
  res.send('uploaded');  
});
app.listen(3000, () => console.log('server started'));

Then we get the buffer property in req.file with the content of the upload file as the value of it.

Upload Limits

The limits object specifies the size limits of the following optional properties:

  • fieldNameSize — maximum field name size. Defaults to 100 bytes
  • fieldSize — maximum field value size. Defaults to 1MB
  • fields — the maximum number of non-file fields. Defaults to Infinity
  • fileSize — maximum file size in bytes. Defaults to Infinity
  • files — maximum of file fields. Defaults to Infinity
  • parts — the maximum number of parts (fields and files). Defaults to Infinity
  • headerPairs — the maximum number of header key-value pairs to parse. Defaults to 2000.

This is useful for preventing denial of service attacks.

We can set the limits as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const upload = multer({  
  limits: {  
    fieldSize: 1024 * 512,  
    fieldNameSize: 200  
  },  
  dest: './uploads/'  
});  
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/upload', upload.single('upload'), (req, res) => {  
  res.send('file uploaded')  
});
app.listen(3000, () => console.log('server started'));

We set the field size limit to 512 KB and field name size to 200 bytes in the code above.

Controlling the Files to Process

The fileFilter field is a function that lets us control which files should be uploaded and which should be skipped.

For example, we can throw an error if the file uploaded doesn’t have the MIME-type image/png and then handle the error in our route as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const multer = require('multer');  
const upload = multer({  
  fileFilter: (req, file, cb) => {  
    if (file.mimetype === 'image/png') {  
      cb(null, true);  
    }  
    else {  
      cb(new multer.MulterError('not a PNG'));  
    }  
  },  
  dest: './uploads/'  
})  
  .single('upload')  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.post('/upload', (req, res) => {  
  upload(req, res, (err) => {  
    if (err instanceof multer.MulterError) {  
      res.send('file not uploaded since it\'s not a PNG');  
    }  
    else {  
      res.send('file uploaded');  
    }  
  })  
});
app.listen(3000, () => console.log('server started'));

First, we have the file type check by setting a function with the fileFilter function:

const upload = multer({  
  fileFilter: (req, file, cb) => {  
    if (file.mimetype === 'image/png') {  
      cb(null, true);  
    }  
    else {  
      cb(new multer.MulterError('not a PNG'));  
    }  
  },  
  dest: './uploads/'  
})  
  .single('upload')

Then in our /upload route, we have:

app.post('/upload', (req, res) => {  
  upload(req, res, (err) => {  
    if (err instanceof multer.MulterError) {  
      res.send('file not uploaded since it\'s not a PNG');  
    }  
    else {  
      res.send('file uploaded');  
    }  
  })  
});

to catch the MulterError and respond accordingly. If the file is a PNG, then it’s uploaded. Otherwise, an error is thrown and the error won’t be uploaded.

In either case, we send a response indicating if the file was uploaded.

Storing Sessions

To store confidential session data, we can use the express-session package. It stores the session data on the server and gives the client a session ID to access the session data.

express-session is a Node package. We can install it by running:

npm install express-session

Then we can include it in our app by adding:

const session = require('express-session');

Usage

The session function returns a middleware to let us store sessions. It takes an option object with the following properties:

cookie

Settings object for the session ID cookie. The default value is:

{ path: '/', httpOnly: true, secure: false, maxAge: null }.

It takes the following properties:

cookie.domain

It specifies the value for the Domain Set-Cookie attribute. No domain is set by default. Most clients will consider the cookie to apply only to the current domain.

cookie.expires

This specifies the Date object of the value for the Expires Set-Cookie attribute. No expiration date is set by default. This means the cookie will be deleted when the session ends.

If both expires and maxAge are set, then the last one defined if what’s used. expires shouldn’t be set directly. We should set maxAge .

cookie.httpOnly

Specifies the boolean value for the HttpOnly Set-Cookie attribute. When it’s truthy, the HttpOnly attribute is set. Otherwise, it’s not. By default, only HttpOnly attribute is set.

cookie.maxAge

The number of milliseconds to use when calculating the Expires Set-Cookie attribute. This is calculated by taking the current server time and adding maxAge milliseconds to it to calculate the Expires datetime. No maximum age is set by default.

cookie.path

Sets the Path Set-Cookie attribute value. This is set to / by default which is the root path of the domain.

cookie.sameSite

A boolean or string for the value of the SameSite Set-Cookie attribute. It can be:

  • true — will set the SameSite attribute to Strict for strict same site enforcement
  • false — won’t set the SameSite attribute
  • 'lax' — set the SameSite attribute to Lax for lax same-site enforcement
  • 'strict' set the SameSite attribute to Strict for strict same-site enforcement

cookie.secure

Boolean value for the Secure Set-Cookie attribute. The Secure attribute is set. Otherwise, it’s not.

Cookies won’t be sent over HTTP connects if this is set to true .

However, it’s recommended. If the app is hosted behind a proxy, then trust proxy must be set if secure is set to true .

genid

A function to generate a new session ID. The default value is a function that uses the uid-safe library to generate IDs.

name

The name of the session ID cookie to set in the response. The default value is 'connect.sid' .

proxy

Trust the reverse proxy when setting cookies via the X-Forwarded-Proto header.

The default value is undefined . Other possibles include:

  • true — the X-Forwarded-Proto header will be used
  • false — all headers are ignored and the connection is considered secure only if it’s a direct SSL/TLS connection
  • undefined — uses the 'trust proxy' setting from Express

resave

Forces the session to be saved back to the session store even if the session was never modified during the request.

This may create race conditions when a client makes 2 parallel requests to our server and changes made to the session on one request may be overwritten when the other request ends.

Defaults to true , but the default may change in the future. false makes more sense as the option.

rolling

Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge , resetting the expiration countdown.

Defaults to false .

When this is set to true and saveUnintialized option is false , the cookie won’t be set on a response with uninitialized session

saveUnitialized

Force a session that’s uninitialized to be saved to the store. A session is uninitialized when it’s new but not modified. Setting this to false reduces server storage usage and comply with laws that require permission before storing cookies.

secret

This is a required option for the secret to sign the session ID cookie. It can be a string or an array of multiple string secrets.

store

The session store instance. It defaults to a new MemoryStore instance.

unset

Controls the result of unsetting req.session through delete , setting to null , etc.

Defaults to 'keep' . The possible values are:

  • 'destroy' — the session will be deleted when the response ends
  • 'keep' — the session will be kept but modifications made during the request aren’t saved.

req.session

We access the session data by using req.session . The data is typically JSON, so nested objects are fine.

req.session comes with the following methods:

regenerate(callback)

We call this to regenerate the session. Once it’s called, a new session ID and Session instance will be initialized at req.session and the callback will be called.

destroy(callback)

Destroys a session. req.session will be unset. Once it’s called, the callback will be called.

reload(callback)

Reloads the session data from the store and re-populates the req.session object. Once it’s done, the callback will be called.

save(callback)

Saves a session back to the store, replacing the contents of the store with the contents in memory.

This usually doesn’t need to be called.

touch()

Updates the maxAge property. This shouldn’t be called usually as this is done automatically.

id

We can get the session ID with the id property.

cookie

This property has the cookie content. This also lets us alter the cookie for the user.

Cookie.maxAge

We can set the maxAge property to change the expiry time in milliseconds.

Cookie.originalMaxAge

We can use this to get the original maxAge value in milliseconds.

req.sessionID

We can get the session ID of the loaded session with sessionId .

Implementing our Own Session Store

Session store must implement EventEmitter with additional methods.

store.all(callback)

An optional method to get all sessions in the store as an array. The callback signature is callback(error, sessions) and it’s called once the session is retrieved.

store.destroy(sid, callback)

A required method to delete a session from the store given the session ID. The callback has the signaturecallback(error) . It’s called once the session is deleted.

store.clear(callback)

An optional method to delete all sessions from the store. The callback signature is callback(error) and it called once the store is cleared.

store.length(callback)

An optional method to count all the sessions in the store. The callback has the signature callback(error, len).

store.get(sid, callback)

A required method to get the session data by the session ID (sid ). The callback has the signature callback(error, session) .

The session is returned if found, otherwise returns null or undefined .

store.set(sid, session, callback)

A required method to set the session given the sid , which is the session ID. The callback has the signature callback(error) once the session is set.

store.touch(sid, session, callback)

This method is suggested to be implemented. This is used to signal to the store that the given session is active. The callback has the signature callback(error) .

Example

An example would be storing the number of views and keep that data for 365 days. We can do that as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const session = require('express-session');  
const app = express();  
app.set('trust proxy', 1)  
app.use(session({  
  secret: 'secret',  
  resave: true,  
  cookie: {  
    maxAge: 24 * 60 * 60 * 365 * 1000  
  }  
}))
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  if (req.session.views) {  
    req.session.views++;  
  }  
  else {  
    req.session.views = 1;  
  }  
  res.send(`${req.session.views} views`);  
})
app.listen(3000);

In the code, we have the secret so that the cookie will be signed. Then in our route handler, we can get and set the data by getting and setting req.session.views , which we created.

Then we should see the number of views as we keep reloading the / route. It sets to expire in 365 days by setting maxAge, so the number will keep going up.

In the response header, we should get that the Set-Cookie response header has a value like:

connect.sid=s%3Ad6jfPu5awWj3EXPF-MdtU8cPzjOY5NRT.33nyXjKdShPAPyw9WWwY4sywP44IOX5SPSt2WUkH0cs; Path=/; Expires=Mon, 28 Dec 2020 00:47:31 GMT; HttpOnly

We have the value of the session ID and expiry time.

Conclusion

We can use Express to create back end apps easily.

It comes with a router to route requests.

All logic is written as middleware that we can compose.

We use the body-parser to parse text request payloads.

Multer is used for handling file uploads.

Categories
JavaScript Mega-Guides React

React Basics Mega-Guide

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

In this article, we’ll look at how to create simple apps with React.

Getting Started

The easiest way to create a React app is to use the Create React App Node package.

We can run it by running:

npx create-react-app my-app

Then we can go to the my-app and run the app by running:

cd my-app  
npm start

Create React App is useful for creating a single-page app.

React apps don’t handle any backend logic or databases.

We can use npm run build to build the app to create the built app for production.

Creating Our First React App

Once we ran Create React App as we did above, we can create our first app. To create it, we go into App.js and then start changing the code there.

To make writing our app easy, we’ll use JSX to do it. It’s a language that resembles HTML, but it’s actually just syntactic sugar on top of JavaScript.

Therefore, we’ll use the usual JavaScript constructs in JSX.

We’ll start by creating a Hello World app. To do this, we replace what’s there with the following in index.js:

import React from "react";  
import ReactDOM from "react-dom";

function App() {  
  return (  
    <div>  
      <h1>Hello World</h1>  
    </div>  
  );  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the App component, which is just a function. It returns:

<div>  
  <h1>Hello World</h1>  
</div>

which is our JSX code to display Hello World. h1 is a heading and div is a div element.

The code above looks like HTML, but it’s actually JSX.

What we have above is a function-based component since the component is written as a function.

In the last 2 lines, we get the element with ID root from public/index.html and put our App component inside it.

Another way to write the code above is to write:

import React from "react";  
import ReactDOM from "react-dom";

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <h1>Hello World</h1>  
      </div>  
    );  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

The code above is a class-based component, which has a render method to render the same JSX code into HTML.

The difference between the 2 examples is that one is a function and the other is a class that extends React.Component .

Otherwise, they’re the same. Any component file has to include:

import React from "react";  
import ReactDOM from "react-dom";

Otherwise, we’ll get an error.

React doesn’t require JSX, it’s just much more convenient to use it. A third way to create a Hello World app is to use the React.createElement method.

We can use the method as follows:

import React from "react";  
import ReactDOM from "react-dom";

const e = React.createElement;  
const App = e("h1", {}, "Hello World");

const rootElement = document.getElementById("root");  
ReactDOM.render(App, rootElement);

The first argument of the createElement method is the tag name as a string, the second argument has the props, which are objects that we pass to the component created, and the third argument is the inner text of them element.

We won’t be using this very often since it’ll get very complex if we have to nest components and adding interaction.

Embedding Expressions in JSX

We can embed JavaScript expressions between curly braces. For example, we can write:

import React from "react";  
import ReactDOM from "react-dom";  
function App() {  
  const greeting = "Hello World";  
  return (  
    <div>  
      <h1>{greeting}</h1>  
    </div>  
  );  
}  
const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

Then we see Hello World on the screen again.

Something more useful world calling a function as follows:

import React from "react";  
import ReactDOM from "react-dom";  
function App() {  
  const formatName = user => {  
    return `${user.firstName} ${user.lastName}`;  
  }; 

  const user = {  
    firstName: "Jane",  
    lastName: "Smith"  
  }; 

  return (  
    <div>  
      <h1>{formatName(user)}</h1>  
    </div>  
  );  
}  
const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we defined a formatName function inside the App component that takes a user object and returns user.firstName and user.lastName joined together.

Then we defined a user object with those properties and called the function inside the curly braces.

Whatever’s return will be displayed between the braces. In this case, it’ll be Jane Smith.

Lifting State Up

We should lift any shared state up to their closest common ancestor.

This way, one state can be shared between multiple child components by passing them down via props.

For example, if we want to build a calculator for converting lengths, we can write the following:

import React from "react";  
import ReactDOM from "react-dom";

class LengthInput extends React.Component {  
  constructor(props) {  
    super(props);  
    const { length } = this.props;  
    this.state = { length };  
  }  
  
  handleChange(e) {  
    this.props.onChange(e);  
  } 

  componentWillReceiveProps(props) {  
    const { length } = props;  
    this.setState({ length });  
  } 

  render() {  
    const { unit } = this.props;  
    return (  
      <>  
        <label>Length ({unit})</label>  
        <input  
          value={this.state.length}  
          onChange={this.handleChange.bind(this)}  
          placeholder={this.props.unit}  
        />  
        <br />  
      </>  
    );  
  }  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { lengthInMeter: 0, lengthInFeet: 0 };  
  } 

  handleMeterChange(e) {  
    this.setState({ lengthInMeter: +e.target.value });  
    this.setState({ lengthInFeet: +e.target.value \* 3.28 });  
  } 

  handleFeetChange(e) {  
    this.setState({ lengthInFeet: +e.target.value });  
    this.setState({ lengthInMeter: +e.target.value / 3.28 });  
  } 

  render() {  
    return (  
      <div className="App">  
        <LengthInput  
          length={this.state.lengthInMeter}  
          onChange={this.handleMeterChange.bind(this)}  
          unit="meter"  
        />  
        <LengthInput  
          length={this.state.lengthInFeet}  
          onChange={this.handleFeetChange.bind(this)}  
          unit="feet"  
        />  
      </div>  
    );  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have a length converter than converts meters to feet when we’re typing in the meters field and vice versa.

What we’re doing is that we keep the lengths all in the App component. This is why the principle is called lifting the states up.

Since we’re keeping the lengths in the App component, we have to pass them down to the LengthInput components.

To do that, we pass props to them. Also, we pass in the units, and the change handler functions down to our LengthInput components, so that they can the functions to update the states in the App component.

In the handleFeetChange and handleMeterChange functions, we set the state according to the values entered in the LengthInput components.

We call this.setState in both functions to set the states. Each time setState is called, render will be called, so that the latest state will be passed down to our LengthInput components.

In the LengthInput components, we have the componentWillReceiveProps hook which will get the latest prop values and then set the length state accordingly.

this.state.length is set as the value of the input elements in LengthInput , so the inputted value will be shown.

The advantage of lifting the states up to the parent component is that we only have to keep one copy of the state. Also, we don’t have to repeat the processing of the states in different child components.

The values of the inputs stay in sync because they’re computed from the same state.

Uncontrolled Components

Uncontrolled components are where we use the DOM properties to manipulate form inputs.

We can use a ref to get form values directly from the DOM.

For example, we can write the following:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.input = React.createRef();  
  } 

  handleSubmit(event) {  
    alert(`Name submitted: ${this.input.current.value}`);  
    event.preventDefault();  
  } 

  render() {  
    return (  
      <form onSubmit={this.handleSubmit.bind(this)}>  
        <label>  
          Name:  
          <input type="text" ref={this.input} />  
        </label>  
        <input type="submit" value="Submit" />  
      </form>  
    );  
  }  
}

In the code above we created the this.input ref in the constructor and attached it to the input element.

Then when we type in something and click Submit, we get whatever we typed into the input box displayed in the alert box since we accessed it with this.input.current.value .

This makes integrating React and non-React code easier since we’re using the DOM to get the value.

Default Values

The value attribute on form elements will override the value in the DOM.

With an uncontrolled component, we often want to specify the initial value and leave subsequent updates uncontrolled.

We can set the defaultValue attribute to do this:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.input = React.createRef();  
  } 

  handleSubmit(event) {  
    alert(`Name submitted: ${this.input.current.value}`);  
    event.preventDefault();  
  } 

  render() {  
    return (  
      <form onSubmit={this.handleSubmit.bind(this)}>  
        <label>  
          Name:  
          <input type="text" defaultValue="Foo" ref={this.input} />  
        </label>  
        <input type="submit" value="Submit" />  
      </form>  
    );  
  }  
}

In the code above, we set the defaultValue attribute to Foo , which is the value of this.input.current.value if we don’t change the input value.

The file input Tag

File inputs are always uncontrolled components because its value can only be set by a user and not programmatically.

Therefore, we have to use a ref to access its value.

For example, we can write the following:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.fileRef = React.createRef();  
  } 

  handleSubmit(event) {  
    alert(`Filename: ${this.fileRef.current.files[0].name}`);  
    event.preventDefault();  
  } 

  render() {  
    return (  
      <form onSubmit={this.handleSubmit.bind(this)}>  
        <input type="file" ref={this.fileRef} />  
        <input type="submit" value="Submit" />  
      </form>  
    );  
  }  
}

In the code above, we created a ref called fileRef and attached it to the file input. Then we get the files[0].name property that has the name of the selected file.

We can use the File API to do things with the selected file.

Lists and Keys

We can transform lists into HTML by calling the array’s map method.

For example, if we want to display an array of numbers as a list, we can write:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
  } 

  render() {  
    return (  
      <div>  
        {[1, 2, 3, 4, 5].map(num => (  
          <span>{num}</span>  
        ))}  
      </div>  
    );  
  }  
}

In the code above, we called map on the [1, 2, 3, 4, 5] array. In the map method’s callback, we returned a span with the number inside and we do the same for each element.

Then we get:

12345

displayed on the screen.

We can assign it to a variable and then pass it into the ReactDOM.render method as follows:

import React from "react";  
import ReactDOM from "react-dom";
const nums = [1, 2, 3, 4, 5].map(num => <span>{num}</span>);
const rootElement = document.getElementById("root");  
ReactDOM.render(nums, rootElement);

We’ll get the same items displayed.

Also, we can use the same code inside the render method:

import React from "react";  
import ReactDOM from "react-dom";

class App extends React.Component {  
  constructor(props) {  
    super(props);  
  } 

  render() {  
    const nums = [1, 2, 3, 4, 5].map(num => <span>{num}</span>);  
    return <div>{nums}</div>;  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

Keys

When we render lists, we should provide a value for key prop for each rendered element so that React can identify which items have changed, added, or removed.

It gives elements a stable identity. We should pick a key by using a string that uniquely identifies a list item among its siblings.

A key should be a string value.

For example, if we want to render a list of to-do items, we should pick the id field as the key ‘s value as follows:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map(todo => (  
          <p key={todo.id.toString()}>{todo.text}</p>  
        ))}  
      </div>  
    );  
  }  
}

In the code above, we have the key={todo.id.toString()} prop to set the key to the todo ‘s id converted to a string.

If we have no stable identity for our items, we can use the index for the entry as a last resort:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [{ text: "eat" }, { text: "drink" }, { text: "sleep" }]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map((todo, index) => (  
          <p key={index.toString()}>{todo.text}</p>  
        ))}  
      </div>  
    );  
  }  
}

index is always available and it’s unique for each array element, so it can be used as a value for the key prop.

Extracting Components with Keys

If we render components, we should put the key prop in the component rather than the element that’s being rendered.

For example, the following is incorrectly using the key prop:

function TodoItem({ todo }) {  
  return <p key={todo.id.toString()}>{todo.text}</p>;  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map(todo => (  
          <TodoItem todo={todo} />  
        ))}  
      </div>  
    );  
  }  
}

In the TodoItem component, we have:

<p key={todo.id.toString()}>{todo.text}</p>;

with the key prop. We don’t want this there because we don’t need to identify a unique li since it’s isolated from the outside already. Instead, we want to identify a unique TodoItem .

Instead, we should write:

function TodoItem({ todo }) {  
  return <p>{todo.text}</p>;  
}class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map(todo => (  
          <TodoItem todo={todo} key={todo.id.toString()} />  
        ))}  
      </div>  
    );  
  }  
}

We should add keys to items return with map ‘s callback.

Keys Only Need to Be Unique Among Siblings

Keys only need to be unique among sibling elements.

For example, we can write:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      posts: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ],  
      comments: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        <div>  
          {this.state.posts.map(post => (  
            <p key={post.id.toString()}>{post.text}</p>  
          ))}  
        </div>  
        <div>  
          {this.state.comments.map(comment => (  
            <p key={comment.id.toString()}>{comment.text}</p>  
          ))}  
        </div>  
      </div>  
    );  
  }

Since posts and comments aren’t rendered in the same div , the key values can overlap.

The key prop doesn’t get passed to components. If we need the same value in a component we have to use a different name.

Embedding map() in JSX

We can embed the expression that calls map straight between the curly braces.

For example, we can write:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
  } 

  render() {  
    return (  
      <div>  
        {[1, 2, 3, 4, 5].map(num => (  
          <span key={num.toString()}>{num}</span>  
        ))}  
      </div>  
    );  
  }  
}

useState

The useState hook lets us manage the internal state of a function component. It takes an initial value as an argument and returns an array with the current state and a function to update it.

It returns the initial state when the component is initially rendered.

We can pass in a function to update the value if the new value is computed using the previous state.

For example, we can write the following to update the value based on a previous one:

function App() {  
  const [count, setCount] = React.useState(0);  
  return (  
    <>  
      Count: {count}  
      <button onClick={() => setCount(0)}>Reset</button>  
      <button onClick={() => setCount(prevCount => prevCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(prevCount => prevCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we have:

setCount(prevCount => prevCount - 1)}

and:

setCount(prevCount => prevCount + 1)}

which decrements the count and increment it respectively by passing in functions that take the previous count as the parameter and return the new count.

Otherwise, we can just pass in the new value to the state update function as we did in:

setCount(0)

useState doesn’t automatically merge update objects. We can replicate this behavior with the spread syntax:

function App() {  
  const [nums, setNums] = React.useState({});  
  return (  
    <>  
      <p>{Object.keys(nums).join(",")}</p>  
      <button  
        onClick={() =>  
          setNums(oldNums => {  
            const randomObj = { [Math.random()]: Math.random() };  
            return { ...oldNums, ...randomObj };  
          })  
        }  
      >  
        Click Me  
      </button>  
    </>  
  );  
}

In the code above, we have:

setNums(oldNums => {  
            const randomObj = { [Math.random()]: Math.random() };  
            return { ...oldNums, ...randomObj };  
          })

to create a randomObj object with a random number as the key and value, and we merge that into another object with the old value then return it.

Then we display it with:

Object.keys(nums).join(",")

by getting the keys and joining them together.

Lazy initial state

We can pass in a function to useState if we want to delay the setting of the initial state.

It’ll be ignored after the initial render if we pass in a function.

This is useful if the initial state is computed from some expensive operation.

For example, we can write:

function App() {  
  const [count, setCount] = React.useState(() => 0);  
  return (  
    <>  
      Count: {count}  
      <button onClick={() => setCount(() => 0)}>Reset</button>  
      <button onClick={() => setCount(prevCount => prevCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(prevCount => prevCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we have:

React.useState(() => 0)

which is a function that just returns 0.

We’ll see the same results as before.

Bailing out of a state update

If we update a state hook with the same value as the current. React will skip updating the state without rendering the children or firing effects.

React uses Object.is() to compare the current and new states, which is close to the === operator except that +0 and -0 are treated to be not equal and NaN is equal to itself.

React may still render before bailing out but it won’t go deeper into the tree if it finds that the old and new values are the same.

useEffect

We can use the useEffect hook to do various operations that aren’t allowed inside the main body of the function component, which is anything outside the rendering phase.

Therefore, we can use this to do any mutations, subscriptions, setting timers, and other side effects.

It takes a callback to run the code.

We can return a function inside to run any cleanup code after each render and also when the component unmounts.

The callback passed into useEffect fires after layout and paint, during a deferred event.

This makes this suitable for running operations that shouldn’t block the browser from updating the screen.

Code that must be run synchronously can be put into the callback of the useLayoutEffect hook instead, which is the synchronous version of useEffect .

It’s guaranteed to fire before any new renders. React will always flush the previous render’s effects before starting a new update.

Conditionally firing an effect

We can pass in a second argument to useEffect with an array of values that requires an effect to be run when they change.

For example, we can use it to get data from an API on initial render as follows:

function App() {  
  const [joke, setJoke] = React.useState({});  
  useEffect(() => {  
    (async () => {  
      const response = await fetch("https://api.icndb.com/jokes/random");  
      const res = await response.json();  
      console.log(res);  
      setJoke(res);  
    })();  
  }, []);  
  return (  
    <>  
      <p>{joke.value && joke.value.joke}</p>  
    </>  
  );  
}

Passing an empty array as the second argument will stop it from loading in subsequent renders.

We can pass in a value to the array to watch the value in the array change and then run the callback function:

function App() {  
  const [joke, setJoke] = React.useState({});  
  const [id, setId] = React.useState(1);  
  useEffect(() => {  
    (async () => {  
      const response = await fetch(`https://api.icndb.com/jokes/${id}`));  
      const res = await response.json();  
      console.log(res);  
      setJoke(res);  
    })();  
  }, [id]);  
  return (  
    <>  
      <button onClick={() => setId(Math.ceil(Math.random() * 100))}>  
        Random Joke  
      </button>  
      <p>{joke.value && joke.value.joke}</p>  
    </>  
  );  
}

In the code above, when we click the Random Joke button, setId is called with a new number between 1 and 100. Then id changes, which triggers the useEffect callback to run.

Then joke is set with a new value, then the new joke is displayed on the screen.

useContext

We can use the useContext hook to read shared data shared from a React context. It accepts the context object returned from React.createContext as an argument and returns the current context value.

The current context value is determined by the value prop of the nearest context provider.

We can use it as follows:

const ColorContext = React.createContext("green");function Button() {  
  const color = React.useContext(ColorContext);  
  return <button style={{ color }}>button</button>;  
}function App() {  
  return (  
    <>  
      <ColorContext.Provider value="blue">  
        <Button />  
      </ColorContext.Provider>  
    </>  
  );  
}

In the code above, we created a new React context with:

const ColorContext = React.createContext("green");

Then in App , we wrapped out Button with the ColorContext.Provider with the value prop set to blue .

Then in Button , we have:

const color = React.useContext(ColorContext);

to get the value passed in from the ColorContext.Provider and set it to color .

Finally, we set the color style of the button with the color ‘s value.

A component calling useContext will always re-render when the context value changes. If re-rendering is expensive, then we can optimize it with memoization.

useContext is the React hooks version of Context.Consumer .

useReducer

This hook is an alternative to useState . It accepts a reducer function of type (state, action) => newState .

useReducer is preferable to useState when we have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

It also lets us optimize performance for components that trigger deep updates because we can pass dispatch down instead of callbacks.

For example, we can write:

const INCREMENT = "INCREMENT";  
const DECREMENT = "DECREMENT";function reducer(state, action) {  
  switch (action.type) {  
    case INCREMENT:  
      return { count: state.count + 1 };  
    case DECREMENT:  
      return { count: state.count - 1 };  
    default:  
      throw new Error();  
  }  
}

function App() {  
  const [state, dispatch] = React.useReducer(reducer, { count: 0 });  
  return (  
    <>  
      Count: {state.count}  
      <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>  
      <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>  
    </>  
  );  
}

In the code above, we have our reducer which returns the new state depends on the action.type ‘s value. In this case, it’s either 'INCREMENT' or 'DECREMENT' .

If it’s ‘INCREMENT’ , we return { count: state.count + 1 } .

If it’s ‘DECREMENT’ , we return { count: state.count — 1 } .

Otherwise, we throw an error.

Then in App , we call useReducer by passing in a reducer as the first argument and the initial state as the second argument.

Then we get the state object, which has the current state object and a dispatch function, which we can call with an action object, which has the type property with the value being one of ‘INCREMENT’ or ‘DECREMENT' .

We used the dispatch function in the buttons to update the state.

Finally, we display the latest state in state.count .

Lazy initialization

We can pass in a function to the 3rd argument of useReducer to initialize the state lazily.

The initial state will be set to init(initialArg) .

For instance, we can rewrite the previous example as follows:

const init = initialCount => {  
  return { count: initialCount };  
};

const INCREMENT = "INCREMENT";  
const DECREMENT = "DECREMENT";

function reducer(state, action) {  
  switch (action.type) {  
    case INCREMENT:  
      return { count: state.count + 1 };  
    case DECREMENT:  
      return { count: state.count - 1 };  
    default:  
      throw new Error();  
  }  
}
function App() {  
  const [state, dispatch] = React.useReducer(reducer, 0, init);  
  return (  
    <>  
      Count: {state.count}  
      <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>  
      <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>  
    </>  
  );  
}

First, we have:

const init = initialCount => {  
  return { count: initialCount };  
};

to return the initial state.

And instead of writing:

React.useReducer(reducer, { count: 0 });

We have:

React.useReducer(reducer, 0, init);

0 is passed in as the initialCount of init .

Then the rest of the code is the same as before.

Bailing out of a dispatch

If the same value is returned from a Reducer hook is the same as the current state, React will bail out without rendering the children or firing effects.

The comparison is done using the Object.is() algorithm.

If we’re doing expensive operations while rendering, we can optimize it with useMemo .

Code-Splitting

We need code-splitting so that product React bundles won’t be too big. As our apps get bigger, the production bundles will also get bigger and take longer to load if we don’t split them and load them only when needed.

Create React App has code-splitting support built-in. We can use the lazy and import functions from React to achieve this.

For example, we can use those functions as follows:

Foo.js:

import React from "react";export default function Foo() {  
  return <div>foo</div>;  
}

App.js:

In the code above, we have Foo.js with the Foo component.

Then, in App.js, we have the App component that has Suspense to load the fallback. UI has the Foo import loads. We need this since Foo is loaded at runtime.

React.lazy loads the Foo component at runtime instead of at build time. It also splits the code from the bundle into a separate file so that each bundled file is smaller.

We’ll get an error telling us to add a fallback UI with the Suspense component if it’s missing.

The fallback prop accepts any React element that we want to render while waiting for the component to load. We can place Suspense anywhere above the lazy component.

Wrapping multiple elements in a single Suspense element also works.


Error Boundaries

To fail gracefully when other modules fail to load, such as where network failure occurs, we can use error boundary components.

To use them, we wrap them around the Suspense component as follows:

ErrorBoundary.js:

Foo.js:

import React from "react";

export default function Foo() {  
  return <div>foo</div>;  
}

App.js:

In the code above, we added the ErrorBoundary component in ErrorBoundary.js.

Then, in App, we wrapped it around our Suspense component, which lets us catch any errors that occur when loading the Foo component.


Route-Based Code-Splitting

Many React apps are single-page apps. They’ll have routes to map URLs to components.

We can split code based on routes. For example, we can incorporate React Router routes into our app and split code based on routes as follows:

Foo.js:

import React from "react";

export default function Foo() {  
  return <div>foo</div>;  
}

Bar.js:

import React from "react";

export default function Bar() {  
  return <div>bar</div>;  
}

App.js:

In the example above, we add the Router component around all our components.

We put the Routes inside the Suspense component so that they can be lazy-loaded. That is, they load only when we load the route in our browser.

This is also why we need the Suspense component around the routes. We need the fallback UI so that it’ll be shown when the routes are loading.


Named Exports

React.lazy only supports default exports. If our module uses named exports, then we have to create an intermediate module that re-exports it as default.

For example, we can arrange our code as follows:

Foo.js:

import React from "react";

export const Foo = function() {  
  return <div>foo</div>;  
};

FooDefault.js:

import { Foo } from "./Foo";

export default Foo;

App.js:

import React, { Suspense } from "react";
const FooComponent = React.lazy(() => import("./Foo"));
export default function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <FooComponent />
    </Suspense>
  );
}

In the code above, we have FooDefault.js that exports Foo as a default export.

Then, we imported it in App.js with React.lazy.


Error Boundaries

JavaScript inside components used to corrupt React’s internal state and cause cryptic errors to be emitted on the next renders.

These errors are always caused by earlier errors in the application code. React didn’t provide a way to handle them gracefully in components and recover from them.

We can use error boundaries to catch JavaScript errors anywhere in the child component tree, log those errors and display a fallback UI instead of crashing the app.

However, error boundaries don’t catch errors for event handlers, asynchronous code, server-side rendering, or errors thrown in the error boundary itself.

We can create an error boundary component by adding the static getDerivedStateFromError() or componentDidCatch() methods inside a class-based component.

For example, we can use it as follows:

class ErrorBoundary extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { hasError: false };  
  } 

  static getDerivedStateFromError(error) {  
    return { hasError: true };  
  } 

  componentDidCatch(error, errorInfo) {  
    console.log(error, errorInfo);  
  } 

  render() {  
    if (this.state.hasError) {  
      return <h1>Error occured.</h1>;  
    } return this.props.children;  
  }  
}

class Button extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { count: 0 };  
  }  
  increment() {  
    this.setState({ count: this.state.count + 1 });  
  }  
  render() {  
    if (this.state.count === 5) {  
      throw new Error("error");  
    } 

    return (  
      <>  
        <button onClick={this.increment.bind(this)}>Click Me</button>  
        <br />  
        <p>{this.state.count}</p>  
      </>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ErrorBoundary>  
          <Button />  
        </ErrorBoundary>  
      </div>  
    );  
  }  
}

In the code above, we have the ErrorBoundary component, which has the componentDidCatch method to log the errors given by the parameters.

Then we added the static getDerivedStateFromError to set the hasError state to true if an error occurred.

In the render method, we render the error message if hasError state is true . Otherwise, we display the child components as usual.

In the Button component, we have a button element that increases the count state it’s clicked. We have an if statement for this.state.count which throws an error if it reaches 5.

Once it reaches 5, the ‘Error occurred’ message from the ErrorBoundary component will be shown since the error is thrown in the render method, which isn’t an event handler, async code, and other places error boundaries can’t catch.

As we can see, we placed the ErrorBoundary outside any code that catches errors. This way, ErrorBoundary can actually catch them.

During development, the whole stack trace will be displayed, we have to disable that in production. This will be done automatically when we make a production build with Create React App.

Catching Errors Inside Event Handlers

We have to use try/catch block to catch errors in event handlers.

For example, we can write the following code to catch those:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { error: null, count: 0 };  
    this.handleClick = this.handleClick.bind(this);  
  } 

  handleClick() {  
    try {  
      if (this.state.count === 5) {  
        throw new Error("error");  
      }  
      this.setState({ count: this.state.count + 1 });  
    } catch (error) {  
      this.setState({ error });  
    }  
  } 
  
  render() {  
    if (this.state.error) {  
      return <h1>Error occurred.</h1>;  
    }  
    return (  
      <button onClick={this.handleClick}>Count: {this.state.count}</button>  
    );  
  }  
}

In the handleClick method, we have an if statement that throws an error if this.state.count is 5.

If an error is thrown, the catch block will set the error state to the error object.

This will then render the ‘Error occurred’ message instead of the button to increase the count state.

React Router

Installation

To use React Router, we have to install it. We can do that by running:

npm i react-router-dom

Basic Usage

React Router has a few basic components.

BrowserRouter

First, there’s the BrowserRouter to do the routing with the HTML5 history API.

It takes a few props:

  • basename — a string prop for the base URL for all locations.
  • getUserConfirmation — a function prop we use to confirm navigation. Defaults to using window.confirm .
  • forceRefresh — a boolean prop that’s true if we want a full refresh on page navigation. It’s useful for imitating traditional server-render apps.
  • keyLength — a number prop that sets the length of location.key . Defaults to 6.
  • children — a React component to render. Before React 16, we can only use a single child element. If we want to render more than one element, we can wrap it in a div.

Switch

Next, there’s the Switch component. It renders the first child Route or Redirect that matches the location.

Switch renders a route exclusively. Route that matches the location renders inclusively.

Switch will only pick one Route to render.

Route

The Route component is the most important component in React Router.

It’s used to map URLs to components and takes the following props:

  • component — we pass in the component to map the URL to by passing in a component to it.
  • render — this prop takes a function that returns something that we want to render.
  • children — a function prop that lets us render something when the path matches Route ‘s path and render something else otherwise.
  • path — a string or array of strings that are paths to match.
  • exact — a boolean prop to that’s true if we want to render only if a path matches exactly.
  • strict — boolean prop that’s true is we only want to match a path with a trailing slash only when the URL entered has a trailing slash. It has no effect when there’re additional URL segments in location.pathname
  • location — an object prop that tries to match its path in the current history location.
  • sensitive — a boolean prop that true if the path is case-sensitive.

Link

The Link component provides accessible navigation around our app.

It takes the following props:

  • to — a string prop with the path that we want to go to. It’s created by concatenating location’s pathname, search and hash properties.
  • to can also be an object that has the pathname, search, state and hash properties. The pathname is a string that has the path to link to. search is a string of the query parameters, hash is a hash top put in the URL, state is the state to persist to the location.
  • to can also be a function which takes the currentlocation as the argument and returns the location representation as a string as an object.
  • replace — a boolean prop that replaces the current entry in the history stack instead of adding one if true
  • innerRef — a function or ref object that we shouldn’t need if we’re using React Router 5.1 or later with React 16. It lets us access the ref of the component.
  • We can any other attribute to the a tag like title , id , className , etc.

Example

We can use the components above together as follows:

import React from "react";  
import ReactDOM from "react-dom";  
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";  
function Home() {  
  return <h2>Home</h2>;  
}

function Foo() {  
  return <h2>Foo</h2>;  
}

function Bar() {  
  return <h2>Bar</h2>;  
}

function App() {  
  return (  
    <Router>  
      <div>  
        <ul>  
          <li>  
            <Link to="/">Home</Link>  
          </li>  
          <li>  
            <Link to="/foo">Foo</Link>  
          </li>  
          <li>  
            <Link to="/bar">Bar</Link>  
          </li>  
        </ul>  
        <Switch>  
          <Route exact path="/">  
            <Home />  
          </Route>  
          <Route path="/foo">  
            <Foo />  
          </Route>  
          <Route path="/bar">  
            <Bar />  
          </Route>  
        </Switch>  
      </div>  
    </Router>  
  );  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the Home , Foo , and Bar components which display some text.

Then we add Router in App and put all our Route s inside so when we click on the Link s, they’ll display the component we specified.

We have:

<Switch>  
    <Route exact path="/">  
        <Home />  
    </Route>  
    <Route path="/foo">  
        <Foo />  
    </Route>  
    <Route path="/bar">  
        <Bar />  
    </Route>  
</Switch>

In the Switch , we have exact in the first Route to only show Home when we go to / .

Then we define 2 more Route s to show Foo when we go to /foo and Bar when we go to /bar .

Creating Sidebar

We can easily create a fixed sidebar on one side of the screen while displaying route content on the other.

To do this, we just need to add our sidebar on one side with links and the Switch and Route components on the other.

For example, we can write the following code to achieve this effect:

import React from "react";  
import ReactDOM from "react-dom";  
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";

const routes = [  
  {  
    path: "/",  
    exact: true,  
    sidebar: () => <div>home</div>,  
    main: () => <h2>Home</h2>  
  },  
  {  
    path: "/foo",  
    sidebar: () => <div>foo</div>,  
    main: () => <h2>Bubblegum</h2>  
  },  
  {  
    path: "/shoelbaraces",  
    sidebar: () => <div>bar</div>,  
    main: () => <h2>Shoelaces</h2>  
  }  
];

function App() {  
  return (  
    <Router>  
      <div style={{ display: "flex" }}>  
        <div  
          style={{  
            padding: "10px",  
            width: "40%",  
            background: "pink"  
          }}  
        >  
          <ul style={{ listStyleType: "none", padding: 0 }}>  
            <li>  
              <Link to="/">Home</Link>  
            </li>  
            <li>  
              <Link to="/foo">Foo</Link>  
            </li>  
            <li>  
              <Link to="/bar">Bar</Link>  
            </li>  
          </ul> <Switch>  
            {routes.map((route, index) => (  
              <Route  
                key={index}  
                path={route.path}  
                exact={route.exact}  
                children={<route.sidebar />}  
              />  
            ))}  
          </Switch>  
        </div> <div style={{ flex: 1, padding: "10px" }}>  
          <Switch>  
            {routes.map((route, index) => (  
              <Route  
                key={index}  
                path={route.path}  
                exact={route.exact}  
                children={<route.main />}  
              />  
            ))}  
          </Switch>  
        </div>  
      </div>  
    </Router>  
  );  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the sidebar in the top of App . And we have the routes array as follows:

const routes = [  
  {  
    path: "/",  
    exact: true,  
    sidebar: () => <div>home</div>,  
    main: () => <h2>Home</h2>  
  },  
  {  
    path: "/foo",  
    sidebar: () => <div>foo</div>,  
    main: () => <h2>Bubblegum</h2>  
  },  
  {  
    path: "/shoelbaraces",  
    sidebar: () => <div>bar</div>,  
    main: () => <h2>Shoelaces</h2>  
  }  
];

In the outermost div of App , we have display: 'flex' style to display the sidebar and the route content side by side, with the sidebar of the left and the route content on the right.

The sidebar is composed of the following code in App :

<div style={{ padding: "10px", width: "40%", background: "#f0f0f0" }}>  
    <ul style={{ listStyleType: "none", padding: 0 }}>  
        <li>  
            <Link to="/">Home</Link>  
        </li>  
        <li>  
            <Link to="/foo">Foo</Link>  
        </li>  
        <li>  
            <Link to="/bar">Bar</Link>  
        </li>  
    </ul>  
    <Switch>  
        {routes.map((route, index) => (  
        <Route key={index} path={route.path} exact={route.exact} children={<route.sidebar />} /> ))}  
    </Switch>  
</div>

In the code above, we have the ul element to display the Link s, which we can click on the show the route that we want.

In the Switch component, we have the routes array mapped to Route s that we want to display in the sidebar.

We want to display the component we set as the value of the sidebar in each entry of routes . Therefore, we set children to that.

In the routes array, we have the route with path set to '/' has the exact property set to true .

We have to do this so that React Router won’t direct all routes that start with a / to the ‘home’ route. This is because React Router doesn’t look at the other routes once it found a match.

If the match isn’t exact, then it’ll take anything that starts with a / as the correct match. In this case, it’ll be the first route.

The right side is composed of the following code:

<div style={{ flex: 1, padding: "10px" }}>  
    <Switch>  
        {routes.map((route, index) => (  
        <Route key={index} path={route.path} exact={route.exact} children={<route.main />} /> ))}  
    </Switch>  
</div>

The code above maps the routes array again to Route s, but we set the children prop to the components set to the main property instead of sidebar .

404 Routes

We can create 404 routes to show something when none of the routes we added are matched.

To do this, we can use the asterisk as a wildcard character.

For example, we can write the following code;

import React from "react";  
import ReactDOM from "react-dom";  
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";

function Foo() {  
  return <h3>Foo</h3>;  
}

function Bar() {  
  return <h3>Bar</h3>;  
}

function NotFound() {  
  return <h3>Not Found</h3>;  
}

function App() {  
  return (  
    <Router>  
      <div>  
        <ul>  
          <li>  
            <Link to="/">Foo</Link>  
          </li>  
          <li>  
            <Link to="/bar">Bar</Link>  
          </li>  
          <li>  
            <Link to="/doesnt-exist">Doesn't Exist</Link>  
          </li>  
        </ul> <Switch>  
          <Route exact path="/">  
            <Foo />  
          </Route>  
          <Route path="/bar">  
            <Bar />  
          </Route>  
          <Route path="*">  
            <NotFound />  
          </Route>  
        </Switch>  
      </div>  
    </Router>  
  );  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the Switch component that has the following Route:

<Route path="\*">  
    <NotFound />  
</Route>

It’ll match any URL that doesn’t match the other routes.

Therefore, when we click the Doesn’t Exist link, we’ll see Not Found.

This also applies when we type in anything other / or /bar as the relative path.

The * is the wildcard character for matching anything.

Recursive Routes

Just like any other routes, we can define routes recursively. The only difference is that we reference the component within the Route with itself.

For example, if we have an array of NEIGHBORS as follows:

const NEIGHBORS = [  
  { id: 0, name: "Jane", neighbors: [1, 2, 3] },  
  { id: 1, name: "Joe", neighbors: [0, 3] },  
  { id: 2, name: "Mary", neighbors: [0, 1, 3] },  
  { id: 3, name: "David", neighbors: [1, 2] }  
];

where the neighbors array references the id of the other entries.

Then we can define a Neighbor component and use it as follows:

import React from "react";  
import ReactDOM from "react-dom";  
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";  
import { useParams, useRouteMatch, Redirect } from "react-router";

const NEIGHBORS = [  
  { id: 0, name: "Jane", neighbors: [1, 2, 3] },  
  { id: 1, name: "Joe", neighbors: [0, 3] },  
  { id: 2, name: "Mary", neighbors: [0, 1, 3] },  
  { id: 3, name: "David", neighbors: [1, 2] }  
];

const find = id => {  
  return NEIGHBORS.find(p => p.id === id);  
};

function Neighbor() {  
  const { url } = useRouteMatch();  
  const { id } = useParams();  
  const neighbor = find(+id); return (  
    <div>  
      <h3>{neighbor.name}’s neighbors</h3> <ul>  
        {neighbor.neighbors.map(id => (  
          <li key={id}>  
            <Link to={`${url}/${id}`}>{find(id).name}</Link>  
          </li>  
        ))}  
      </ul> 
      <Switch>  
        <Route path={`${url}/:id`}>  
          <Neighbor />  
        </Route>  
      </Switch>  
    </div>  
  );  
}

function App() {  
  return (  
    <Router>  
      <Switch>  
        <Route path="/:id">  
          <Neighbor />  
        </Route>  
        <Route path="/">  
          <Redirect to="/0" />  
        </Route>  
      </Switch>  
    </Router>  
  );  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the find function to make find neighbors easy.

Then in the Neighbor component, we have:

const { id } = useParams();  
const neighbor = find(+id);

to get the id from the URL, and then find the neighbor with it using the find function.

Then we display the neighbors of the neighbor by writing:

<ul>  
    {neighbor.neighbors.map(id => (  
    <li key={id}>  
        <Link to={`${url}/${id}`}>{find(id).name}</Link>  
    </li>  
    ))}  
</ul>

Then we have a Switch component that has Neighbor again in the Route :

<Switch>  
    <Route path={`${url}/:id`}>  
        <Neighbor />  
    </Route>  
</Switch>

This is the part that’s recursive, since Neighbor references itself here. We set the path to `${url}/:id` so that when the Link s above is clicked, we’ll see the new neighbors.

Then in App , we just have the usual routes. Once again, we have path=”/:id” to find a neighbor by id .

Context API

We can use Context to share data between React components. However, it should be used sparingly since it creates tight coupling between components.

To use it within a simple app, we can write the following:

const ColorContext = React.createContext("green");

class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {value => (  
            <button style={{ color: value }}>{this.props.children}</button>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Provider value="blue">  
          <Button>Click Me</Button>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}

In the code above, we created a Context to share data by writing:

const ColorContext = React.createContext("green");

createContext takes a default value as an argument, where we passed in 'green' .

Then in the App component, we have the ColorContext.Provider component with the value prop set to the value that we want to share.

In the example above, it’ll be 'blue' . We wrapped it around the components that we want to share the data with so that we can access the value from that component.

In this case, we created a new Button component, which has the ColorContext.Consumer component. Inside it, we can get the value shared from the context provider from the value parameter in the function we inserted inside the ColorContext.Consumer component.

value should be set to 'blue' since that’s what we set as the value of the value prop.

Inside the function we passed in the consumer, we returned a buttom element with the style prop and we set the color style to value , which is 'blue' .

Alternatives to Context

If we want to pass data into a deeply nested component, we can instead pass in the whole component down to where we want it. This way, we don’t have to worry about passing props to multiple levels to pass something that’s only needed by deeply nested components.

For example, if we want to pass the color prop to Button components, which is contained in a ButtonBar . We can do that as follows:

class Button extends React.Component {  
  render() {  
    return (  
      <button style={{ color: this.props.color }}>{this.props.children}</button>  
    );  
  }  
}

class ButtonBar extends React.Component {  
  render() {  
    return this.props.buttons;  
  }  
}

class App extends React.Component {  
  render() {  
    const buttons = [  
      <Button color="blue">Click Me</Button>,  
      <Button color="green">Click Me 2</Button>  
    ];  
    return <ButtonBar buttons={buttons} />;  
  }  
}

In the App component, we have the Button components in the buttons array. Then we passed the whole array straight down to the ButtonBar component.

Then ButtonBar just returns what we passed in, which is this.props.buttons .

This also means more complexity in the higher-order components, so it may not be suitable in all cases.

Updating Context from a Nested Component

We can pass in functions to the object that we pass into createContext so that we can call them inside the component that has the context consumer component.

For example, we can write the following:

const colorObj = {  
  color: "green",  
  toggleColor: () => {}  
};

const ColorContext = React.createContext(colorObj);  
class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {({ color, toggleColor }) => (  
            <button onClick={toggleColor} style={{ color }}>  
              {this.props.children}  
            </button>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      color: "blue",  
      toggleColor: () => {  
        this.setState(state => ({  
          color: state.color === "green" ? "blue" : "green"  
        }));  
      }  
    };  
  } 

  render() {  
    return (  
      <div>  
        <ColorContext.Provider value={this.state}>  
          <Button>Click Me</Button>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}

The code above starts with defining the colorObj object, which is passed into createContext as the default value of ColorContext .

Then in the App component, we initialize this.state by setting it to an object with the toggleColor function, and the color property set to 'blue' .

We pass this.state as the value of the value prop of ColorContext.Provider .

Then we access the whole object inside the ColorContext.Consumer component in the Button component.

Inside there, we get the color and toggleColor property from the this.state which we passed in from the ColorContext.Provider . Then we pass toggleColor into the onClick prop, and color into the object that we passed into the style prop.

Then when we click the Click Me button, the text color will toggle between blue and green.

Consuming Multiple Contexts

We can consume multiple contexts by nesting them. For example, we can do that as follows:

const ColorContext = React.createContext("green");  
const BorderContext = React.createContext("");
class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {color => (  
            <BorderContext.Consumer>  
              {border => (  
                <button style={{ color, border }}>{this.props.children}</button>  
              )}  
            </BorderContext.Consumer>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Provider value="blue">  
          <BorderContext.Provider value="3px solid green">  
            <Button>Click Me</Button>  
          </BorderContext.Provider>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}

In the code above, we create 2 Contexts, ColorContext and BorderContext and passed in values to the value prop to both. We nested the providers in the App component, which means that both contexts can be consumed by the Button component inside.

Then in the Button component, we have consumers for both contexts nested in each other. And then we can get both values that were passed in from the providers.

We then use both values to set the styles of the button .

In the end, we a button with blue text and a thick green border.

Conclusion

We can create a React app with the Create React App Node package.

Then we can add components as a function, class, or with React.createElement .

The first 2 ways are used most often since they return JSX in the function or the render method of the component class respectively.

JSX is much more convenient than createElement for writing JavaScript code with React, especially when our app gets complex.

We can embed JavaScript expressions in between curly braces.

Hooks are used to change internal state, commit side effects, or hold any other logic.

React Router lets us navigate between different pages,

The Context API lets us share data between any components.

Categories
JavaScript Vue

Using a For Loop with Vuejs

We can render lists by using a for loop with Vuejs.

The for loop equivalent in Vuejs is the v-for directive.

To use it, we can write;

<ul id="example">
  <li v-for="item in items" :key="item.name ">
    {{ item.name }}
  </li>
</ul>

and:

const example = new Vue({
  el: '#example',
  data: {
    items: [
      { name: 'Foo' },
      { name: 'Bar' }
    ]
  }
})

We have an items array with the list items to render

Then we use v-for with the items array to render them items.

We need the key prop with a unique value for each entry for Vuejs to identify the entries properly.

We can also add the index by changing our v-for for loop.

For instance, we can write:

<ul id="example">
  <li v-for="(item, index) in items">
    {{ index }} - {{ item.name }}
  </li>
</ul>

and:

const example = new Vue({
  el: '#example',
  data: {
    parentMessage: 'Parent',
    items: [
      { name: 'Foo' },
      { name: 'Bar' }
    ]
  }
})

index has the index of the array.

We can also loop through objects.

For instance, we can write:

<ul id="v-for-object">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>

and

new Vue({
  el: '#v-for-object',
  data: {
    object: {
      name: 'james'.
      age: 20,
      gender: 'male'
    }
  }
})

We loop through the keys of the object in our Vuejs app with the same for loop.

value has the property value.

We can also add the property name and index to the loop.

For instance, we can write:

<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>

value is the property value and name is the property name.

We can also add the index to our Vuejs for loop by writing:

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

We can create a Vuejs for loop that display numbers by writing a number after the in instead of an array or object.

For instance, we can write:

<div>
  <span v-for="n in 100">{{ n }} </span>
</div>

We can v-for with a `template component to render multiple items.

For instance, we can write:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li><hr /></li>
  </template>
</ul>

to render multiple items in each iteration of the v-for Vuejs for loop.

The Vuejs equivalent of a for loop is the v-for directive.

We can use it to render objects and array entries on the screen.

Also, we can use it to loop through a range of numbers and display them.