Skip to content

Instantly share code, notes, and snippets.

@ChuckJonas
Last active November 14, 2021 17:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save ChuckJonas/0b9e9743cf83119f50e4273c5782d472 to your computer and use it in GitHub Desktop.
Save ChuckJonas/0b9e9743cf83119f50e4273c5782d472 to your computer and use it in GitHub Desktop.
React React

Charlies React Notes

jsx

  • must always have 1 and only 1 root node
  • must use className in place of class
  • can bind javascript with {}
  • In JSX, event listener names are written in camelCase, such as onClick or onMouseOver.
  • use Ternary opp for inline conditional logic
  • multi-line jsx can be wrapped in ( )
  • && works best in conditionals that will sometimes do an action, but other times do nothing at all.
const tasty = (
  <ul>
    <li>Applesauce</li>
    { !baby && <li>Pizza</li> }
    { age > 15 && <li>Brussels Sprouts</li> }
    { age > 20 && <li>Oysters</li> }
    { age > 25 && <li>Grappa</li> }
  </ul>
);

Lists

const people = ['Rowe', 'Prevost', 'Gare'];

const peopleLis = people.map((person,i) =>
  <li key={'person_' + i}>{person}</li>
);

// ReactDOM.render goes here:
ReactDOM.render(<ul>{peopleLis}</ul>, document.getElementById('app'))

Items in a list should be given a unique key.

Styling

  • styles defined in objects use cammel case. margin-top -> marginTop
  • units can be passed as integers and will default to px
  • can inject object literals: <h1 style={{ color: 'red' }}>Hello world</h1>
  • can import shared styles from file

React.Component

  • create a component with class MyComponent extends React.Component
  • render components with <MyComponent />
  • must implement render(). returns jsx
  • constructor, takes props. Must call super
constructor(props){
    super(props);
}

Event Handlers

  • created as a function in the component
  • can be passed to child components via props
  • called in child component through accessing props (can then be bound to the html-event attributes)
  • should be bounded to this in constructor with this.myHandler = this.myHandler.bind(this);

Stateless functional components

If you have a component class with nothing but a render function, then you can write that component as a function.

export const GuineaPigs = (props) => {
  let src = this.props.src;
  return (
      <div>
        <h1>Cute Guinea Pigs</h1>
        <img src={src} />
      </div>
    );
}

Controlled vs Uncontrolled

Uncontrolled Component: a component that maintains its own internal state.

Controlled Component: A controlled component is a component that does not maintain any internal state. Since a controlled component has no state, it must be controlled through props

Proptypes

validates that the passed in props are what's expected. (not needed for typescript!)

MessageDisplayer.propTypes = {
  message: React.PropTypes.string
};

state vs props

state: store information that the component itself can change. props: store information that can be changed, but can only be changed by a different component.

  • Props are passed into the component and CANNOT Be modified!

stateful component vs stateless component

  • "Stateful" describes any component that has a state property
  • "stateless" describes any component that does not.

state

  • use this.setState({ myObj:'val' }); -- only overwrites values passed in -- causes a render() to fire. (thus, cannot be called from render())

props

  • props are passed in as jsx attributes on components <MyComponent myProp="123" />

props.children

  • Components can contain other components inside jsx
  • Every component's props object has a property named children. Accessed via this.props.children
  • If a component has more than one child between its JSX tags, then this.props.children will return those children in an array. However, if a component has only one child, then this.props.children will return the single child, not wrapped in an array.
<BigButton>
  <LilButton />
</BigButton>

defaultProps

You can define "default values" for props to take on when they are not explicitly set, by setting defaultProps on your component.

 MyComponent.defaultProps = {
   {text:"This is a default value!"}
 };

Typescript

class MyComponent extends Component<IProps, IStates> {
    public static defaultProps: IProps = { /* ... */ }; 
    // ...
}

Life-cycle methods

"Mounting" Lifecycle Methods

