Saturday, October 19, 2019

Why do we need React Hooks?

What are hooks

You go to any react session in 2019 or look at any sample on React, you get a word how cool hooks are? Hooks has slowly graduated from hippies & enthusiasts to all. There's only one reason it's talked so much. Because "It makes sense"


Like any new solution, there will be issues and we'll talk about them as we work on more samples, but on the whole, hooks has given React community a unique experience that makes us feel(proud) better as developers.

Why now?

So far we've used different ways to create React components. createClass and/or ES6 classes. Although things have improved with time, some of the fundamentals have taken a down turn and got more complicated. Before we delve into answers, let's talk about a fundamental reason React came to serve this market - "Easy, simple and reusable components".

With time, additional baggage web technology brought in, made React some what convoluted. React engineering had to go back to their boards and re-engineer some of their thoughts and concepts to achieve and stay focussed on the primary objective.


  • Handling state within a component
  • Life cycle events for a component
  • Separate visual elements from non-visual one's (service invocations from UI)
  • Better integration with other solutions like MobX, Redux etc
Anyone who worked on React long enough would either feel overwhelmed with 13.x & later or feel everything is fine, why change? Either ways, you'd start to look at what hooks bring to table, you'll see your code get a lot better and much more manageable, as opposed to writing less re-usable components or building more HOC.


Different types of Hooks


In addition, we can create our own custom hooks that are more re-usable. Not to forget, we also have other libraries like Redux that come with their own hooks. Yes, HOOKS EVERYWHERE!

Use Reducer - 2

In our earlier article we saw how to use both a simple and an object based state with reducer. Reducer's are powerful functions and their purpose is revealed as app/component complexity increases. The more we try to localize reducer functionality in to a single component, the more we build heavier components and lesser are our chance to re-use components.

It's no longer about a component consuming a simple reducer. We'll have scenarios where our components may need multiple reducers. By moving reducer to upper layers is the best way for us to handle a complete solution. Say for e.g. a simple login form. As soon as a user logs in, there'll be sections of application wanting to know a unique ID of user, other sections needs name of user, while others just need to know if the user is logged in or not.




Our approach for this solution is to start with a reducer, associate with context, allow context to work with components.


Although above is a Redux flow chart, our approach for reducer + context is pretty much the same. Now you know why confusions arise on will Reducer + Context replace REDUX.

export const initialState = {
  data: 'NO_DATA',
  userinfo: {
    email: '',
    username: 'GUEST'
  }
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_DATA':
      return {
        ...state,
        data: action.payload
      }
    case 'LOGOUT':
    case 'RESTORE_STATE':
      return initialState;
    case 'LOGIN':
      return {
        ...state,
        userinfo: action.payload.userinfo
      }

    default:
      return state;
  }
}

export default reducer;

Above code is a simple reducer that has an initial state. In a real world situation, FETCH_DATA will make a call to remote service. But not here. Let's keep it simple. It's not long before we build & invoke a remote service from reducer. But not now. "FOCUS". In addition, we have a simple LOGIN and LOGOUT handler update and clean state.

Next step, create a store and context 

import React, {createContext, useReducer} from 'react';
import reducer, {initialState} from "../reducers";

const Store = createContext();


const Provider = ({children}) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const store = {state, dispatch};
  return (
    <Store.Provider value={store}>
      {children}
    </Store.Provider>
  )
}

export {Store, Provider}

We now have a custom component "Provider" that injects Store context. Any child of Provider will now have a context and in the process reducer. Context as we saw from previous examples can be nested too. If you want to define any additional context to components down the line this code does not hinder you in anyway. Our next step is to integrate our App component

import React from 'react';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
import HomePage from "./components/pages/home";
import {Provider} from "./components/store";
import Appbar from "./components/controls/Appbar";
import Features from "./components/pages/features";

function App() {
  return (
    <div>
      <Router>
        <Provider>
        <Appbar/>
        <div className="container">
        <h2>Let's try a Reducer on global context!</h2>

          <Route exact path="/" component={HomePage}/>
          <Route path="/features" component={Features}/>

        </div>
        </Provider>
      </Router>
    </div>
  );
}

export default App;

Our App component hosts other component's. For brevity, let me explain you, why the structure and some reason for this madness


We have a multiple pages and pages with multiple components. Objective is to see how we can exchange data from one component to another. Across pages, components within pages, with refresh and without refresh. When we move from one page to another, we go through page lifecycle, so refresh happens automatically, but Navbar is constant across the application, to refresh data automatically is some achievement. Even within the same page, data from one component to another should be shared without props and other events. Lets's start with Home page. It has 2 components - Component1 & Component 2.

