Using GraphQLSchema to construct the schema programmatically.

For most of the cases defining a fixed schema when the application starts, by adding Query and Mutation types solely using schema language is good enough. But sometimes we might need to define a dynamic schema and we can achieve this by creating a new JS objects.

Construct dynamic schema for ‘User Greeting’ example.

Let’s get again to the ‘Greetings user’ example, because of it’s simplicity and define a Query with field named greetingUser which will accept userName and bornMonth parameters, first of type string and the second of type int and return userType.

And the userType will return greetingOne  which will simply say “Hello [userName]” and greetingTwo  that will let the user know how many months left till their next birthday. Both of type string.

./src/server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the User type
var userType = new graphql.GraphQLObjectType({
  name: 'UserType',
  fields: {
    greetingOne: { type: graphql.GraphQLString },
    greetingTwo: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    greetingUser: {
      type: userType,
      args: {
        userName: { type: graphql.GraphQLString },
        bornMonth: { type: graphql.GraphQLInt }
      },
      resolve: function (_, {userName, bornMonth}) {
        var date = new Date();
        var daysLeft = bornMonth - (date.getMonth() + 1);
        daysLeft = daysLeft < 0 ? daysLeft + 12 : daysLeft;
        return {
          greetingOne: `Hello ${userName}`,
          greetingTwo: `Your birthday is comming in ${daysLeft} month(s)`
        };
      }
    }
  }
});

var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

what we just did:
– we defined the user type which is pretty self explanatory (Lines 8-14)
– then we created the query type, that has these parameters:
type which is the return type, in this case userType
args is the input parameter types.
resolve is the resolver function.

Transform Dogs catalog to use dynamic schema.

Creating the dog type

var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

Creating the query type

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    }
  }
});

Creating a mutation type

...
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    } 
...

Adding the query schema

...
var schema = new graphql.GraphQLSchema({query: queryType});

...
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
...

Putting it all together

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the dogs type
var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    },
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    }    
  }
});


var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Adding Middleware to log requested IPs

Middleware is a good way to pre-process the request before handing it out to GraphQL.
We could add authentication that could check the auth token in the header or middleware that will log the request IPs. Let’s do the second one.

Adding middleware could be done the same way as we are doing it in Express app. Add the highlighted lines, and let’s see what they do.

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
  type Mutation {
    setNewDog(id: String, breed: String, displayImage: String): String
  }  
  type dog {
    id: String,
    breed: String,
    displayImage: String
  }
  type dogsCatalog {
    getDogByBreed(breed: String): dog
  }
  type Query {
    queryDogs: dogsCatalog
  }  
`);
class dogsCatalog {
  
  getDogByBreed({breed}) {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    return result;
  }
}
// The root provides a resolver function for each API endpoint
var root = {
  queryDogs: () => {
    return new dogsCatalog();
  },
  setNewDog: ({id, breed, displayImage}) => {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    if(result != null) {
      return 'dog already exists!';
    }
    dogs.push({
      "breed": breed,
      "displayImage": displayImage,
      "id": id      
    });
    return 'dog added successfully !';
  }
};

// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4001);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

 what we just did:
– we created the middleware object to log every request ip (lines 52-56)
– in the middleware we added the next() function to proceed to the next middleware none we are done with the request processing.
– we told Express to use our new middleware app.use(logger);

And as simple as that we created our own logging class.

Mutations and input types

 

Let’s scrap our user greeting and make really useful schema, that will show up a dog breed catalog. We are going to use it in another tutorial to create a React component to display the dog catalog.

Set up mock data.

Create a file under ./src/modules/mock_data that will serve as our data provider. Let’s add a couple of dog breeds there.

./src/modules/mock_data/dogs.js

module.exports = [
  {
    "breed": "affenpinscher",
    "displayImage": "https://images.dog.ceo/breeds/affenpinscher/n02110627_10225.jpg",
    "id": "Z1fdFgU"
  },
  {
    "breed": "african",
    "displayImage": "https://images.dog.ceo/breeds/african/n02116738_2515.jpg",
    "id": "Z1gPiBt"
  },
  {
    "breed": "airedale",
    "displayImage": "https://images.dog.ceo/breeds/airedale/n02096051_6335.jpg",
    "id": "ZNDtCU"
  },
  {
    "breed": "akita",
    "displayImage": "https://images.dog.ceo/breeds/akita/Japaneseakita.jpg",
    "id": "6HalQ"
  },
  {
    "breed": "appenzeller",
    "displayImage": "https://images.dog.ceo/breeds/appenzeller/n02107908_3311.jpg",
    "id": "Z1tlucN"
  },
  {
    "breed": "basenji",
    "displayImage": "https://images.dog.ceo/breeds/basenji/n02110806_3415.jpg",
    "id": "Zo1k5y"
  }  
];

Writing a query to fetch the mock data.

Let’s first write a query to fetch dog’s details based on the breed.

Based on the JSON data above, dog’s schema would look like this:

...
  type dog {
    id: String,
    breed: String,
    displayImage: String
  }
  type dogsCatalog {
    getDogByBreed(breed: String): dog
  }
  type Query {
    queryDogs: dogsCatalog
  } 
...

We created a dogCatalog query (line 7) that accepts the dog breed, and returns a dog type declared in the beginning of the sniped above.

Then the resolver would look like this:

...
class dogsCatalog {
  
  getDogByBreed({breed}) {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    return result;
  }
}
...

 

Write the mutation.

The mutations are used to modify data. In GraphQL they are similar as queries and could be written in a similar way.

The basic idea.

...
  type Mutation {
    setNewDog(id: String, breed: String, displayImage: String): String
  }  
...

here, we defined mutation named setNewDog which simply takes a few parameters (id, breed and displayImage) and returns the status message.

The resolver will look like this:

...
  setNewDog: ({id, breed, displayImage}) => {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    if(result != null) {
      return 'dog allready exists!';
    }

    dogs.push({
      "breed": breed,
      "displayImage": displayImage,
      "id": id      
    });
    return 'dog added successfuly !';
  }
...

Let’s put them all together:

./src/server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');

// Construct a schema, using GraphQL schema language

var schema = buildSchema(`


  type Mutation {
    setNewDog(id: String, breed: String, displayImage: String): String
  }  

  type dog {
    id: String,
    breed: String,
    displayImage: String
  }

  type dogsCatalog {
    getDogByBreed(breed: String): dog
  }

  type Query {
    queryDogs: dogsCatalog
  }  
