Basic React Hooks explained

React Hooks

What are React Hooks?

The preferred way to create components in React nowadays are functional components.

React Component often have an inner state. They show an image or a text or other UI elements depending on user actions. User actions like clicking a button can change the inner state of a component.

In the early days using "evil" 🙃 class components it was easy to hold the state within the component.
You had a "state" class property and a "setState" class method to change the state.


import React,{Component} from 'react';
 
class App extends Component {
  constructor(){
    super()    
    // Here I set sth. in my state
    this.state={
      text : 'Welcome to Barbarian Developer'
    }
  }
 
  private sayGoodbye(){
    // here I modify my component state
    this.setState({
      text:'Bye Bye Earthling'
    })
  }
  render() {
      return (
          <div>
            <h1>{this.state.text}</h1>
            <button onclick="{()"> this.sayGoodbye()}>
              Say Goodbye
            </button>
          </div>
      ); 
  }
}

With functional components React needs a new way to hold the state of a component.
TADA: React Hooks are born.

But React Hooks are more than this. There is a large huge machinery under the hood. In this post we will only look at these 3 basic hooks:

useState - Godfather of the hooks

  • Probably the most common hook
  • You can set and hold the current state of the component
    • When component re-renders, the value of the hook does not change
    • When the value of the hook changes, the component is re-rendered.
  • useState returns an array
    • first item is the value of the state
    • second item is a method to set the state
    • const [value, setValue] = useState(initialValue)
  • Below you have a component which shows a message.
    • The message text is the state of the component
    • When the message text changes the component is re-rendered
Example: 
export function MessageComponent() {
  const [text, setText] = useState('Welcome to Barbarian Developer')

  return  (<>
  <h1>{ text }</h1>
  <button onClick={() => setText('Bye bye earthling')}>Say Goodbye</button>
  </>);
}

useEffect - The hidden worker

  • Let you perform side effects on the component without blocking the rendering
  • Side effects like:
    • Fetching data from an external source
    • Manipulating the DOM when component loads
    • Setting up a connection to a database
    • etc.
  • The useEffect hook is called on every render unless you specify the second argument which tells the hook only to render when the given values change
    • useEffect(() =>{})  ➡️ The inner function is always called
    • useEffect(() =>{}, [])  ➡️ The inner function is called once
    • useEffect(() =>{}, [someVar1, someVar2])  ➡️ The inner function is called when someVar1 or someVar2 changes.
  • The following example displays chuck norris jokes
    • The jokes are fetched within useEffect
    • When fetching is successful the text is set
    • You will see a console output because useEffect is called when the component loads for the first time
    • When you click "Say Goodbye" you will notice that "Effect called" is not written to the console output.
      • This is because of the second argument of the useEffect hook. It says that the effect is only executed when the value of "counter" changes.
    • When you click "New Joke" the value of the counter variable changes
      • useEffect is triggered and a new joke is loaded
export default function App() {
  const [text, setText] = useState('Welcome to Barbarian Developer')
  const [counter, setCounter] = useState(0);

  useEffect( () =>{
    console.log("Effect Called...")
    const loadRandomJoke = async () => {
      const response = await fetch('https://api.chucknorris.io/jokes/random');
      const json = await response.json();
      setText(json.value);
    }

    loadRandomJoke();
  },[counter])

  return  (<>
  <p>{ text }</p>
  <button onClick={() => setText('Bye bye earthling')}>Say Goodbye</button>
  <button onClick={() => setCounter(c => c + 1)}>New Joke</button>
  </>);
}

useReducer - The big brother of useState

  • Component has a more complex state
    • You are loading data from an external source and e.g. you want to show
      • a loading spinner while loading
      • an error when it fails
      • the items when it succeeds
      • etc.
  • It uses the Flux pattern like Redux
  • To understand useReducer, following concepts need to be known
    • reducer => returns the state
    • action => changes the state
    • dispatcher => triggers the action to change the state
  • useReducer is initialized with a reducer and it returns the state and a dispatcher
  • The following example displays Chuck Norris jokes
    • The component has now 2 state properties
      • loading and text
    • When loading property is set to true
      • it shows a "Loading" text
      • There is 2 second delay when loading the jokes so that you can see the "Loading" text
    • When the joke is loaded 
      • it shows the joke and a "New Joke" button



function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const loadRandomJoke = () => {
    dispatch({type: 'load' });
    setTimeout(async() =>{
      const response = await fetch('https://api.chucknorris.io/jokes/random');
      const json = await response.json();
      dispatch({type: 'setJoke', payload: json.value });
    }, 2000);
  }

  useEffect( () =>{
    loadRandomJoke();
  },[])

  return  (<>
  { state.loading && (<p>loading...</p>)}

  { !state.loading && (<>
    <p>{ state.text }</p>
    <button onClick={() => loadRandomJoke()}>New Joke</button>
  </>)}
  </>);
}

const initialState = {
  loading: true,
}

function reducer(state, action) {
  switch (action.type) {
    case 'load':
      return { ...state, loading: true};
    case 'setJoke':
      return {
        loading: false,
        text: action.payload
      };
  }
}

useRef - State without Rerender

Sometimes you want to store and change data within the component without rerendering the whole component. In such cases you can use useRef.

Lets se an example in which we display the number of times a user clicks the button.

function Counter() {
  let ref = useRef(0); // We start with the value 0

  function handleClick() {
    ref.current = ref.current + 1;
  }
  
  function show() {
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <>
      <button onClick={handleClick}>
        Click me!
      </button>
      <button onClick={show}>
        Show
      </button>
    </>
  );
}

In this case the component is not rerendered when the user clicks the button. There can be use cases when this is useful.

Comments

Popular posts from this blog

What is Base, Local & Remote in Git merge

Asynchronous Nunjucks with Filters and Extensions

Debug Azure Function locally with https on a custom domain