HomePage.js
============
import React from 'react';
import Component2 from "../controls/component2";
import Component1 from "../controls/component1";

const HomePage = () => {
  return (
    <div>
      <h4>This is Home page</h4>
      <Component1/>
      <hr />
      <Component2/>
    </div>
  );
};

export default HomePage;

Component1.js
==============
import React, {useContext} from 'react';
import {Store} from "../store";

const Component1 = () => {
  const {state, dispatch} = useContext(Store);
  const onSetContext = (e) => {
    e.preventDefault();
    dispatch({type: 'FETCH_DATA', payload: 'Hello World!!!'});
  }
  const onResetContext = (e) => {
    e.preventDefault();
    dispatch({type: 'RESTORE_STATE'});
  }
  return (
    <div>
      <button className="btn btn-primary" style={{margin: '15px'}} onClick={onSetContext}>Set Context</button>
      <button className="btn btn-secondary" style={{margin: '15px'}} onClick={onResetContext}>RESET Context</button>
    </div>
  );
};

export default Component1;

Component2.js
==============
import React, {useContext} from 'react';
import {Store} from "../store";

const Component2 = () => {
  const {state, dispatch} = useContext(Store);
  return (
    <div className="container">
      <p className="success"> Value in state is <small>{state.data}</small></p>
    </div>
  );
};

export default Component2;

Component1updates state and Component2 shows current content from state. Now come's a question - can 'useState' handle this better? Answer is YES. But wait for complete answer down below and my reasons to go with reducer + context. This code is like any other component connected to a 'useReducer' hook (but done through useContext, silly). You have state & dispatch mechanism. When we want to observe data, you fetch from state and when you wish to update state, you dispatch your action through 'dispatch' object. Nothing different or new from what we saw in earlier article.


How about, fetching these values from another page. That'll not be any different. You add 'useRedcuer' hook to this new page, get contents of state and use it. As simple as that.

Now, let's take a more objective example of login/registration. A simple features page, has a form and it is subscribed to app context through useContext

import React, {useContext, useState} from 'react';
import {Store} from "../store";

const Features = () => {
  const [username, setUsername] = useState();
  const [email, setEmail] = useState();
  const {state, dispatch} = useContext(Store);
  const onLogin = (e) => {
    e.preventDefault();
    dispatch({type: 'LOGIN', payload: {userinfo: {email, username}}});
  }
  return (
    <div>
      <form>
        <div className="form-group">
          <label htmlFor="exampleInputEmail1">Email address</label>
          <input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
                 placeholder="Enter email" onChange={(e) => setEmail(e.target.value)} />
            <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone
              else.</small>
        </div>
        <div className="form-group">
          <label htmlFor="exampleInputName">Name</label>
          <input type="text" className="form-control" id="exampleInputName"
                 placeholder="Full Name" onChange={(e) => setUsername(e.target.value)}/>
        </div>
        <button type="button" className="btn btn-primary" onClick={onLogin}>Submit</button>
      </form>
    </div>
  );
};

export default Features;

