@devgetting/react-init

A React library with easier configuration  🚀


Keywords
react, babel, library, typescript
License
ISC
Install
npm install @devgetting/react-init@3.0.4

Documentation

@devgetting/react-init

A React library with easier configuration  🚀

Basic installation

We need to have already installed these libraries with correct versions.

Libraries

These libraries are required to start with React development (obviously 😜).

About Create React App CLI

This library is not supported into CRA projects, due decorator structure is an experimental proposal and it may change in the future. But don't worry, if this change in the future, we'll fix it 🥲.

If you want to see more about why CRA is not accepting this structure, see Can I Use Decorators?

Installing @devgetting/react-init via npm

  npm install @devgetting/react-init

Usage/Examples

Initializing our React project

The first configuration is calling the ReactApplication class with the static run method. The method requires the id from index.html file into our index.ts

import { ReactApplication } from '@devgetting/react-init';

ReactApplication.run("root")

Creating our first view

To create our first view into this implementation, we need create some files.

Component.tsx

import React from 'react';

export default function() {
    return <h1>This is our first component</h1>
}

This file requires to be exported with normal function to work with the current class instance that we are going to see bellow.

ComponentView.ts

import { View } from '@devgetting/react-init';
import Component from '../components/Component';

@View({
    component: Component,
    baseUrl: '/'
})
export class ComponentView {}

Registering view

import { ReactApplication } from '@devgetting/react-init'
import { ComponentView } from './views/ComponentView';

ReactApplication.run("root")
  .view(ComponentView)
  .start()

Component actions

This library doesn't use useState hook to make changes to the current components. To create events like hook does, we need to use the @action decorator.

// ===============================================

import { View } from '@devgetting/react-init/';
import Component from '../component/Component';

@View({
  component: Component,
  baseUrl: '/'
})
export class ComponentView {
  email: string;

  @action
  setEmail(email: string) {
    this.email = email;
  }
}

// =======================================
import React from 'react';
import { ComponentView } from '../views/ComponentView';

export function(this: ComponentView) {
  return (
    <React.Fragment>
      <input 
        type="text" 
        value={this.username} 
        onChange={(e) => this.setEmail(e.target.value)} 
      />
      <p>{ this.email }</p>
    </React.Fragment>
  )
}

@routing decorator

If you want to manage redirection from a view to another one, you have to use the @routing decorator and the Routing Type.

ComponentView.ts

import Component from '../components/Component';
import { Routing, routing, View } from '@devgetting/react-init';

@View({
  component: Component,
  baseUrl: '/'
})
export class ComponentView {
  @routing
  history: Routing;
}

Component.tsx

import React from 'react';
import { ComponentView } from '../views/ComponentView';

export default function(this: ComponentView) {
  return (
    <button onClick={() => this.history.redirect('/about')}>Go to About</button>
  )
}

Route Params

If you require params into the URL you can add params into params array of @View decorator.

@View({
  component: Component,
  baseUrl: '/user',
  params: [':userid']
})
export class ComponentView {}

This will make available a route like this: http://localhost:3000/dashboard/<userid>

Getting specific param

If you want to catch a view param is necessary use the @param decorator.

@View({
  component: Component,
  baseUrl: '/dashboard',
  params: [':userid']
})
export class ComponentView {
  @param('userid')
  userId: string;
}

If route is http://localhost:3001/dashboard/2 this property will retreive 2.

Registering new views

In case you want to add a new view you just have to add it into the ReactApplication class on your index.ts file.

import { ReactApplication } from '@devgetting/react-init';
import { ComponentView } from './views/ComponentView';
import { AboutView } from './views/AboutView';

ReactApplication.run("root")
  .view(ComponentView)
  .view(AboutView)
  .start();

Custom Not Found View

In case user access to a not registered URL, a 404 page is going to be show it by default but if you want a custom 404 view, you can do it creating a simple React component.

// components/NotFound.tsx
import React from 'react';

const NotFound = () => (
  <>
    <h1>Page not found</h1>
    <p>This route is not available</p>
  </>

);

export default NotFound;

// src/index.ts

import { ReactApplication } from '@devgetting/react-init';
import { ComponentView } from './views/ComponentView';
import { AboutView } from './views/AboutView';
import NotFound from './components/NotFound';

ReactApplication.run("root")
  .view(ComponentView)
  .view(AboutView)
  .notFound(NotFound)
  .start()

Context & Listener

An important feature into react is the way we can create context to share information between components avoiding cascade props with the child components. @devgetting/react-init provides us a simple way to share this information into different components.

Creating a Listener

to create a Listener we just need to create a new class and add the @Listener decorator.

import { Listener } from '@devgetting/react-init';

@Listener
export class HomeViewListener {}

We need register our Listener into our view class

@View({
  //baseUrl & component defined too

  listener: HomeViewListener
})
export class ComponentView {}

Once we have registered out Listener into our view, We need to create a context class that will manage all the shared data. This class needs to be registered as a Context and we need to tell it which Listener is going to be notified once we make a change.

import { Context } from '@devgetting/react-init';

@Context
export class ApplicationContext {
	public userList: string[] = [];
	public value: string;

	public registerUser(user: string) {
		this.userList.push(user);
	}

	public setValue(value: string) {
		this.value = value;
	}
}

Implementing the shared component

This is how our Component view looks like

export function Component() {
  return (
    <>
      <h1>Home View</h1>
      <RegisterUser />
      <UserList />
    </>
  );
}

This is how RegisterUser and UserList are implementing the context data.

import { action, notify, observer, Receiver } from '@devgetting/react-init';

@Controller(HomeViewListener)
class RegisterUserController {
  @Receiver(ApplicationContext)
  private applicationContext: ApplicationContext;

  get username() {
    return this.applicationContext.value || "";
  }

  @action //affects current component
  changeUsername(username: string) {
    this.applicationContext.setValue(username);
  }

  @notify //affects all registered components into the Listener
  registerUser() {
    this.applicationContext.registerUser();
  }
}

export const RegisterUser = observer(RegisterUserController, ({ controller }) => {
  const actions = {
    registerUser: () => controller.registerUser(),
    changeUsername: (e: React.ChangeEvent<HTMLInputElement>) =>
      controller.changeUsername(e.target.value),
  };

  const { registerUser, changeUsername } = actions;

  return (
    <>
      <input value={controller.username} onChange={changeUsername} />
      <button onClick={registerUser}>Register</button>
    </>
  );
})
import { observer, Controller, Receiver } from '@devgetting/react-init';

@Controller(HomeViewListener)
class UserListController {
  @Receiver(ApplicationContext)
  private applicationContext: ApplicationContext;

  get userList() {
    return this.applicationContext.userList;
  }
}

export const UserList = observer(UserListController, ({ controller }) => {
  return (
    <ul>
      {controller.userList.map((user) => (
        <li>{user}</li>
      ))}
    </ul>
  );
});

And this is how it looks like!