`);

class dogsCatalog {
  
  getDogByBreed({breed}) {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    return result;
  }
}

// The root provides a resolver function for each API endpoint
var root = {
  queryDogs: () => {
    return new dogsCatalog();
  },
  setNewDog: ({id, breed, displayImage}) => {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    if(result != null) {
      return 'dog already exists!';
    }

    dogs.push({
      "breed": breed,
      "displayImage": displayImage,
      "id": id      
    });
    return 'dog added successfully !';
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Test Using GraphQL UI

Writing a query to fetch existing data.

Now navigate to GraphQL UI http://localhost:4001/graphql and add this in the query window (up left)

query queryLabel($breed: String!) {
  queryDogs {
    getDogByBreed(breed: $breed) {
      id
      displayImage,
      breed
    }
  }
}

and this in the variables window (down left)

{
  "breed": "african"
}

hit play and the result should be this:

{
  "data": {
    "queryDogs": {
      "getDogByBreed": {
        "id": "Z1gPiBt",
        "displayImage": "https://images.dog.ceo/breeds/african/n02116738_2515.jpg",
        "breed": "african"
      }
    }
  }
}

Now change the breed in the variables window (down left) to ‘labrador’ and hit play again, and the result is null since there is no record for this breed.

Write a mutation to add a new record to data structure.

Open a new QraphQL UI tab and add the following mutation in the query window:

mutation setNewDogLabel($id: String, $breed: String, $displayImage: String) {
  setNewDog(id: $id, breed: $breed, displayImage: $displayImage)
}

and this in the variables window:

{
  "id": "Z2iEkxg",
  "breed": "labrador",
  "displayImage": "https://images.dog.ceo/breeds/labrador/n02099712_2120.jpg"
}

Hit play and if everything went well you will see the success message.

{
  "data": {
    "setNewDog": "dog added successfuly !"
  }
}

(Out of curiosity you might want to hit play one more time to see what message you will get)

Now, navigate back to the previous GraphQL UI tab and hit play again and you will see newly created record.

 

 

 

 

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');

// Construct a schema, using GraphQL schema language

var schema = buildSchema(`

  input DogInput {
    id: String,
    breed: String,
    displayImage: String
  }

  type Mutation {
    setNewDog(input: DogInput): String
  }  

  type dog {
    id: String,
    breed: String,
    displayImage: String
  }

  type dogsCatalog {
    getDogByBreed(breed: String): dog
  }

  type Query {
    queryDogs: dogsCatalog
  }  
`);

class dogsCatalog {
  
  getDogByBreed({breed}) {
    var result = dogs.find(function(dog){
      return breed == dog.breed ? dog : null;
    });
    return result;
  }
}

// The root provides a resolver function for each API endpoint
var root = {
  queryDogs: () => {
    return new dogsCatalog();
  },
  setNewDog: ({input}) => {
    var result = dogs.find(function(dog){
      return input.breed == dog.breed ? dog : null;
    });
    if(result != null) {
      return 'dog allready exists!';
    }

    dogs.push({
      "breed": input.breed,
      "displayImage": input.displayImage,
      "id": input.id      
    });
    return 'dog added successfuly !';
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Object Types

In many cases returning a scalar values like Int or String might not be enough and we might want to return more complex data, like Objects. Let’s modify our previous example to do this.

Adding type object into GraphQL’s schema.

Objects could be defined in the schema the same way as we defined the Query (lines 4-7) and then we could modify greetingUser Query to return our new object: greeting instead of String 

...
var schema = buildSchema(`

  type greeting {
    userName: String
    userRollDice(userName: String): String
  }

  type Query {
    greetingUser(userName:String!): greeting
  }
