React Eas­ier doc­u­men­ta­tion

 react-easier

– hooks that make React a lot easier

react-easier

React Eas­ier doc­u­men­ta­tion

Install react-easier

In a React pro­ject, prefer­able ini­tial­ized with Vite, run:

npm install react-easier

Now you're ready to use all the hooks pro­vided by react-eas­ier, that makes React much eas­ier to use when it comes to states (within and across com­po­nents), forms, fetch­ing data and han­dling keys in lists.

Note:

When being installed react-easier will try to make a minor change to vite.config.js.

This change makes Vite treat js-files as jsx-files, which is needed for the hook useDe­bug to work in an opti­mal way when it logs state changes hap­pen­ing out­side Com­po­nents (in your cus­tom hooks, util­i­ties etc).

If you use git/ver­sion con­trol you will see that vite.con­fig.js has been mod­i­fied. Com­mit the change to your repos­i­tory!

React Eas­ier doc­u­men­ta­tion

useStates – in one component

The hook useS­tates is an alter­na­tive to React:s built in hook useS­tate. It sim­pli­fies state han­dling by mak­ing React lis­ten to direct changes to state vari­ables, and elim­i­nates the need of set­ter func­tions. A com­par­i­son with iden­ti­cal func­tion­al­ity:

react – useState

import { useState } from 'react';

export default function Component() {
  const [counter1, setCounter1] = useState(1);
  const [counter2, setCounter2] = useState(42);

  return <>
    <button
      onClick={() => setCounter1(counter1 + 1)}>
      Counter 1: {counter1}
    </button>
    <button
      onClick={() => setCounter2(counter2 + 1)}>
      Counter 2: {counter2}
    </button>
  </>;
}

react-easier – useStates

import { useStates } from 'react-easier';

export default function Component() {
  const s = useStates({
    counter1: 1, counter2: 42
  });

  return <>
    <button onClick={() => s.counter1++}>
      Counter 1: {s.counter1}
    </button>
    <button onClick={() => s.counter2++}>
      Counter 2: {s.counter2}
    </button>
  </>;
}

React Eas­ier doc­u­men­ta­tion

useStates – across components

When you want to share states across com­po­nents in vanilla React you can use cre­ate­Con­text and com­bine it with useC­on­text and useS­tate to achieve this, with some fairly com­plex boiler plate code.

With useS­tates it becomes much sim­pler to share states across com­po­nents. You name and set the ini­tial val­ues of a state in a par­ent com­po­nent and retrieve it by name in any descen­dant com­po­nent:

In a parent component:

import { useStates } from 'react-easier';
import SubComponent from './SubComponent';

export default function App() {

  const g = useStates('globalState', {
    counter1: 1,
    counter2: 42
  });

  return <>
    <p>Counter 1 value: {g.counter1}</p>
    <p>Counter 2 value: {g.counter2}</p>
    <SubComponent />
  </>;
}

In any descedant component:

import { useStates } from 'react-easier';

export default function SubComponent() {

  const g = useStates('globalState');

  return <>
    <button onClick={() => g.counter1++}>
      Counter 1: {g.counter1}
    </button>
    <button onClick={() => g.counter2++}>
      Counter 2: {g.counter2}
    </button>
  </>;
}

Counter 1 value: 1

Counter 2 value: 42

Note: You can create as many named states as you want – and initialize them at any level in your component hierarchy!

React Eas­ier doc­u­men­ta­tion

useStates and form handling

Controlled inputs and state variables

Forms are made up by a num­ber of inputs (input ele­ments – input type="text", input type="num­ber", input type="radio" etc. – as well as textarea ele­ments, select ele­ments and so forth).

In order to con­nect an input ele­ment to a state vari­able React uses a con­cept called con­trolled inputs.

Make sure you famil­iar­ize your­self with this con­cept before read­ing any fur­ther, but in short: A con­trolled input is 'two-way-bound' to a state vari­able. When the user makes a change to the value of the input ele­ment the state vari­able changes. And vice versa: When you pro­gram­mat­i­cally change the value of the state vari­able the input value changes.

So what's the problem?

Con­trolled inputs are accom­plished using an unhealth­ily large amount of boiler plate code in vanilla React. In com­par­i­son react-eas­ier makes things much eas­ier...

Enter your name

Try out the exam­ple above, then go look at the exam­ple code:

React Eas­ier doc­u­men­ta­tion

useStates and form handling – an example

Every object you cre­ate by using the useS­tates hook gets a bind method. Call bind with the name of a state vari­able as your argu­ment – see the exam­ple below. The name, value and onchange han­dler of the input will then be set auto­mat­i­cally:

react-easier: controlled inputs

import { useStates } from 'react-easier';
export default function Component() {
  const s = useStates({ firstName: '', lastName: '' });
  return <>
    <h3>{s.firstName || s.lastName ?
      s.firstName + ' ' + s.lastName : 'Enter your name'}</h3>
    <form>
      <label>First name: 
        <input type="text" {...s.bind('firstName')} />
      </label>
      <label>Last name: 
        <input type="text" {...s.bind('lastName')} />
      </label>
    </form>
  </>;
}

vanilla react: equivalent code

import { useState } from 'react';
export default function Component() {
  const [firstName, setFirstName] = useState('');
  const[lastName, setLastname] = useState('');
  return <>
    <h3>{firstName || lastName ?
      firstName + ' ' + lastName : 'Enter your name'}</h3>
    <form>
      <label>First name: 
        <input
          type="text" name="firstName" 
          value={firstName}
          onChange={e => setFirstName(e.target.value)}
        />
      </label>
      <label>Last name: 
        <input
          type="text" name="lasttName"
          value={lastName}
          onChange={e => setLastname(e.target.value)}
        />
      </label>
    </form>
  </>;
}

React Eas­ier doc­u­men­ta­tion

useStates and form handling – more info

How to use with selects

This is how you bind to select ele­ments:

return <>
  <select {...s.bind('favoriteColor')}>
    <option>Red</option>
    <option>Green</option>
    <option>Blue</option>
  </select>
</>;

React Eas­ier doc­u­men­ta­tion

useStates and form handling – more info

How to use with radio buttons

Group radio but­tons together by bind­ing them to the same state vari­able, then pro­vide the value of the spe­cific radio but­ton as a sec­ond argu­ment to bind:

return <>
  <label>Red
    <input type="radio" {...s.bind('favoriteColor', 'Red')} />
  </label>
  <label>Green
    <input type="radio" {...s.bind('favoriteColor', 'Green')} />
  </label>
  <label>Blue
    <input type="radio" {...s.bind('favoriteColor', 'Blue')} />
  </label>
</>;

React Eas­ier doc­u­men­ta­tion

useStates and form handling – more info

How to use with checkboxes

You bind to check­boxes by using three argu­ments together with bind:

  • the state name of the state vari­able
  • the value cor­re­spond­ing to the box being checked
  • the value cor­re­spond­ing to the box being unchecked
return <>
  <label>Are you cool?
    <input type="checkbox" {...s.bind('isCool', true, false)} />
  </label>
</>

React Eas­ier doc­u­men­ta­tion

useStates and form handling – more info

Bind to sub-objects in the state

If you have 'sub-ob­jects' in your state you can bind to them as well. This can be con­ve­nient if you want to keep the input of sev­eral dif­fer­ent forms nicely orga­nized/name­spaced in a state object.

const g = useStates('globalState', {
  loginForm: {email: '', password: ''},
  regForm: {email:'', password: '', passwordDoubleCheck: ''},
  ...
});
return <>
  <input type="email" {...g.loginForm.bind('email')}>
  etc...
</>;

React Eas­ier doc­u­men­ta­tion

useStates and form handling – more info

Limitations of the bind method

The bind method of useS­tates works fine with most com­mon types of input ele­ments.

For now it doesn't sup­port multi-choice selects and a few other less com­monly used input ele­ments. These can be 'bound' – made into con­trolled inputs – using stan­dard React syn­tax.

React Eas­ier doc­u­men­ta­tion

useAutoKeys

React requires you to set a key for each jsx ele­ment / array item in a list. And for a good rea­son: This makes the ren­der­ing of lists faster and more effi­cient.

The hook use­Au­toKeys auto­mat­i­cally sets this key, so you don't have to. Only call it once in your appli­ca­tion, at the top of your App/top-most com­po­nent:

// In your App/top-most component (and only here - once for a whole project)
import {useAutoKeys} from 'react-easier'; 

export default function App(){
  useAutoKeys();
  // ...other stuff...
}

React Eas­ier doc­u­men­ta­tion

useAutoKeys – an example

import {useAutoKeys} from 'react-easier';