A component "mounts" when it renders for the first time. This is when mounting lifecycle methods get called.

  1. componentWillMount(): When a component renders for the first time, ComponentWillMount gets called right before render

  2. render(): render belongs to two categories: mounting lifecycle methods, and updating lifecycle methods.

  3. componentDidMount(): When a component renders for the first time, componentDidMount gets called right after the HTML from render has finished loading. ComponentDidMount is a good place to connect a React app to external applications, such as web APIs or JavaScript frameworks.

"Updating" Lifecycle Methods

The first time that a component instance renders, it does not update. A component updates every time that it renders, starting with the second render.

Whenever a component instance updates, it automatically calls all five of these methods, in order:

  1. componentWillReceiveProps(nextProps): typically used for comparing incoming props to current props or state, and deciding what to render based on that comparison.

  2. shouldComponentUpdate(nextProps, nextState) : boolean: used to prevent render() from being called (return false) based on next values

  3. componentWillUpdate(nextProps, nextState): The main purpose of componentWillUpdate is to interact with things outside of the React architecture. You cannot call this.setState from the body of componentWillUpdate!

  4. render()

  5. componentDidUpdate(prevProps, prevState): usually used for interacting with things outside of the React environment, like the browser or APIs.

"Unmounting" Lifecycle Method

componentWillUnmount(): A component's unmounting period occurs when the component is removed from the DOM.

Common Patterns

Stateless Child Components Update Sibling Components

  1. parent component defines a methods that calls setState(). Methods is binded to this.
  2. parent passes method to prop
  3. The child receives the passed-down function, and uses it as an event handler.

Each Component should have only 1 job. One stateless component display information, and a different stateless component offer the ability to change that information.

//parent
class Parent extends React.Component {
  constructor(props) {
    super(props);

    this.state = { name: 'Frarthur' };

    this.changeName = this.changeName.bind(this);
  }

  changeName(newName) {
    this.setState({
      name: newName
    });
  }

  render() {
    return (
      <div>
        <Child onChange={this.changeName} />
        <Sibling name={this.state.name} />
      </div>
    );
  }
});

//child
export class Child extends React.Component {
  constructor(props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    const name = e.target.value;
    this.props.onChange(name);
  }

  render() {
    return (
      <div>
        <select
          id="great-names"
          onChange={this.handleChange}>
          <option value="Frarthur">Frarthur</option>
          <option value="Gromulus">Gromulus</option>
        </select>
      </div>
    );
  }
}

//Display
export class Sibling extends React.Component {
  render() {
    const name = this.props.name;
    return (
      <div>
        <h1>Hey, my name is {name}!</h1>
      </div>
    );
  }
}

Separate Container Components From Presentational Components

If a component has to have state, make calculations based on props, or manage any other complex logic, then that component shouldn't also have to render HTML-like JSX.

Instead of rendering HTML-like JSX, the component should render another component. It should be that component's job to render HTML-like JSX.

Article


//container class
const GUINEAPATHS = [
  'https://s3.amazonaws.com/codecademy-content/courses/React/react_photo-guineapig-1.jpg',
  'https://s3.amazonaws.com/codecademy-content/courses/React/react_photo-guineapig-2.jpg'
];

class GuineaPigsContainer extends React.Component {
  constructor(props) {
    super(props);

    this.state = { currentGP: 0 };

    this.interval = null;

    this.nextGP = this.nextGP.bind(this);
  }

  nextGP() {
    let current = this.state.currentGP;
    let next = ++current % GUINEAPATHS.length;
    this.setState({ currentGP: next });
  }

  componentDidMount() {
    this.interval = setInterval(this.nextGP, 5000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    let src = GUINEAPATHS[this.state.currentGP];
    return <GuineaPigs src={src} />;
  }
}

ReactDOM.render(
  <GuineaPigsContainer />,
  document.getElementById('app')
);


//display class
export class GuineaPigs extends React.Component {