`);
...

Once we have the new greeting type into the schema we could write the resolver, which could be a plain Java Script object object (in our case a function that returns the exposed methods):

...
var greeting = (userName) => {

  function rollDice() {
    return Math.floor(Math.random() * 6) + 1;
  }  
    
  function userRollDice(args) {
    return `User ${args.userName} rolled: ${rollDice()}`;
  }
  return  {
    userName: userName,
    userRollDice: userRollDice
  }
}
...

what we just did:
– we created a new method called greeting which exposes one string property named userName, that we are passing and one method called userRollDice which will return String which will describe (in plain text) what dice # the used rolled.
– we also added a helper method called rollDice which will return a random number.

The whole code should look like this:

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language

var schema = buildSchema(`

  type greeting {
    userName: String
    userRollDice(userName: String): String
  }

  type Query {
    greetingUser(userName:String!): greeting
  }
`);

var greeting = (userName) => {
  
  function rollDice() {
    return Math.floor(Math.random() * 6) + 1;
  }  
    
  function userRollDice(args) {
    return `User ${args.userName} rolled: ${rollDice()}`;
  }
  return  {
    userName: userName,
    userRollDice: userRollDice
  }
}


// The root provides a resolver function for each API endpoint
var root = {
  greetingUser: (args) => {
    return greeting(args.userName);
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4001);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Let’s give it a try. Using the Document explorer click on Query and you will see that there is a query named greetingUser, that expects userName string, and returns the new method greeting.

greetingUser(userNameString!): greeting

Explore the greeting and you will see that it could be queried for userName and userRollDice which also expects one string parameter.

userRollDice(userNameString): String
Having this information we are ready to construct our new query. Add the code below in GraphQL UI’s query box (up left)
query queryLabel($userName: String!) {
  greetingUser(userName: $userName){
    userName
    userRollDice(userName: $userName)
  }
}

and leave the parameter box (down left) the same for now.

{
  "userName": "Sam Smith"
}

Hitting play will produce a nice response like this:

{
  "data": {
    "greetingUser": {
      "userName": "Sam Smith",
      "userRollDice": "User Sam Smith rolled: 4"
    }
  }
}

GraphQL just returned us the greeting object that we requested.

Using ES6 features.

Let’s modify the resolver to use ES6 class syntax and also let’s add a destructors to clean up the code a bit.

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language

var schema = buildSchema(`

  type greeting {
    userName: String
    userRollDice(userName: String): String
  }

  type Query {
    greetingUser(userName:String!): greeting
  }
`);

class greeting {
  
  constructor(userName) {
    this.userName = userName;
  }
  
  rollDice() {
    return Math.floor(Math.random() * 6) + 1;
  }  
    
  userRollDice({userName}) {
    return `User ${userName} rolled: ${this.rollDice()}`;
  }
}


// The root provides a resolver function for each API endpoint
var root = {
  greetingUser: ({userName}) => {
    return new greeting(userName);
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4001);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 what we just did:
– we replaced the JavaScript function resolver with class having the same methods.
– in userRollDice method we replaced args.userName with the destructor {userName}
– in the root resolver we call greetingUser, passing the userName and returning new instance of greeting

GraphQL schemas, queries and resolvers.

In the previous example we learned how to run GraphQL with Express http server and we created a simple query example with returns hardcoded data. Let’s make this useful and create real world example.

Adding Nodemon for rapid development.

Before we continue, let’s make development process easier and run the project using Nodemon instead of Node. Nodemon is the same like Node but it also monitors the project folder for changes, and automatically restarts the server for us.

yarn add nodemon --dev

Creating schema and resolvers.

Technically speaking we already created our schema and resolver in the previous example. Let’s quickly go through what we did:

The most basic way to create a response in GraphQL is like it was described in the previous tutorial: creating schema describing the field name and type i.e.

var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

and then creating a resolver i.e.

var root = {
  hello: () => {
    return 'Hello world!';
  },
};

which simply returns whatever is in the return statement: in this case “hello world!” for any hello field request.

Let’s make the example more useful by

Passing parameters.

The basic types that we could use out of the box are StringIntFloatBoolean, and ID

Let’s change the hello query to accept userName parameter of type String and prints ‘hello [userName]’. Also we will add another field called rollDice and will return a random number between 0 and 10.
Passing parameters requires two steps:
– define the parameter in the schema (line 8) hello(userName:String!): String
– read the parameter in the resolver (line 15) hello: (args) => { ...

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
  type Query {
    hello(userName:String!): String,
    rollDice: Int
  }
`);
// The root provides a resolver function for each API endpoint

var root = {
  hello: (args) => {
    return `Hello ${args.userName}`;
  },
  rollDice: () => {
    return Math.floor(Math.random() * 6) + 1;
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 what we just did:
– we added userName parameter in the schema and defined it as s String type (line 8)
– in the resolver we are getting the userName parameter from the args parameter, and returning the greeting line 16.
– in addition we also added another field call rollDice which returns a random number between 0 and 10.

Making queries with parameters.

Save the file and navigate the browser to http://localhost:4000/graphql

GraphQL UI

 

Explore the schema using documentation explorer.

Look at the top right side in the browser (picture above) and you will find the Documentation explorer (If it is collapsed click on ‘Docs’ in the upper right section). Let’s pretend that we didn’t build the schema but we still need to know what queries we could fire. That’s where Documentation explorer could help.
Click on Query and you will see this:

hello(userNameString!): String
This describes the ‘hello’ and the ‘rollDice’ queries, the input parameters and the output type.

Making the query.

Since now we know the query name and the parameters and their types we are ready to write our first query with parameters.

Add the query in the up left text box like it’s shown on the picture above,

query queryLabel($userName: String!) {
  hello(userName: $userName)
}

and then add the parameter in the left, down box named query variables. When we write query with parameters it’s always better instead to directly pass the parameter value, to use the $ syntax to define a variable in the query.

{
  "userName": "Sam Smith"
}

 what we just did:
– we described a query with label queryLabel and input parameter called $userName of type String
– The ! symbol means that $userName can’t be null
– (line 2) we are querying hello and passing $userName as a parameter
– in the second code snipped (under query variables) we are passing the actual value of our parameters. In our case the value of $userName

Hit the play button in the upper left section and you will get the response:

{
  "data": {
    "hello": "Hello Sam Smith"
  }
}

If we also want to query the rollDice we could just add this to our query:

query queryLabel($userName: String!) {
  hello(userName: $userName)
  rollDice
}

The great thing about GraphQL is that it will return only the queries that we requested, and also we could combine multiple queries (from the same schema) in one response.

Adding Redux

branch-name:  
Click To Copy

 

In the previous tutorial we showed how to build custom Redux like store, which is a great way to understand how Redux work, but if we want a production grade app, we have to do more work on our custom Redux like store, since we cheated a bit.

For example our redux store emits setState no matter if the properties are actually changed or not which is ok for a demo app but for a big app with a lot of mapStateToProps it will have performance impact, so let’s not re-invent the wheel but use the Redux store instead.

yarn add redux react-redux

Since Redux like implementation that we did in the previous tutorial is very similar to the actual Redux, there are just a few changes that we need to do in order to start using Redux.

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import reducers from '../../reducers';

import fetch from 'unfetch';


const store = createStore(reducers, {});

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <Provider store={store}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
              <Route exact path="*" component={PageLayout} />    
            </Switch>
          </Router>
        </ApolloProvider>
      </Provider>
    );
  }
}

 what we just did:
– we removed our custom store implementation:

import Store from ‘../../store’;
import Reducers from ‘../../reducers’;
import Provider from ‘../../containers/Provider’;


– and replaced it with the Redux provider and create store (line 8 and 9)
– we called the Redux createStore (line 15)
– finally we wrapped the app with the new provider passing the store (line 28)

Next step is just cleaning up the code a bit.
– We renamed the index.js reducer to ./src/reducers/useer.js so we could keep each different reducer types in separate files and we added one main reducer ./src/reducers/index.js that will combine all other reducers. (in our case ./src/reducers/useer.js for now)

./src/reducers/user.js

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

const initialState = {
  todos: [],
  userName: "No Name",
  editMode: false
};

  const reducer = (state = initialState, action) => {
    switch (action.type) {
      case CHANGE_USERNAME: {
        let newState = {...state};
        newState.userName = action.data;
        return newState;
      }

      case TOGGLE_EDIT_MODE: {
        let newState = {...state};
        newState.editMode = !newState.editMode;
        return newState; 
      }

      default:
        return state;
    }
  };

export default reducer;

 

./src/reducers/index.js

import { combineReducers } from 'redux';
import user from './user';

export default combineReducers({
  user
});

 

And the best part is that since our Redux like implementation was so similar to the actual Redux implementation, we have to make just two little changes:
– Replace import connect from '../../containers/Provider/connect'; with the Redux connect import { connect } from 'react-redux';
– And since we added combineReducers we have to add user property to access the users reducer.

./src/components/About/index.js ./src/components/Greetings/index.js.

import React, { Component } from 'react';
import { connect } from 'react-redux';

const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( storeState ) => {
  return {  
    userName: storeState.user.userName,
    editMode: storeState.user.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

 

./src/components/About/index.js ./src/components/About/index.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

const CHANGE_USERNAME = 'CHANGE_USERNAME';
class About extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: this.props.userName,
    };    
  }
  handleChange() {
    const userName = document.querySelector('input[name=username]').value;
    this.setState( { userName: userName } );
    this.props.onEdit(userName);
  }
  render() {
    return (
      <div>
        <p>This is <input type="text" name="username" value={this.state.userName} onChange={() => { this.handleChange()}} /></p>
      </div>
    );
  }
}
//export default About;
const mapStateToProps = storeState => ({
  userName: storeState.user.userName
}
);
const mapDispatchToProps = dispatch => ({
  onEdit: (userName) => dispatch({
    type: CHANGE_USERNAME,
    data: userName
  })
});
const AboutContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(About);
export default AboutContainer;

 

And delete ./src/containers/Provider folder.

And now all custom Redux like implementation was replaced wit the actual Redux store. Give it a try and everything shoul;d work like before but using the actual Redux store.

 

branch-name:  
Click To Copy

 

Max Consecutive Ones

Task

Given a binary array, find the maximum number of consecutive 1s in this array.

Example 1:

Input: [1,1,0,1,1,1]
Output: 3
Explanation: The first two digits or the last three digits are consecutive 1s.
    The maximum number of consecutive 1s is 3.

Note:

  • The input array will only contain 0 and 1.
  • The length of input array is a positive integer and will not exceed 10,000

Solution

The solution:

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMaxConsecutiveOnes = function(nums) {
    var count = 0;
    var last = 0;
    for(var c in nums) {
        if(nums[c])
            count ++;
        else {
            if(count > last)
                last = count;
            count = 0;
        }
    }
    return Math.max(count, last);
};

 

Plus One

Task

Given a non-empty array of digits representing a non-negative integer, plus one to the integer.

The digits are stored such that the most significant digit is at the head of the list, and each element in the array contain a single digit.

You may assume the integer does not contain any leading zero, except the number 0 itself.

Example 1:

Input:

 [1,2,3]

Output:

 [1,2,4]

Explanation:

 The array represents the integer 123.

Example 2:

Input:

 [4,3,2,1]

Output:

 [4,3,2,2]

Explanation:

 The array represents the integer 4321.

This problem was taken from Leetcode

Solution

The solution:

The solution is pretty straight forward. We traverse all digits in the opposite direction, and make sure that we add +1 only on the last digit (the first iteration ). Then if the digit > 9 we set up the dit to 0, and we carry on 1 to add it to the next digit, and keep going till we reach the first digit. If the first digit is 9 and cary over is not 0, we add 1 to the beginning of the array.

let’s consider: 9 9 9

iteration 1:
9            9
9            9
9 + 1 =  0 + cary on: 1

iteration 2:
9            9
9 + 1 =  0 + cary on: 1
0            0

iteration 3:
9 + 1 = 0 + cary on: 1
0          0
0          0

finally:
1     << adding leading 1 
0
0
0

which gave up the final result: 1 0 0 0

The solution will look like this:

Java Script

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function(digits) {
    
    var carryOn = 0;
    for(q = digits.length - 1; q!=-1; q--) {
        var digit = digits[q];
        if(q == digits.length - 1) {
            digit = digit + 1;
            if(digit == 10) {
                digit = 0;
                carryOn = 1;
            }
        }
        else {
            if(digit == 9 && carryOn) {
                digit = 0;
            }
            else {
                digit = digit + carryOn;
                carryOn = 0;
            }
        }
        digits[q] = digit;
    }
    if(carryOn > 0) 
        digits.unshift(1);
    return digits;
};

 

what we just did:
– (lines 11 – 18) happened only if this is the last digit, where we have to add 1
– (lines 18 – 20) if we have carry on value of carryOn > 0 and digit = 9 simply set digit = 0 and left carryOn to be equal to 1 for the next iteration.

Valid Parentheses

Task

Given a string containing just the characters '('')''{''}''[' and ']', determine if the input string is valid.

An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.

Note that an empty string is also considered valid.

Example 1:

Input:

 "()"

Output:

 true

Example 2:

Input:

 "()[]{}"

Output:

 true

Example 3:

Input:

 "(]"

Output:

 false

Example 4:

Input:

 "([)]"

Output:

 false

Example 5:

Input:

 "{[]}"

Output:

 true

This problem was taken from Leetcode

Solution

The solution:

Let’s look at the simplest scenario where we have only one type of brackets: ‘(‘ and ‘)’. Then all that we need to do in order to figure out if each bracket has corresponding closing bracket is to put each opening bracket into a stack, and pop one bracket when we see closing bracket.

Ideally if the brackets are “normalized” (all of the open one have corresponding closing brackets) we will end up with empty stack.

in example :

( ( ) ( ) ( ( ) ) )
1 2  3  4 5  6  7  8  9  10

so here are all 10 steps:

steps  stack
1          (
2          (  (
3         (
4         (  (
5         (
6         (  (
7         (  (  (
8         (  (
9         (
10

Immediately it becomes clear that if the string  length is not an even number, it automatically becomes invalid. So we could do this check in the very beginning (lines 9 and 10)  below.

So let’s look at the current example where we have 3 different tags: ‘{}’, ‘()’, ‘[]’

The rule for a valid string is:
open tags:
– we could have as many open tag as we want. i.e. : ({[[ ...
closing tags:
– every closing tag should match previously opened tag ([]) – valid, ([)] – invalid.

So now we know the rules, let’s write the code.

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    
    var input = s.split('');
    
    if(input.length % 2 != 0)
        return false;
    
    var stack = [];

    var tagIndex = {
        '(': 0,
        ')': 1,
        '{': 2,
        '}': 3,
        '[': 4,
        ']': 5
    };        


    for(var q=0;q < input.length; q++) {
        var symbol = input[q];
        var tagType = (tagIndex[symbol] % 2); // 0 - open, 1 - close
        if(tagType == 0) {
            stack.push(symbol);
        }
        else {
            // this is a closing tag, make sure that it follows the rules
            lastTag = stack.pop();            
            lastTagIndex = tagIndex[lastTag];
            if( tagIndex[symbol] != lastTagIndex + 1 )
                return false;
        }
    }
    
    if(stack.length > 0)
        return false;
    return true;
};

 

what we just did:
– lines 9 and 10: we check if the length of the string is odd and return false immediately if so.
– we added tagIndex object where every bracket has it’s index which will help us to identify if we have the open or closing bracket.
– If it is open bracket, we just putting it inside the stack.
– if this is a close bracket, we are using the newly created  tagIndex to figure out if this is the right closing bracket from the same type. Simply following the rule that we described above (lines 32 -35)  `if( tagIndex[symbol] != lastTagIndex + 1 )` Basically we check if the previous opening tag in the stack is of the same type of the current closing tag.

Build Redux like custom store.

 This exercise is a bit more challenging, that’s why I will add two git branches: one with the middle of the tutorial and one with the final product.

Although we are not going to use our custom Redux like store in the feature tutorials, it will be rewarding to be able to create your own ‘Redux like’ store.

You will understand how Redux work and why there is no magic happening there but a plain JavaScript is doing the job.

branch-name:  
Click To Copy
branch-name:  
Click To Copy

Local component state.

Let’s modify the Greeting component to use class instead of function and see how the state is tied up with the component.

./src/components/Greetings/index.js

import React, { Component } from 'react';
const styles = require('./styles.scss');
const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
class Greetings extends Component {
  constructor(props) {
    super(props); 
    this.state = {
      userName: "no name",
      editMode: false
      }
  }  
  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.setState({userName: newName, editMode: false});
  }
  usernameChanged(el) {
    this.setState({ userName: el.target.value });
  }
  render() {
    let element = <h2 onClick={() =>{ this.setState({editMode: true});    }}>Hello:  {this.state.userName}</h2>;
    if(this.state.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.state.userName} onChange={(el) => { this.usernameChanged(el)}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
  <div>
    <div className={styles.wrapper}>
      {element}
    </div>
  </div>);
  }
}
export default Greetings;

 what we just did:
– we set up the initial state into the constructor with userName set to “no name” and editMode set to false, so the component just simply shows “Hello: no name”
– in the render function we are checking if component is in edit mode editMode parameter and rendering either the <h2> tag with “Hello: [USERNAME] or the tag with editable <input> (line 23)
– we added doneEditUsername() function hooked to the ‘done’ button, which will simply set the state userName to whatever was the user input.
– also added usernameChanged() function  that will set the new username state so the input tag will reflect the new component name. Otherwise the input tag will be read only.

Now if you give it a try and play around switching to a different pages you will notice that the component won’t preserve the changes and it will default to “no name” each time when you switch back and forth to some other page. This happens because the local state is not connected to a store.

Creating persistent store.

In order for our component to preserve the changes, we need to have some sort of persistent store. There are many ways to achieve this but let’s explore the simplest one.

Use plain global object as a store.

This simply could be achieved by adding a store object to the global window object, and set up the default values (lines 13 – 16)

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import Store from '../../store';
import Reducers from '../../reducers';

import fetch from 'unfetch';

window.store = {
  userName: "no name",
  editMode: false
}

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <ApolloProvider client={client}>
        <Router>
          <Switch>
            <Route exact path="*" component={PageLayout} />    
          </Switch>
        </Router>
      </ApolloProvider>
    );
  }
}

and let’s hook our new store:

./src/components/Greetings/index.js

import React, { Component } from 'react';
const styles = require('./styles.scss');
const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
class Greetings extends Component {
  constructor(props) {
    super(props); 
    this.state = {
      userName: window.store.userName,
      editMode: window.store.editMode
      }
  }  
  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    window.store.userName = newName;
    window.store.editMode = false;
    this.setState({userName: window.store.userName , editMode: window.store.editMode});
  }

  usernameChanged(el) {
    window.store.userName = el.target.value;
    this.setState({ userName: window.store.userName });
  }

  render() {
    let element = <h2 onClick={() =>{ window.store.editMode = !window.store.editMode; this.setState({editMode: window.store.editMode});    }}>Hello:  {this.state.userName}</h2>;
    if(this.state.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.state.userName} onChange={(el) => { this.usernameChanged(el)}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
  <div>
    <div className={styles.wrapper}>
      {element}
    </div>
  </div>);
  }

}
export default Greetings;

what we just did:
– we hooked window.store to feed the local state properties. (Lines 9 and 10). This way when the component is re-rendered next time it will get these values from the persistent global store.
– we changed doneEditUsername() and usernameChanged() to store the values of the userName and editMode into our global store and used these values to set the state and update the component.
– we updated the value of window.store.editMode to be equal to the opposite value of itself so we could toggle the edit mode  window.store.editMode=!window.store.editMode (line 26)

Now our component preservers the state. Even if you toggle the edit mode, navigate to some different section and get back, the component will conveniently wait into the editing mode, with the input field ready to get the new user name.

 Cool right now we have functional persistent state, but the way it was implemented is a mess!

Why ?
– We are setting the store parameters from many different places in the code, which soon will become a nightmare to debug.
– we are mutating the state, one more reason why debugging will become almost impossible.

Let’s fix this.

Doing it the Redux way.

Let’s create a CreateStore function (line 10 below) that will expose three helper methods:

  • dispatch(action) – which with take action parameter which will look like this for example: {type: ‘CHANGE_USER_NAME’, data: ‘New User Name’ }
  • getState() – will return current state. This way we will protect the state from direct mutations. Only dispatching an actions could do this.
  • subscribe(handler) – will be fired on component will mount and will pass a handler function that will react on state change, and call setState to update the local state and trigger UI update. The handler function for the Greetings component will look like this:
    ...
          // this function is called when dispatch method is called and state is changed.
          const newState = window.store.getState();
          this.setState( {userName: newState.userName,
                          editMode: newState.editMode
          });
    ...

    this function will also return the unsubscribe function that we have to fire on componentWillUnmaunt

Let’s start with creating the store:

./src/store/index.js

const validateAction = action => {
  if (!action || typeof action !== 'object' || Array.isArray(action)) {
    throw new Error('Action must be an object!');
  }
  if (typeof action.type === 'undefined') {
    throw new Error('Action must have a type!');
  }
};

const createStore = reducer => {
  let state;
  const subscribers = [];

  const store = {
    
    dispatch: action => {
      validateAction(action);
      state = reducer(state, action);
      subscribers.forEach(handler => handler());
    },

    getState: () => state,

    subscribe: handler => {
      subscribers.push(handler);
      return () => {
        const index = subscribers.indexOf(handler);
        if (index !== -1) {
          subscribers.splice(index, 1);
        }
      };
    }
  };

  // Returns the initial state
  store.dispatch({type: '--INIT--'});
  return store;
};
export default createStore;

 what we just did:
– like we described above, we created createStore function that will return an object with dispatch, getState and subscribe functions (lines 16,22,24);
dispatch will take an action and will pass it to the reducer, which will match the appropriate action type and will update the store (line 18).
Next it will call all subscribed functions, which will react on state update and will do setState to trigger UI update.
getState simply returns the new state and protects the state object from accidental mutation.
– subscribe function accepts handler function which will be invoked when the dispatch method was called to dispatch an action. It also returns another function to unsubscribe from the store and remove the handler from the subscribers array (line 26)

And now let’s do the reducer which simply will take the initial state, and then we will use the classic switch-case to match the action.type and return the new state. So right now only the reducers are able to update the state, making debugging much more predictable.

./src/reducers/index.js

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

const initialState = {
  userName: "No Name",
  editMode: false
};

  const reducer = (state = initialState, action) => {
    switch (action.type) {

      case CHANGE_USERNAME: {
        let newState = {...state};
        newState.userName = action.data;
        newState.editMode = false;
        return newState;
      }

      case TOGGLE_EDIT_MODE: {
        let newState = {...state};
        newState.editMode = !newState.editMode;
        return newState; 
      }

      default:
        return state;
    }
  };

export default reducer;

Let’s instantiate the store and attach it to the window object so it will be accessible by the components:

./src/components/App/index.js

import React, { Component } from 'react';
import PageLayout from '../../containers/PageLayout';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Store from '../../store';
import Reducers from '../../reducers';
import styles from './styles.scss';


window.store = Store(Reducers);

export default class App extends Component {
  render() {
    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL }),
      cache: new InMemoryCache()
    });  
    return (
      <div className={styles.appWrapper}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
            <Route exact path="*" component={PageLayout} />  
            </Switch>
          </Router>
        </ApolloProvider>
      </div>        
    );
  }
}

and now let’s use our new store in the Greeting component:

./src/components/Greetings/index.js

import React, { Component } from 'react';
const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
class Greetings extends Component {
  
  constructor(props) {
    super(props); 
    const store = window.store.getState();
    let userName = store.userName;
    let editMode = store.editMode;

    this.state = {
      userName: userName,
      editMode: editMode
      }
  }  

  componentWillMount() {
    this.unsubscribe = window.store.subscribe(() => {
      // this function is called when dispatch method is called and state is changed.
      const newState = window.store.getState();
      this.setState( {userName: newState.userName,
                      editMode: newState.editMode
      });
    });  
  }
  componentWillUnmount() {
    this.unsubscribe();
  }  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    window.store.dispatch({type: CHANGE_USERNAME, data: newName });  
  }

  usernameChanged(el) {
    this.setState({ userName: el.target.value });
  }

  render() {
    let element = <h2 onClick={() =>{ window.store.dispatch({type: TOGGLE_EDIT_MODE });     }}>Hello:  {this.state.userName}</h2>;
    if(this.state.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.state.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
  <div>
    <div className={styles.wrapper}>
      {element}
    </div>
  </div>);
  }

}
export default Greetings;

Well done! Now it’s less messy, but the implementation still requires a lot of work to wire up the components, also we expose the store as a global variable which is a bit messy.

branch-name:  
Click To Copy

Create Provider and connect to pass properties using React Context.

We could fix this by using React’s Context.
Let’s explain this. Usually, we pass parameters from high order component to the components down below, but if we have to pass parameter from the highest component to the 4th component, we have to keep passing the property as props to all components down to the 4th component. This is quite uncomfortable and tedious, but here is where Context comes handy. Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Let’s do this! We will create:

  • a high order component that will pass the store properties down using the Context. We will call it Provider.
  • a Connect component, which will act like a wrapper component (or factory component) and will transform back the properties from the context and pass them to the wrapped component.

Creating provider component.

./src/containers/Provider/index.js

import React from 'react';
import PropTypes from 'prop-types';

class Provider extends React.Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: PropTypes.object
};



export default Provider;

 what we just did
– we created a high order component that will convert a store properties into a context property.
– then the component will render the child components.

It’s pretty straight forward. Now we have the store properties passed into the context, and we will need a way to retrieve them back. For this purpose we will have to create a connect component.

Creating connect factory component.

./src/containers/Provider/connect.js

import React from 'react';
import PropTypes from 'prop-types';

const connect = (
    mapStateToProps = () => ({}),
    mapDispatchToProps = () => ({})
  ) => Component => {
    class Connected extends React.Component {

      onStoreOrPropsChange(props) {
        const {store} = this.context;
        const state = store.getState();

        const stateProps = mapStateToProps(state, props);
        const dispatchProps = mapDispatchToProps(store.dispatch, props);
        this.setState({
          ...stateProps,
          ...dispatchProps
        });
      }

      componentWillMount() {
        const {store} = this.context;
        this.onStoreOrPropsChange(this.props);
        this.unsubscribe = store.subscribe(() =>
          this.onStoreOrPropsChange(this.props)
        );
      }

      componentWillReceiveProps(nextProps) {
        this.onStoreOrPropsChange(nextProps);
      }

      componentWillUnmount() {
        this.unsubscribe();
      }
      render() {
        return <Component {...this.props} {...this.state}/>;
      }
    }
  
    Connected.contextTypes = {
      store: PropTypes.object
    };
  
    return Connected;
  };

  export default connect;

 what we just did:
– we created a higher order component factory.
– It takes two functions and returns a function that takes a component and returns a new component, passing the store as prop value.
– the component retrieves back the store from the context (lines 11,12)
– then it calls mapStateToProps and mapDispatchToProps functions which lives in the actual component that we want to connect to the store. These functions does exactly when their names suggest. They map the properties from the store returning the state.
– next the setState is called with the updated state, and the view got updated.

Using the provider component.

This is as simply as wrapping the application with our Provider component.

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import Store from '../../store';
import Reducers from '../../reducers';
import Provider from '../../containers/Provider';

import fetch from 'unfetch';


let store = Store(Reducers);

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <Provider store={store}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
              <Route exact path="*" component={PageLayout} />    
            </Switch>
          </Router>
        </ApolloProvider>
      </Provider>
    );
  }
}

Now the store properties are converted to Context, and passed down.

Use Connect wrapper component.

Now in order to retrieve the store properties and use them, we wrap the components that should get these properties from the store with the newly created connect component, and clean up the code that we don’t need any more.

./src/components/Greetings/index.js

import React, { Component } from 'react';
import connect from '../../containers/Provider/connect';

const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( state ) => {
  return {  
    userName: state.userName,
    editMode: state.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

 what we just did:
– we wrapped Greetings component with the ‘connect’ component (lines 63-66) which now passes store properties to the Greetings component.
– we mapped the properties that we are interested in in mapStateToProps so only these properties will be passed to the Greeting component (line 44).
– we mapped the functions that will dispatch actions in mapDispatchToProps (line 51)
– we are calling changeUserName and toggleLogInPopup to dispatch the appropriate actions and update the state.
– we set up the input field to use this.props.userName from the store (line 32) and also set up the ‘Hello’ display to use this.props.userName

Now everything is wired into nice automatic way, and this is in general how Redux works.

Add another store connected component and share the store.

Let’s also add another store connected component and see how both of them could use/edit the same store property. Let’s modify the About component to show/edit the userName property.

import React, { Component } from 'react';
import connect from '../../containers/Provider/connect.js';

const CHANGE_USERNAME = 'CHANGE_USERNAME';
class About extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: this.props.userName,
    };    
  }
  handleChange() {
    const userName = document.querySelector('input[name=username]').value;
    this.setState( { userName: userName } );
    this.props.changeUserName(userName);
  }
  render() {
    return (
      <div>
        <p>This is <input type="text" name="username" value={this.state.userName} onChange={() => { this.handleChange()}} /></p>
      </div>
    );
  }
}
//export default About;
const mapStateToProps = storeState => ({
  userName: storeState.userName
}
);
const mapDispatchToProps = dispatch => {
  return {
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};

const AboutContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(About);
export default AboutContainer;

Pretty straight forward! Now no matter where you are going to edit the username both ‘About’ and ‘Greetings’ components will reflect the change.

branch-name:  
Click To Copy
branch-name:  
Click To Copy