export default function App() {

  useAutoKeys(); // only needed once in your app

  const people = [
    { id: 1, name: 'Anna' }, 
    { id: 2, name: 'Boris' },
    { id: 5, name: 'Cecilia' }, 
    { id: 8, name: 'David' }
  ];

  return <>
    {people.map(({name}) => 
      <p>{name}</p> 
    )}
  </>;
}

With use­Au­toKeys every­thing is fine - React will auto­mat­i­cally use the id for each per­son as a key for each p-tag. You will see a list of peo­ple:

Anna
Boris
Cecilia
David

With­out use­Au­toKeys this code would still run, but because no keys would have been set, you would get the fol­low­ing warn­ing:

Warning:
Each child in a list should have a unique "key" prop.

Learn more: Options for use­Au­toKeys

React Eas­ier doc­u­men­ta­tion

useAutoKeys – options

You can call use­Au­toKeys with­out any argu­ments.

But there are two optional argu­ments that let you cus­tomize its behav­ior:

useAutoKeys(possibleKeys, useIndexIfNoMatchingKey);

possibleKeys

Valid values:    An array of strings and/or regular expressions.
Default value:  ['_id', 'id', /.*Id$/, /.*_id$/]

  • The default val­ues checks if the items has one of the prop­er­ties _id (Mon­goDB etc.), id, someTableId or some_table_id (SQL) and uses that prop­erty as key.
  • Defaults should be fine as long as the pri­mary id field comes before any for­eign key id field.

useIndexIfNoMatchingKey

Valid values:    true, false
Default value:  true   (use array indexes as keys, if the items lack id:s)

React Eas­ier doc­u­men­ta­tion

useFetch

If you want to fetch data from an API in vanilla React, a com­mon way to do so is inside a use­Ef­fect hook. Before you're done there will be quite a few lines of boil­er-plate code to write, unless you use a cus­tom hook. The use­Fetch hook is sim­ple to use for fetch­ing, and tai­lored to be used together with useS­tates:

Fetching data with the useFetch hook

import { useStates, useAutoKeys, useFetch } 
  from 'react-easier';

export default function App() {

  useAutoKeys(); // only needed once in your app

  const s = useStates({
    people: useFetch('/people.json')
  });
  
  return <>
    <h3>People</h3>
    {s.people.map(({ fName, lName, email }) =>
       <p>{fName} {lName} ({email})</p>)}
  </>;
}

The data being fetched

[{
  "id": 1, "email": "ncroad0@ucoz.com",
  "fName": "Neilla", "lName": "Croad"
}, {
  "id": 2, "email": "lbernhardsson1@arizona.edu",
  "fName": "Lucila", "lName": "Bernhardsson"
}, {
  "id": 3, "email": "codea2@clickbank.net",
  "fName": "Christiano", "lName": "O'Dea",
}]

People

Neilla Croad (ncroad0@ucoz.com)

Lucila Bern­hards­son (lbern­hards­son1@ari­zona.edu)

Chris­tiano O'Dea (codea2@click­bank.net)

React Eas­ier doc­u­men­ta­tion

useFetch – options

There are two optional argu­ments you can send to use­Fetch, type and fetchOp­tions:

useFetch(url, type, fetchOptions);
useFetch(url, fetchOptions); // type not needed if 'json'

type

The type argu­ment is a string with the valid val­ues json, text, blob, clone, for­m­Data, array­Buffer that con­trols how the raw response data will be inter­preted/unpacked.

If you don't spec­ify type it will default to 'json'.

fetchOptions

Options is an object con­tain­ing the stan­dard request options for fetch, see MDN - Sup­ply­ing request options. You can use it to add extra head­ers, send a POST request with a request body etc.

Apart from the stan­dard request options you can also sup­ply an extra prop­erty/option to use­Fetch, post­Pro­cess­ing that will allow you to post­process fetched data. Learn more about post­pro­cess­ing.

React Eas­ier doc­u­men­ta­tion

useFetch – postprocessing

Apart from the stan­dard request options you can also sup­ply an extra prop­erty/option to use­Fetch, post­Process, that will allow you to post­process fetched data.

The func­tion recieves the result of the fetch and you can post­process it any way you like as long as you return an array.

The post­Process option is use­ful if you want to fil­ter your data directly after fetch­ing it or if you are fetch­ing data that is wrapped in an object/array struc­ture that you don't care about pre­serv­ing.

Example 1 – filtering the data

// Remove admins from result set
useFetch('/api/users', {
  postProcess: users => users.filter(
    user => user.role !== 'admin'
  )
})

Example 2 – picking out a property

// Pick the data array from a
// result set that is an object

useFetch('/api/users', {
  postProcess: result => result.data
})

Note: The ini­tial value returned from use­Fetch is always an empty array. This plays nicely with jsx. As soon as the data is fetched – and post­processed, if you choose to do so – the array will fill up with items.

React Eas­ier doc­u­men­ta­tion

useDebug – log state changes

The hook useDe­bug allows for detailed debug­ging of all changes to states.

Only call it once in your appli­ca­tion, at the top of your App/top-most com­po­nent:

// In your App/top-most component 
// (and only here - once for a whole project)

import {useDebug} from 'react-easier'; 

export default function App(){
  useDebug();
  // ...other stuff...
}

example of debug output

The debug log shown above (from the browser con­sole) is the out­put from useDe­bug, when we run our small use­Fetch exam­ple. Every action that changes any state cre­ated with useS­tates is logged, along with exten­sive info, includ­ing on which line, and in which file, the state change occured.

Note: When you con­sole.log a state kept by useS­tates (or any object or array from that state) you will see that it is actu­ally a javascript proxy object, to con­sole.log the 'raw' object - sim­ply add '._' after it.

React Eas­ier doc­u­men­ta­tion

useOnMount and useOnCleanup

The hooks useOn­Mount and useOn­Cleanup are just a more seman­tic alter­na­tive to using React's built in use­Ef­fect hook, in order to do some­thing when a com­po­nent mounts and clean­ing up the effects of your actions when the com­po­nent unmounts.

In the exam­ple below we want to add a class to the body ele­ment when our com­po­nent mounts and we want to clean up, by remov­ing the same class, when the com­po­nent unmounts. Both code snip­pets accom­plish this – use whichever syn­tax you pre­fer!

Vanilla React with useEffect

import { useEffect } 
  from "react";

export default function RegPage() {

  const bodyClasses = document.body.classList;

  useEffect(x => {
    bodyClasses.add('on-reg-page');
    return () => bodyClasses.remove('on-reg-page');
  }, []);

  return <>Some output...</>;
}

With useOnMount and useOnCleanup

import { useOnMount, useOnCleanup } 
  from "react-easier";

export default function RegPage() {

  const bodyClasses = document.body.classList;

  useOnMount(() => bodyClassses.add('on-reg-page'));

  useOnCleanup(() => bodyClasses.remove('on-reg-page'));

  return <>Some output...</>;
}
Note: useOnMount and useOnCleanup accepts async functions. useEffect does not.

React Eas­ier doc­u­men­ta­tion

Terms of use, MIT License

Copy­right © 2023, Thomas Frank ('iron­boy')

Acknowl­edg­ments: Logo cre­ated by Muham­mad Usman, Vecteezy.com.

Per­mis­sion is hereby granted, free of charge, to any per­son obtain­ing a copy of this soft­ware and asso­ci­ated doc­u­men­ta­tion files (the “Soft­ware”), to deal in the Soft­ware with­out restric­tion, includ­ing with­out lim­i­ta­tion the rights to use, copy, mod­ify, merge, pub­lish, dis­trib­ute, sub­li­cense, and/or sell copies of the Soft­ware, and to per­mit per­sons to whom the Soft­ware is fur­nished to do so, sub­ject to the fol­low­ing con­di­tions:

The above copy­right notice and this per­mis­sion notice shall be included in all copies or sub­stan­tial por­tions of the Soft­ware.

THE SOFT­WARE IS PRO­VIDED “AS IS”, WITH­OUT WAR­RANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUD­ING BUT NOT LIM­ITED TO THE WAR­RANTIES OF MER­CHANTABIL­ITY, FIT­NESS FOR A PAR­TIC­U­LAR PUR­POSE AND NON­IN­FRINGE­MENT. IN NO EVENT SHALL THE AUTHORS OR COPY­RIGHT HOLD­ERS BE LIABLE FOR ANY CLAIM, DAM­AGES OR OTHER LIA­BIL­ITY, WHETHER IN AN ACTION OF CON­TRACT, TORT OR OTH­ER­WISE, ARIS­ING FROM, OUT OF OR IN CON­NEC­TION WITH THE SOFT­WARE OR THE USE OR OTHER DEAL­INGS IN THE SOFT­WARE.