PKJ.no

Petter Kjelkenes, a blog about technical stuff.

Global state with standard react hooks

Posted on 06. June, 2019

Today we will take a look at how we can implement simple global state using useContext and useReducer hooks.

We don't need redux to implement global state. That said, redux has some nice features that we wont have using the method i am about to show.

If you want to follow along use npx create-react-app.

We will start by creating a HoC withGlobalContext and a hook useGlobalState. withGlobalContext is supposed to be called once, and preferably on the top component. useGlobalState is a hook that we can use anywhere on all of our components.

src/useGlobalState.js

import React, { useContext, useReducer } from 'react';

const GlobalStateContext = React.createContext();

export function withGlobalContext(WrappedComponent, initialStateFunc = () => {}, reducer) {
  return (props) => {
    const [state, dispatch] = useReducer(reducer, initialStateFunc());
    return (
      <GlobalStateContext.Provider value={[state, dispatch]}>
        <WrappedComponent {...props} />
      </GlobalStateContext.Provider>
    )
  }
}

export default function useGlobalState() {
  return useContext(GlobalStateContext);
}

This is the magic of hooks! Here we have created two functions

  • A HoC: withGlobalContext
    Which we will use on the App component.
  • A hook: useGlobalState
    Which is just a wrapper around useContext.

Lets create an App component and also 3 components that uses global state.

src/App.js

import React, { useState } from 'react';
import { withGlobalContext } from './useGlobalState';
import CounterComponent from './components/CounterComponent';
import MyNameComponent from './components/MyNameComponent';
import AsyncComponent from './components/AsyncComponent';

function App() {
  const [showAsyncComponent, setShowAsyncComponent] = useState(true);

  return (
    <div>
      <CounterComponent />
      <MyNameComponent />
      <button onClick={() => setShowAsyncComponent(!showAsyncComponent)}>Toggle async component</button>
      {showAsyncComponent && (
        <AsyncComponent />
      )}
    </div>
  );
}

function reducer(state, action) {
  switch (action.type) {
    case 'load-apis-fail':
      return {
        ...state,
        apis: [],
        apisIsLoading: false,
      };
    case 'load-apis':
      return {
        ...state,
        apis: [],
        apisIsLoading: true,
      };
    case 'set-apis':
      return {
        ...state,
        apis: action.payload,
        apisIsLoading: false,
        apisIsAlreadyLoaded: true,
      };
    case 'set-name':
      return {
        ...state,
        name: action.payload,
      };
    case 'clear-name':
      return {
        ...state,
        name: '',
      };
    case 'increment':
      return {
        ...state,
        counter: state.counter + 1
      };
    case 'decrement':
      return {
        ...state,
        counter: state.counter - 1
      };
    default:
      throw new Error();
  }
}

export default withGlobalContext(App, () => {
  // Can be dynamic setup logic here..
  return {
    counter: 0,
    name: '',
    apis: [],
    apisIsLoading: false,
    apisIsAlreadyLoaded: false,
  }
}, reducer);

We use our HoC withGlobalContext.

Here we send:

  • The default global state.
  • A reducer function for the global state.

src/actions/index.js

export const setNameAction = (name) => ({type: 'set-name', payload: name});
export const clearNameAction = () => ({type: 'clear-name'});

export const setApis = (payload) => ({type: 'set-apis', payload});

export const loadApis = () => ({type: 'load-apis'});

export const loadApisFail = () => ({type: 'load-apis-fail'});

We will be using these actions through the test components we will create.

src/components/CounterComponent.js

import React from 'react';
import useGlobalState from '../useGlobalState';

const CounterComponent = () => {
  const [globalState, dispatch] = useGlobalState();

  return (
    <h2 style={{display: 'flex'}}>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <div style={{width: '30px'}}>
        {globalState.counter}
      </div>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </h2>
  )
}

export default CounterComponent;

The counter component uses our custom hook "useGlobalState". This hook returns the globalState object and a dispatch method so that we can send actions to change the global state.

src/components/MyNameComponent.js

import React, { useState } from 'react';
import useGlobalState from '../useGlobalState';
import { setNameAction, clearNameAction } from '../actions';

const MyNameComponent = () => {
  const [globalState, dispatch] = useGlobalState(); // global state.
  const [name, setName] = useState(''); // local state name variable

  return (
    <div>
      <h3>My name is {globalState.name}</h3>
      <p>
        <input type="text" value={name} onChange={e => setName(e.currentTarget.value)} />
        <button onClick={() => dispatch(setNameAction(name))}>
          Set name
        </button>
        <button onClick={() => dispatch(clearNameAction())}>
          Clear name
        </button>
      </p>
    </div>
  )
}

export default MyNameComponent;

MyNameComponent also uses global state. This component has the ability to update the name and clear the name of the global state.

src/components/AsyncComponent.js

import React, { useEffect } from 'react';
import useGlobalState from '../useGlobalState';
import axios from 'axios';
import {loadApis, loadApisFail, setApis} from '../actions';

const AsyncComponent = () => {
  const [globalState, dispatch] = useGlobalState();
  const apis = globalState.apis;
  const isLoading = globalState.apisIsLoading;

  useEffect(() => {
    if (globalState.apisIsAlreadyLoaded) {
      return;
    }
    dispatch(loadApis());
    axios.get('https://api.met.no/weatherapi/available.json').then((response) => {
      setTimeout(() => {
        dispatch(setApis(Object.values(response.data)));
      }, 500);
    }).catch(() => {
      dispatch(loadApisFail());
    })
  }, [dispatch, globalState.apisIsAlreadyLoaded]);

  console.log(globalState);
  return (
    <div>
      {isLoading && (
        <p>
          Loading forecast api list..
        </p>
      )}
      {!isLoading && (
        <ul>
          {apis.map(api => (
            <li key={api.name}>{api.name}</li>
          ))}
        </ul>
      )}
    </div>
  )
}

export default AsyncComponent;

AsyncComponent uses the global state. It fetches some values from a API then dispatches actions.

PHP, the good and the bad

Posted on 27. September, 2018

PHP is probably amongst the most hated programming languages around the world. Some people love it, some people say its a toy language, some people hate it with a passion.

PHP has grown a lot within the last couple of years. PHP now supports proper OOP, and have concepts like Classes, Interfaces, Traits, Closures / lamdas, Generators, Strict types, and more.

This article will be a list of what I think is good, and what I dont like about PHP.

The good

  • PHP is really easy to get started with. Put a script on a web server and run it. This can also be bad.
  • You dont have to learn basic concepts like how the http protocol works, just create a script and start echoing stuff out. This can also be bad.
  • PHP 7+ is really fast, it beats the contenders such as Ruby and Python.
  • PHP has composer which can be compared to gem, pip and npm. Composer was probably the best thing that happened with PHP.
  • PHP has plenty of well tested blogging systems and cms systems. E.g. Wordpress, Drupal, Joomla and eZ Publish. This alone is huge, and might be a big reason that PHP is still alive.
  • PHP has great libraries for doing advanced integrations and large legacy systems.
  • Creating complex libraries is great because PHP supports almost full type safety in classes.
  • Well tested frameworks used in production for years: Symfony and Laravel.
  • PHP Documentation is readable and indexed greatly by google. Just google that function and you are good.
  • PSR. PHP Standards Recommendation. This project is great, it means that libraries written can work with each other. Also PSR consist of a coding standards guide, which leads to better code.

What i personally dont like

Global function mess.

PHP's mess around global functions can be frustrating.

<?php
$array = [1,2,3];
// Can just this:
$array = array_merge($array, [4]);
// Be this?
$array->merge([4]);

Dollar signs $ :(

Dollar sign in front of variables shouldn't be necessary. I would rather use literals to concatenate.

Security.

Because PHP is so simple by default, it doesn't have good defaults for security such as XSS injections.

<?php 

$opsIForgotToEscapeThisVariable = $_GET['xss-attacks-are-welcome-here'];
echo "Hello $opsIForgotToEscapeThisVariable";

Too easy to learn.

I mean it. Its way too easy. PHP is so easy to learn, which leads to really bad code written by junior developers which just started learning to code.

I did not sign up for Java

I dont like Java. Actually I dont like Java at all. PHP is starting to feel more like Java. This can be great, but often leads to overly complex code. E.g. use a simple array instead of creating 10 value object classes that implements each of its own interface?

Legacy code

When working with PHP projects you would often work with legacy code. This legacy code was e.g. written by really bad programmers or was written for PHP 4 with no OOP at all. Working with such code makes you feel like a sad -sad Panda. Often these systems are also full of bugs.

Legacy code can be true for other programming languages as well. But I solely think PHP has the worst legacy code. PHP 4 was really bad and the eco system was not there.

PHP 7.4, typed properties RFC

Posted on 19. September, 2018

There is an ongoing vote process for typed properties. Typed properties would be really good for PHP, as PHP has already gone on the way to be a strict types language.

Edit:

The RFC has been accepted, and PHP 7.4 will now include typed properties.

The benefits of the new typed properties:

  • Relying on dockblocks for code completion in IDE's.
  • Runtime errors if wrong types are used.
  • Future performance improvements can be done internally in PHP.
  • Less type checking code, meaning cleaner code.

This is an example of how you can define typed properties, in reference to the RFC:

Less code

First of, the obvious. There are no longer needs for types in dockblocks and getters / setters to ensure type safety. This alone is a huge deal. Today, per property there are at least 21 lines of code that can be removed. This given the PSR coding standards.

<?php

class Example {
  /**
   * @var string
   */
  private $title;
  
  /**
   * @var string $title
   */
  public function setTitle(string $title)
  {
      $this->title = $title;
  }
  
  /**
   * @return string
   */
  public function getTitle()
  {
      return $this->title;
  }
}

Which can be converted to 3 lines of code.

<?php

class Example {
  public string $title;
}

Sometimes you would need a getter / setter in cases where the contents of the variable mutates to something else. Still the dockblock would not be needed.

Hello

My name is Petter Kjelkenes. I live in a small country named Norway.

I am a full-stack developer, mostly using tech like: PHP, Symfony, Node.js, React and CMS tech like eZ Platform + sanity.

Contact me

Petter Kjelkenes

(+47) 482 913 65