Agin, code is similar to above samples. we have a form, update local state with name and email (on hindsight, we could have done a useRef, in place of useState too. That'd have given a lot more freedom to handle button state and things like that. For now, it does not hurt). When user taps on login, we submit the page with dispatch action.. It in turn invokes reducer, updates local state with user information. We don't have anything drastic in our application to show the world post login, but what's important is, you'll see navbar change from 'GUEST' to the username entered. 

import React, {useContext} from 'react';
import {Link} from 'react-router-dom'
import {Store} from "../store";

const Appbar = () => {
  const {state, dispatch} = useContext(Store);
  return (
    <div>
      <nav className="navbar navbar-expand-lg navbar-light bg-light">
        <Link className="navbar-brand" to="/">Navbar w/ text</Link>
        <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText"
                aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
          <span className="navbar-toggler-icon"></span>
        </button>
        <div className="collapse navbar-collapse" id="navbarText">
          <ul className="navbar-nav mr-auto">
            <li className="nav-item active">
              <Link className="nav-link" href="#">Home <span className="sr-only">(current)</span></Link>
            </li>
            <li className="nav-item">
              <Link className="nav-link" to="/features">Features</Link>
            </li>
            <li className="nav-item">
              <Link className="nav-link" href="#">Pricing</Link>
            </li>
          </ul>
          <span className="navbar-text">
      You are a {state.userinfo.username} user!
    </span>
        </div>
      </nav>
    </div>
  );
};

export default Appbar;

Nothing fancy with Appbar. We are updating data from state and the way we listen to state is through 'useContext'.

It'll be an interesting exercise for you to try the same application without reducer with just context alone. Try it out and you are in for some surprise!

useState vs useReducer

We can do a lot of stuff that we do with useReducer in useState. I prefer to go 'useReducer'
  1. Even within component, when we have more than couple of 'state' objects to deal with
  2. When there's business logic or validation to deal with before state is updated
  3. Have a component re-use for the same logic across other components
  4. Better testability
If you have any more thoughts, please do share.

Our next work will be to integrate Redux with React Hooks and capability to associate multiple reducers for context.


Friday, October 18, 2019

useReducer

useReducer is one of the more powerful hooks that help you handle your data. This is akin to reducer you'd have come across with any Redux implementation. Reducer concept is simple. It's a simple JS function that allows you to be focussed on what you want to do. It returns a simple state and action. Based on your action invoked state is updated. That's it. Nothing more and nothing less.


To slide in easily let's try a simple counter example from our previous cases. But before we get into the example, lte's look at what a reducer is? It's a simple javascript function. It has nothing to do with either Redux or React. All that a reducer does is what the name suggests. "Reduces" or alters the state.

Any reducer takes 2 parameters - state and action. Based on the type of action, state is updated.

const counterReducer = (state, action) => { console.log("Invoked - Reducer"); switch (action.type) { case "INCREMENT": console.log("Value before increment", state); state = state + 1; console.log("Value post increment", state); break; // return state; case "DECREMENT": state = state - 1; // return state; break; default: break; } return state; };

As you see in the code above check action type, update state which in this case is a simple variable and that makes updating state easier.

Full code of the components is as below

const Counter = () => {
  const [state, dispatch] = useReducer(counterReducer, 1);
  return (
    <div>
      <div className="row">
        <form className="col s12">
          <div className="row">
            <div className="input-field col s4">
              <label htmlFor="counter">{state}</label>
            </div>
          </div>
          <div className="row">
            <div className="input-field col s4">
              <button
                className="btn blue darken-5"
                onClick={e => {
                  e.preventDefault();
                  dispatch({ type: "INCREMENT" });
                }}
              >
                Increment
              </button>
            </div>
            <div className="input-field col s4">
              <button
                className="btn red darken-5"
                onClick={e => {
                  e.preventDefault();
                  dispatch({ type: "DECREMENT" });
                }}
              >
                Decrement
              </button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
};

export default Counter;

Important thing to notice above is how we use 'useReducer' hook. First, we start by declaring it from 'react' package. We use the hook and pass reducer as a parameter along with initial value. What we get back is state and dispatch. We no longer deal with 'state' directly. 'dispatch' is the way forward. With dispatch code splatter is avoided and all UI related business validations can be handled in reducer. Another advantage is capacity to do uit testing and physical code separation - Reducer's can be defined in a different file, this way code is lot more clean and easier to comprehend.

Our next stop is to have a more complicated reducer with an object. Let's a simple ToDo reducer. As you can see from the code below, we can easily extend this to any bigger or smaller complex object!

const reducer = (state, action) => {
  switch(action.type) {
    case 'ADD_TODO':
      state = {
        todos: [...state.todos, {task:action.todo, completed: false}],
        todoCount: state.todoCount +  1
      }
      break;
    case 'TOGGLE_TODO':
      state = {
        todos: state.todos.map((todo, index) => index === action.index ? { ...todo, completed: !todo.completed } : todo),
        todoCount: state.todoCount
      }
      break;
    case 'DELTE_TODO':
      state = {
        todoCount: state.todoCount - 1,
        todos: state.todos.filter((todo) =>  todo.task !== action.task)
      }
      break;
    default:
      break;
  }
  console.log(state, action);
  return state;
}

Everything is the same as in previous example - State, action, but what changes is how we update state. For 'ADD_TODO' we update 'todos' item with new item, without leaking what it contains. Rest other cases are self explanatory

Code for the component is as below. It's no different from any other component, excpet for how we handle events to update data

const ToDo = () => {
  const [text, setText] = useState('');
  const [{todos, todoCount}, dispatch] = useReducer(reducer, {todos: [], todoCount: 0});
  return (
    <div className="">
      <div className="row">
        <form className="col s12" onSubmit={(e) => {e.preventDefault();}}>
          <div className="row">
            <div className="input-field col s6">
              <input placeholder="New Todo" name="todo_text" type="text" value={text} 
                onChange={(e) => setText(e.target.value)}/>
              <label htmlFor="todo_text">New Todo</label>
            </div>
            <div className="input-field col s6">
              <button className="btn geen darken-5" onClick={(e) => {
                e.preventDefault();
                dispatch({type: 'ADD_TODO', todo: text});
                setText("");
              }}>Add</button>
              <p>Total items - {todoCount}</p>
            </div>
          </div>
        </form>
      </div>
      {
        todos.length <= 0 ? null : todos.map((todo, index) => {
          return (
              <div className="row" key={index}>
                <div className="input-field col s6" style={{
                  textDecoration: todo.completed ? "line-through" : ""
                }}>
                  <p>{todo.task}</p>
                </div>
                <div className="input-field col s1">
                  <button className="btn green darken-5" onClick={(e) => { dispatch({type: 'TOGGLE_TODO', index})}}>Toggle</button>
                </div>
                <div className="input-field col s1">
                  <button className="btn red darken-5" onClick={(e) => { dispatch({type:'DELTE_TODO', task: todo.task})}}>Delete</button>
                </div>
              </div>
          )
        })
      }
    </div>
  )
}

export default ToDo;

Redcuer's in a simple or even a complex component, does not do justice to its capability. Together with context, Reducer's make a huge impact to overall synergy of application. Not only does it offer separation of concerns, but as stated earlier, it allows for applications to be unit tested easily and above all, allows for easier agile development methodologies, a big plus for companies across geographies. Let's look at them text Redux + Context

Wednesday, October 16, 2019

useContext

useState hook helps us persist information locally in a component. What if we want to use data across components. Indeed there's a complicated way in the form of useState. We can declare useState at a top level component and then use props to pass those components to child components and manipulate global state.

Sure that does work and I have done that myself. If your application is nothing more than 2 pages and has zero complexity, such a solution works. You don;t want to deal with the complications of MobX or Redux, local state should be good enough. But seldom we deal with such applications. A typical application may have user data, preferences, API information to be used across the application. We could use a variety of strategy to put together a working solution. We could have API information as a part of ENV, user details in things like cookie, and finally data in local storage (PWA approach). But you see how the application is broken, with very little strategy. If we wish to go local storage, would it not be easier if we have some global context and we persist that global context as opposed to objects from different classes and components?

In comes useContext. React Hooks most liked item is "useContext". People who dealt with the complexity of Redux for smaller application can talk days about how useContext changed their approach and ease of application. Conversely, with context and useReducer hook many started to understand how Redux actually works and started to use Redux judiciously as opposed to walk in opposite direction.

Let's start by building a simple context and then progress into something more concrete.

A single Provider Context

There are 3 sides to using context

  1. Create a store
  2. Add provider
  3. Add consumer
Let's start by building a very simple context app that will remember something as simple as a a number that we can access across components, without passed as props.

To start with we create store for context, say numcontext.js

Code for store
import { createContext } from "react";

const NumContext = createContext();
export default NumContext;

Provider code
With the context created, we need add it to our index.js, along with initial value of kind

<NumContext.Provider value={42}>
 <App />
</NumContext.Provider>

Consumer code
For us to consume this context, we need to add consumer attribute on NumContext in our component and that's it!
import React from "react";
import NumContext from "./numcontext";

function App() {
  return (
    <div className="container">
      <h2>A simple Number context!</h2>
      <NumContext.Consumer>
        {(value) => (<p>Some text - {value}</p>)}
      </NumContext.Consumer>
    </div>
  );
}

export default App;

Now, go ahead, create another component in your application and add it to App.js or in index.js (Within Provider tag). Do the same as above. Create a consumer tag and a callback function to read from context.

Above example is good for us to start. For all practical reasons we need to a more realistic example. Now let's consider a simple logon application. We create context first, empcontext.js

import {createContext} from 'react';

const EmpInit = {
  firstName: 'Tom',
  lastName: 'Moody',
  email: 'tom.moody@dontopen.com',
  updateDetails: function(details) {
    this.firstName = details.firstName;
    this.lastName = details.lastName;
    this.email = details.email;
  }
};

const EmpContext = createContext();

export {EmpContext, EmpInit};

Next step is to include this context into index.js/App.js or your parent component

<EmpContext.Provider value={EmpInit}>
    <EmpComponent />
    <Login />
</EmpContext.Provider>
Note: Don't forget to add your imports. That's important :)

Finally add consumer in your component to consume data, like the following, say empcomponent.js

import React from "react";
import { EmpContext } from "./empcontext";

const EmpComponent = () => {
  return (
    <div className="container">
      <h4>Employee details</h4>
      <EmpContext.Consumer>
        {data => {
          return (
            <dl className="row">
              <dt className="col-sm-3">Name</dt>
              <dd className="col-sm-9">
                {data.firstName} {data.lastName}
              </dd>
              <dt className="col-sm-3">Email</dt>
              <dd className="col-sm-9">
                {data.email}
              </dd>
            </dl>
          );
        }}
      </EmpContext.Consumer>
    </div>
  );
};

export default EmpComponent;

Whenever EmpComponent is displayed, you should see it glimmer with your init values. Keep changing the init values to see the display change. Now wait, I know what you are thinking. You say there's more to the story. Let's add a login form to update context, then see, if updated state shows up.

Note: Add a simple page router and have different pages for your application. Keep it simple for experimentation sake


import React, { useState } from "react";
import { EmpContext } from "./empcontext";
const Login = () => {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [email, setEmail] = useState("");

  const onUpdateDetails = context => {
    context.updateDetails({firstName, lastName, email});
  };
  return (
    <div className="container">
    <EmpContext.Consumer>
      {(context) => {
        console.log(context);
        return (
          <form>
            <div className="form-group">
              <input
                type="text"
                className="form-control"
                id="firstName"
                placeholder="First Name"
                value={firstName}
                onChange={e => setFirstName(e.target.value)}
              />
            </div>
            <div className="form-group">
              <input
                type="text"
                className="form-control"
                id="lastName"
                placeholder="Last Name"
                value={lastName}
                onChange={e => setLastName(e.target.value)}
              />
            </div>
            <div className="form-group">
              <input
                type="email"
                className="form-control"
                id="email"
                placeholder="Email ID"
                value={email}
                onChange={e => setEmail(e.target.value)}
              />
            </div>
            <button className="btn btn-info" onClick={(e) => { 
              e.preventDefault();
              onUpdateDetails(context);
            }}>
              Update
            </button>
          </form>
        );
      }}
      </EmpContext.Consumer>
    </div>
  );
};

export default Login;

With router in place, go ahead implement Login component for say /login route and /details component for EmpComponent. Play around and see details change as you change value's in /login route.

Multi-Provider Context


Application rarely has one context. Indeed you'll want an over arching AppContext, but then different modules will need respective context - Separation Of Concerns (SOC). With context hooks, its much, much easier than you think. Here's a sample of what I mean.

<div>
  <AppContext.Provider value={appInit}>
    <Navbar />
    <NumContext.Provider value={42}>
      <App />
    </NumContext.Provider>
    <EmpContext.Provider value={EmpInit}>
      <EmpComponent />
      <Login />
    </EmpContext.Provider>
  </AppContext.Provider>
</div>

What's Next?

We saw useState earlier. useState helps to maintain state for simple components. As we strive to have stateless components, useState starts to wither and give way to context. With context we still have a lot of nut and bolts to take care of. That's were useReducer hook comes to our rescue. Let's look at that next. useReducer is a very powerful concept and simple to handle. You'll see how it'll change the way we handle state. Down the line when we do useRedux, you'll see how easy it is to follow as opposed to the time you spent learning Redux the old school way.

Is this a replacement for Redux?


Context works great for simple cases and to an extent for certain complex cases too. But saying context can replace Redux is taking it too far from my little experience. I'd give rather a subjective answer to this "It depends". I'm a firm believer on "Do what's needed". Do start an application with Redux. Start small, see if the application performs with what's available in the first place. Slowly expand your contours, if the need is not fullfilled with context, go for Redux. 

Do you need to change to Class based development for Redux?
No. Redux supports hooks. You'll see a blog post coming soon on how to add Redux to hooks.

useState

useState

Allows for a component to save data within the component and for its children

A simple counter

create a simple variable through useState with an initial value. Use the components to fiddle with its value

import React, { useState} from 'react';

const CounterPage = ({counter}) => {
  let [count, setCount] = useState(counter)

  const addCounter = (e) => {
    setCount(++count);
    
  }

  const reduceCounter = (e) => {
    setCount(--count);
  }

  return (
    <div className="container">
      <h4>Counter component</h4>
      <p>Count is {count}</p>
      <button class="btn waves-effect waves-light" onClick={addCounter}>
        <i class="material-icons">add</i>
      </button>  
      <button class="btn waves-effect waves-light" onClick={reduceCounter}>
        <i class="material-icons">arrow_downward</i>
      </button>
    </div>
  )
}

export default CounterPage;


Let's do this same counter from an object. It's not that different from above. As you can see from the code below, the only change you may see in the code is the way we update. We use ... (spread operator) to copy over current content and update the variable of our choice.

import React, {useState} from 'react'

const SimpleObjectCounter = () => {
  const init = {counter1: 10, counter2: 10};
  let [complexCounter, setComplexCounter] = useState(init);

  return (
    <div className="container">
      <h2>A little COMPLEX counter</h2>
      <p>We are now dealing with a counter object and not a simple counter variable</p>
      <p>
        Counter 1 - {complexCounter.counter1}<br />
        Counter 2 - {complexCounter.counter2}
      </p>
      <button onClick={e => setComplexCounter(currentState => ({
        ...currentState,
        counter1: currentState.counter1 + 1
      }))}>Increment Counter 1</button>
    </div>
  )
}

export default SimpleObjectCounter


A form with state

This is an extension of what we have above, with HTML form. Whats interesting in this case though is the way we do 2 way update to state. A simple onChange event to update state and set value property of HTML control with data from state. And that's pretty much it

import React, {useState} from "react";

const SimpleForm = () => {
  const formInit = {first_name: '', last_name: ''};
  let [simpleForm, setSimpleForm] = useState(formInit);

  return (
    <div className="row">
      <form className="col s8 offset-m3" onSubmit={(e) => {
        e.preventDefault();
        console.log(simpleForm);
      }}>
        <div className="row">
          <h4>Simple useState for a Form</h4>
          <div className="input-field col s4">
            <input
              placeholder="Placeholder"
              id="first_name"
              type="text"
              value = {simpleForm.first_name}
              className="validate"
              onChange = {e => {
                const value = e.target.value;
                // console.log(value);
                setSimpleForm(currentState => ({
                ...currentState,
                first_name: value
              }))}}
            />
            <label htmlFor="first_name">First Name</label>
          </div>
          <div className="input-field col s4">
            <input id="last_name" type="text" className="validate" onChange = {e => {
              const value = e.target.value;
              // console.log(value);
              setSimpleForm(currentState => ({
              ...currentState,
              last_name: value
            }))}} />
            <label htmlFor="last_name">Last Name</label>
          </div>
        </div>
        <div className="row">
        <div className="col s6">
            <button type="submit">Submit</button>
        </div>
        </div>
      </form>
    </div>
  );
};

export default SimpleForm;

You can also split above state to multiple variables and track them separately too, if that's easier

const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');

Create a custom state hook

Let's move on to another level. Instead of handling all state level items in raw mopde, we could create a custom hook of our own and expose simple functions for operation. Like say from the above example, we could have functions like updateState('FirstName', e.target.value) in onChange event. Easier to follow and re-use too.
Custom react hook is nothing more than a simple JS function that exposes certain functionality and maintains state. This keeps the code clean

import {useState} from 'react'

export const useFormState = initialValues => {
  let [formState, setFormState] = useState(initialValues);
  return [
    formState,
    e => {
      setFormState({...formState, [e.target.name]: e.target.value})
    }
  ];
  
}

How do we use it in our component. Easier than you think, import it and a simple invocation

import React from "react";
import {useFormState} from "./useFormState";

const ReusableFormState = () => {
  const formInit = {email: '', password: ''};
  let [simpleForm, handleFormChange] = useFormState(formInit);

  return (
    <div className="row">
      <form className="col s8 offset-m3" onSubmit={(e) => {
        e.preventDefault();
        console.log(simpleForm);
      }}>
        <div className="row">
          <h4>A Reusable useForm state with React Hooks</h4>
          <div className="input-field col s4">
            <input
              placeholder="EMail ID"
              name="email"
              type="email"
              value = {simpleForm.email}
              className="validate"
              onChange = {handleFormChange}
            />
            <label htmlFor="email">EMail</label>
          </div>
          <div className="input-field col s4">
            <input name="password" type="password" className="validate" 
              onChange = {handleFormChange} />
            <label htmlFor="password">Password</label>
          </div>
        </div>
        <div className="row">
        <div className="col s6">
            <button type="submit">Submit</button>
        </div>
        </div>
      </form>
    </div>
  );
};

export default ReusableFormState;



Together with "useReducer" state hook is a great way to write your code

Friday, October 4, 2019

useRef

useRef is a way to grab a handle of a DOM object and manipulate it for various purposes.

There are many uses for this hook. All the way from validation, styling to more trivial things like set focus on controls, gather data from a form....etc. Let's start with a handle to a control and set focus to it when page loads.

import React, {useRef, useState, useEffect} from 'react'

const Login = () => {
    const emailInputRef = useRef();
    const [loginForm, setLoginForm] = useState({email:'', password: ''});
    useEffect(() => {
      emailInputRef.current.focus();
    }, [])
    return (
        <div
      className="row flex-row justify-content-center align-items-center"
      style={{ marginTop: "80px", marginBottom: "30px" }}
    >
      <div className="col-lg-4 ">
        <div className="bs-component">
          <form onSubmit={(e) => { e.preventDefault(); console.log(loginForm); }}>
            <fieldset>
              <legend>Player Login</legend>
              <div className="form-group">
                <label htmlFor="exampleInputEmail1">Email address</label>
                <input
                  type="email"
                  className="form-control"
                  id="exampleInputEmail1"
                  aria-describedby="emailHelp"
                  placeholder="Enter email"
                  ref={emailInputRef}
                  value={loginForm.email}
                  onChange={e=>{
                    setLoginForm({email: e.target.value});
                  }}
                />
                <small id="emailHelp" className="form-text text-muted">
                  EMail ID you registerd with
                </small>
              </div>
              <button type="submit" className="btn btn-primary">
                Submit
              </button>
            </fieldset>
          </form>
        </div>
      </div>
    </div>
    )
}

export default Login;

This can be extended more to other cases like say for example, we want to extract  data from inputref, we can do that easily

const onSubmit = (e) => {
  e.preventDefault();
  const email = emailInputRef.current.value
  const password = passwordINputRef.current.value
  // Now submit data to login
  // Redirect user
}

There are few other simple cases useRef will be super helpful for things like Scroll-Spy, tabs etc

Wednesday, October 2, 2019

useEffect

useEffect

This hook interests people connected to life cycle events. For people used to class component's, useHook is the answer.

From the example below, we see how we can do something like componentDidUpdate & componentDidUnload. The way we use this hook is pass 2 parameters. First parameter is a callback function and second parameter is dependency array/parameter and a very important one.

useEffect(() => {
    console.log('Component loaded');
    return () => {
      console.log('Component unloaded');
    };
  }, []);

There are 3 key functionalities to this second parameter

  • If we don't pass an empty array, this function will be called for every render
  • If we pass an empty array, this function will be called when the component is loaded
  • If we pass one or more parameters to this array, this function will be called, for every change to those parameters
Here's an example of a simple component with this hook. All that it does is prints current time on screen


import React, { useState, useEffect } from "react";
import GitInfo from "./gitinfo";
import RedditSearch from "./reddit";

function App() {
  const [time, setTime] = useState(null);

  useEffect(() => {
    let handle = setInterval(() => {
      setTime(Date);
    }, 1000);

    return () => {
      clearInterval(handle);
    };
  });

  return (
    <>
      <div className="container">
        <h2>React Hooks - useEffect</h2>
        <main role="main" className="container">
          <h1 className="mt-5">Sticky footer with fixed navbar</h1>
          <p className="lead">
            {time}
            <br />
        </main>
        <hr />        
      </div>
      <footer className="footer">
        <div className="container">
          <span className="text-muted">Place sticky footer content here.</span>
        </div>
      </footer>
    </>
  );
}

export default App;



Let's take this a step further to create an event, say a mousemove event.


import React, { useState, useEffect } from "react";
import GitInfo from "./gitinfo";
import RedditSearch from "./reddit";

const initXY = {
  x: null,
  y: null
};
function App() {
  const [time, setTime] = useState(null);
  const [xy, setXY] = useState(initXY);

  useEffect(() => {
    let handle = setInterval(() => {
      setTime(Date);
    }, 1000);

    return () => {
      clearInterval(handle);
    };
  });

  const mouseMoveHandle = e => {
    setXY({
      x: e.clientX,
      y: e.clientY
    });
  };

  useEffect(() => {
    window.addEventListener("mousemove", mouseMoveHandle);
    console.log('Event added back');
    return () => {
      window.removeEventListener("mousemove", mouseMoveHandle);
    };
  }, []);

  const disableMouseMove = (e) => {
    e.preventDefault();
    console.log('Button event invoked');
    window.removeEventListener("mousemove", mouseMoveHandle);
  }

  return (
    <>
      <div className="container">
        <h2>React Hooks - useEffect</h2>
        <main role="main" className="container">
          <h1 className="mt-5">Sticky footer with fixed navbar</h1>
          <p className="lead">
            {time}
            <br />
            Mouse move event<br />
            `x: ${xy.x}, y: ${xy.y}`
          </p>
          <button className="btn" onClick={disableMouseMove}>Disable mouse move</button>
        </main>
        <hr />        
      </div>
      <footer className="footer">
        <div className="container">
          <span className="text-muted">Place sticky footer content here.</span>
        </div>
      </footer>
    </>
  );
}

export default App;



Typical example for useEffect in the contemporary is when a form is loaded, we need to have control focus to be on the right field, for user to start editing. Together with useRef hook we can easily achieve that. Further more, when there are multiple fields involved, we can enable/disable buttons, add validations through useEffect.

In addition to form handling one other place useEffect comes handy is to invoke remote web services



import React, {useEffect, useState} from 'react'
import JSONPretty from 'react-json-pretty';

const GitInfo = () => {

  const [profile, setProfile] = useState(null);
  const getGitProfile = async () => {
    const response = await fetch("https://api.github.com/users/krishnansriram")
    const json = await response.json();
    console.log(json);
    setProfile(json)
  }
  useEffect(() => {
    getGitProfile()
    console.log('Received git profile');
  }, []);
  
  return (
    <div className="container">
      <h4>GIT profile</h4>
      <JSONPretty id="json-pretty" data={profile}></JSONPretty>
    </div>
  )
}

export default GitInfo;

As I stated earlier some of the real-world concepts include enable/disable button based on input values from other fields. In the past we used to write validation functions all over the place and invoke them, just to find out issues and no central place to handle it etc. As you go through more samples from the BLOG, you'll see what I mean. Back to this case, here's a sample

import React, {useState, useRef, useEffect} from "react";
import { useDispatch } from "react-redux";
import uuid from "uuid/v4";

const TodoInput = () => {
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const addButtonRef = useRef();
  const txtName = useRef();
  useEffect(() => {
    txtName.current.focus();
  }, [null])
  
  useEffect(() => {
    console.log('Enable/Disable Add button')
    if(name.length > 5 && description.length > 5) {
      addButtonRef.current.removeAttribute("disabled");
      console.log('Expect button to be enabled, from NOW');
    } else {
      addButtonRef.current.setAttribute("disabled", true);
    }
  }, [name, description])

  const onAdd = (e) => {
    e.preventDefault();
    // Invoke your API here
  }
  return (
    <div>
    <form>
    <div className="form-row">
      <div className="col-3">
        <input type="text" className="form-control" ref={txtName} placeholder="Name" onChange={(e) => setName(e.target.value)} />
      </div>
      <div className="col-7">
        <input type="text" className="form-control" placeholder="Description" onChange={(e) => setDescription(e.target.value)} />
      </div>
      <div className="col">
        <button className="btn btn-primary" ref={addButtonRef} onClick={onAdd}>Add</button>
      </div>
    </div>
  </form>
    </div>
  );
};

export default TodoInput;

We take ref to input components and track them as user types in. Validates is then and then and enable "Add" button. Much simpler than traditional approach.....

We are not restricted with just UI stuff alone. We can build a custom hook to do remote fetch. Say, you need to load profile from GitHub into one of the component/page. Here's how useEffect can help. Later when we look at context and redux examples, you'll see how much this helps's not only at component level, but at app level. With PWA's knocking our door, this functionality can be super helpful, even if it does not make much sense now.

import {useEffect, useState} from 'react';

const useFetch = (url) => {
  const [profile, setProfile] = useState(null);

  const getGitProfile = async (url) => {
    const response = await fetch(url)
    const json = await response.json();
    console.log(json);
    setProfile(json)
  }
  
  
  useEffect(() => {
    getGitProfile(url)
    console.log('Received git profile');
  },[url]);

  return {profile}
}

export default useFetch;

Using above code in our application is lot more easier and cleaner too!

import React from 'react'
import JSONPretty from 'react-json-pretty';
import useFetch from './useFetch';

const GitInfo = () => {
  const fetchProfile = useFetch("https://api.github.com/users/krishnansriram");
  
  return (
    <div className="container">
      <h4>GIT profile</h4>
      <JSONPretty id="json-pretty" data={fetchProfile.profile}></JSONPretty>
    </div>
  )
}

export default GitInfo;