  render() {
    const src = this.props.src;
    return (
      <div>
        <h1>Cute Guinea Pigs</h1>
        <img src={src} />
      </div>
    );
  }
}

Best way

react-webpack2-typescript-hmr

Easy way

create-react-app-typescript

npm install -g create-react-app

create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start

Hard way

references

http://blog.tomduncalf.com/posts/setting-up-typescript-and-react/ https://javascriptplayground.com/blog/2017/04/react-typescript/

steps

install typescript globally

npm install -g typescript

setup new project

> mkdir project-name
> cd project-name
> git init
> nano .gitignore

node_modules
dist
npm-debug.log

>npm init -y

init typescript

> npm i -D typescript
> tsc --init

Edit tsconfig.json:

{
  "compilerOptions": {
    "module": "es6",
    "target": "es6",
    "moduleResolution": "node",
    "baseUrl": "src",
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": false,
    "sourceMap": true,
    "outDir": "ts-build",
    "jsx": "preserve"
  },
  "exclude": [
    "node_modules"
  ]
}

add react & react-dom

> npm i -S react
> npm i -D @types/react
> npm i -S react-dom
> npm i -D @types/react-dom

setup up Webpack and Babel

> npm i -D webpack webpack-notifier
> npm i -D ts-loader
> npm i -D babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0
> nano .babelrc

{
  "presets": ["es2015", "react"]
}

> mkdir config
> nano `config/webpack.config.js`

const webpack = require('webpack')
const path = require('path')

module.exports = {
  // put sourcemaps inline
  devtool: 'eval',

  // entry point of our application, within the `src` directory (which we add to resolve.modules below):
  entry: [
    'index.tsx'
  ],

  // configure the output directory and publicPath for the devServer
  output: {
    filename: 'app.js',
    publicPath: 'dist',
    path: path.resolve('dist')
  },

  // configure the dev server to run
  devServer: {
    port: 3000,
    historyApiFallback: true,
    inline: true,
  },

  // tell Webpack to load TypeScript files
  resolve: {
    // Look for modules in .ts(x) files first, then .js
    extensions: ['.ts', '.tsx', '.js'],

    // add 'src' to the modules, so that when you import files you can do so with 'src' as the relative route
    modules: ['src', 'node_modules'],
  },

  module: {
    loaders: [
      // .ts(x) files should first pass through the Typescript loader, and then through babel
      { test: /\.tsx?$/, loaders: ['babel-loader', 'ts-loader'], include: path.resolve('src') }
    ]
  },
}

add scripts to package.json

 "scripts": {
    "build": "webpack --config config/webpack.config.js",
    "start": "webpack-dev-server"
  }

defining globabls

https://blog.johnnyreilly.com/2016/07/using-webpacks-defineplugin-with-typescript.html

Workspace configuration

  • setup project the hardway (we want to use webpack)
  • create a build directory (either ant or dx) to handle deploying meta-data (page, controller, static resource)
  • add package.json scripts to compile -> copy assets to build -> deploy
"scripts": {
    "build": "webpack --config config/webpack.config.js && npm run prepare",
    "prepare": "cp dist/app.js build/package/StaticResources/App.resource && cp -r src/salesforce/* build/package",
    "push": "ant deploy -buildfile build -Ddir build/package",
    "deploy": "npm run build && npm run push",
    "start": "webpack-dev-server --config config/webpack.config.js --https"
  }

serving locally

  1. Setup page to toggle scripts on URL param (?local=1):
<apex:page showHeader="true" sidebar="false">

    <div id="app"></div>
    <apex:outputPanel layout="none" rendered="{!$CurrentPage.parameters.local != '1'}">
        <script src="{!$Resource.App}" />
    </apex:outputPanel>

    <apex:outputPanel layout="none" rendered="{!$CurrentPage.parameters.local == '1'}">
        <script src="https://localhost:3000/dist/app.js" />
    </apex:outputPanel>
</apex:page>
  1. run npm start to use webpack to serve content locally. Will refresh page eveything you save

Working with SF data

RemoteObjects

  1. Create typings
//this is super confusing but it works... https://github.com/Microsoft/TypeScript/issues/6413
export abstract class RemoteObject{}

export interface RemoteObject {
    get?(field: string): string;
    set?(field: string, value: string);
    retrieve?(args: any, callback: RetrieveCallbackType);
    //todo: implement rest of methods
}

interface RetrieveCallbackType { (err:any, records:RemoteObject[], event:any): void }

export class RemoteObjectModel {
    Account: {new(p: any): RemoteObjectModel.Account}
}

export namespace RemoteObjectModel {
    export class Account extends RemoteObject{
        constructor(values: any){super()}
    }
}

For every remoteModel you add, you'll have to add code for:

a. MySObject: {new(p: any): RemoteObjectModel.MySObject} to RemoteObjectModel class b. A new class (MySObject) to the RemoteObjectModel namespace that extends RemoteObject

  1. setup remoteObjects in vf page
    <apex:remoteObjects jsNamespace="remoteObjectModel">
        <apex:remoteObjectModel name="Account" fields="Id,Name">
            <apex:remoteObjectField name="Active__c" jsShorthand="Active"/>
        </apex:remoteObjectModel>
    </apex:remoteObjects>

3: call remote object

let account: RemoteObjectModel.Account = new this.props.remoteObjectModel.Account({});
account.retrieve({limit:10},(err, records, event) => {
    console.log(err,records,event);
    if(err) {
        alert(err.message);
    }
    else {
        records.forEach(function(record) {
            console.log(record.get('Name'));
        });
    }
});
  1. An object called remoteObjectModel (based on jsNamespace) will be injected into global scope automaticly
  2. declare a var in your app: declare var remoteObjectModel: RemoteObjectModel;

Rest API

  1. npm install axios --save https://github.com/mzabriskie/axios
  2. inject token into global scope <script type="text/javascript">const accessToken = '{!$Api.Session_ID}';</script>
  3. declare a var in your app: declare var accessToken: string;
  4. make callout using token. Can generate types from response using something like http://json2ts.com/

Type Assertions vs Casting

Example, You call the rest API and assert that the response is of type Account

public class Account{
  public foo(){
  }
  public Name: string;
  
}

let acc : Account = Rest.query('select Name FROM Account limit 1');

While the system may let you do this, and some other the properties do line up, when you go to access .foo on the returned object, you will get undefined.

The proper way to do this would be to use Object.Assign(new Account(), Rest.query('select Name FROM Account limit 1'));. This creates a new Account instance and copies over the properties!

[react-redux-typescript-guide](https://github.com/piotrwitek/react-redux-typescript-guide)
[FAQ](http://redux.js.org/docs/FAQ.html)
[Redux Actions in typescript](https://spin.atomicobject.com/2017/07/24/redux-action-pattern-typescript/)
```typescript
export enum TypeKeys {
INC = 'INC',
DEC = 'DEC',
OTHER_ACTION = '__any_other_action_type__'
}
export interface IncrementAction {
type: TypeKeys.INC;
by: number;
}
export interface DecrementAction {
type: TypeKeys.DEC;
with: number;
}
export interface OtherAction {
type: TypeKeys.OTHER_ACTION;
}
export type ActionTypes =
| IncrementAction
| DecrementAction
| OtherAction;
function counterReducer (s: any, action: ActionTypes) {
switch (action.type) {
case TypeKeys.INC:
return { counter: s.counter + action.by };
case TypeKeys.DEC:
return { counter: s.counter - action.with };
default:
return s;
}
}
const incrementCounter = (by: number): IncrementAction => ({ type: TypeKeys.INC, by });
```

typescript & react

Example Todo Project

define state & prop interfaces

interface IAppProps {
  day : number;
  totalDays: number;
  title? : String;
}

interface IAppState {
  currentDay: number;
}

interface IHeaderProps {
  title? : String;
  displayDay: number;
}

class def

export class App extends React.Component<IAppProps, IAppState>{
    public state : IAppState;

    constructor(props : IAppProps) {
        super(props);

        this.state = {
            selectedDay: 0
        };
    }
    
    public render(){
      return <div>{this.state.selectedDay}</div>
    }
}

default props

export class Header extends React.Component<IHeaderProps, {}>{

    public static defaultProps: IHeaderProps = {
        totalDays: null,
        day: null,
        title: 'Welcome to our van adventure'
    };

    constructor(props : IHeaderProps) {
        super(props);
    }
    
    public render(){
      return <div>{this.state.selectedDay}</div>
    }
}

Typescript data mapper pattern

http://cloudmark.github.io/Json-Mapping/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment