Skip to content

Instantly share code, notes, and snippets.

@AshCoolman
Last active February 28, 2018 11:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AshCoolman/ae3946b509897d615fbdc0ed7b8dfdfb to your computer and use it in GitHub Desktop.
Save AshCoolman/ae3946b509897d615fbdc0ed7b8dfdfb to your computer and use it in GitHub Desktop.
React reason doc notes
  • just a record
  • need a make function
  • creates new component every JSX invocation
  • props = labeled arguments make, last prop must be chilren

Props Forwarding

switch (ageFromProps) {
| None => <Foo name="Reason" />
| Some(nonNullableAge) => <Foo name="Reason" age=nonNullableAge />

Corrent, but shorthand is:

<Foo name="Reason" age=?agefromProps>

Self

  • state
  • retainedprops
  • handle
  • reduce

^ Big-bag-of-information passed to lifecycle events/render/ + a few others ???

  • Reason comes with JSX syntx (binding???)

Children

Are fully typed - accepts any data structure the receive component accepts

<A> <div /> <div /> </A>

is effectively

<A> ([| <div />, <div /> |
])</A>

(!!! I think, example has array assigned as a variable )

But that won't work, as children are wrapped,

let kids = [| <div />, <div /> |];
ReasonReact.element(
  A.make([| kids |])
);

To avoid double wrapping spread <A> ...kids </A>

Due to strict typing, need these:

  • .nullElement
  • .stringToElement
  • arrayToElement

Callback w/out state update

1. NOT reading into self

let make = (~onClick, children) =>
...
  render: (self) => <button onCLick=onClick />

2. Reading into self

To use an externally defined function which has access to self, you need to use self.handle(<fn>)

elt component = ..
let make = (~onClick, _ch) => {
  let click = (e, self) => {
    onClick(e);
    Js.log(self.state);
  };
 {
   ...component,
   initialState: ...,
   redner: (self) => <button onClick={self.handle(click)} />
},

??? I'm a bit confused why you wouldn't use a higher-order

let click => state => (e) =>
...
render: (self) => <button onClick={click(state)} />

self.handle:

  • single payoad, plus arg self
  • returns unit ??? Wait...wat

^ WRONG this list is the behavior of the callback in the example !!!!

  • Don't pass handle to helper functions, pass the whole self and annotate it ??? - avoids complexity
  • self.handle is curried with callback, cbPayload (e.g. onClick and event). Usually only ask for onClick (partial now???), and then takes cbPayload and executes func body.
  • Multiple arguments = wrap in IIFE that creates tuple, and pass that self.handle((all) => doClick(all))(one, two, three)
  • Want state update? CANT USE self.handle, must use self.send

Stateful components

Like ReactJS stateful, + reducer (like redux). aka state-machine.

let component = ReasonReact.reducerComponent("Name");
let make = (~name, _children) => {
	...component,
	initialState: () => 0,
	render: (self) => {
		let greeting = name ++ " counter:" ++ self.state;
		<div>{ReasonReact.stringToElement(greeting)}</div>
	}
};

Initial state

Declare state type (type state = {counter: int, showPopUp: bool}; right before ReasonReact.reducerComponent call.

Actions & reducer

Only change state in reducer with pattern matcher:

type action =
  | Click
  | Toggle;

type state = {
   count: int, 
   show: bool
};

let component - ReasonReact.reducerComponent("MyForm");

/* .... */

	reducer: (action, state) =>
		switch (action) {
		| Click => ReasonReact.Update({ ...state, count: state.count +1 })
		| Toggle => ReasonReact.Update({ ...state, is: !state.is })
	};
  • User define action is named by convention - variant of all state transitions (aka sm "token")
  • reducer (aka sm "state transition")
  • in render, instead of self.handle (no state updates, use self.sendtakes an action ???? notreduce`???

State update through reducer

Alternatives to ReasonReact.Update:

Important notes

  • action has payload
  • Don't pass event as payload - pooling could recycle in async reduction/state update
  • reducers pure
  • Event.preventDefault(Event) must happen in self.send BEFORE returning action type (??? event pooling)
  • Valid to trigger different action (!!! thunky) in SideEffects
  • No instance variables (??? e.g. initialState?/??) self contains only handle, not send. Replace with unit reducer: ((), state) => ReasonReact.NoUpdate. Else compile time error

Tip

  • Cram much into reducer
  • lean callback handlers

Thus state updates/side-effects co-located

Async

Js.Global.setInterval(() => self.send(Tick), 1000)

  • no willMount
  • willUpdate/shouldUpdate take record {oldSelf, newSelf}

Note: return ReasonReact.NoUpdate whenever possible from lifecycle events (???hooks)

  • retainedProps is persisted previous prop.
didUpdate: ({ oldSelf, newSelf }) =>
  if (oldSelf.retainedProps.message !== newSelf.retainedProps.message) {
    Js.log("message changed")
  • willReceiveProps nextprops are labeled arguments in make, current props are oldSelf.retainedProp etc etc

ReactJS effectively mutation without re-render. Reason has a place for these

type state = {
  blah: option(string),
  rah: ref(option(int))
}

let component...

let make...
initialState: () => { blah: Some("Blah!", rah: ref(None)},
didMount: ({ state }) => {
  state.rach := Some(Js.Global.setInterval(...));
  ReasonReact.NoUpdate
},
....

Ceremony is prep for concurrent react ??? this link

Not "Reason ref" for mutation, like a this.myVar instance variable in ReactJS???

type state = {
  isOpen: bool,
  mySectionRef: ref(option(ReasonReact.reactRef))
};

let setSectionRef = (theRef, {ReasonReact.state}) => {
  state.mySectionRef := Js.Nullable.to_opt(theRef);
  /* wondering about Js.Nullable.to_opt? See the note below */
};

let component = ReasonReact.reducerComponent("MyPanel");

let make = (~className="", _children) => {
  ...component,
  initialState: () => {isOpen: false, mySectionRef: ref(None)},
  reducer: ...,
  render: (self) => <Section1 ref={self.handle(setSectionRef)} />
};

!!! oh on its just equiv of <div ref={ ref => this.theRef = ref} />

  • nullable
  • escape hatch to allow acces to React.Component methods
let handleClick = (event, self) =>
  switch (self.state.mySectionRef^) {
  | None => ()
  | Some(r) => ReasonReact.refToJsObj(r)##someMethod(1, 2, 3) /* I solemnly swear that I am up to no good */
  };

??? e.g. testing?

They reckon its easy.

  • Reason - wrap
  • ReactJS - wrap, then import

Yeah looks fucking easy

....dayam. Shit is tight.

Map to ReactJS synthetic events - accessing values

ReactDOMRe.domElementToObj(ReactEventRe.Form.target(event))##value;

shrug

Some funky BuckleScript FFI accessors

-_-

<div style=(
  ReactDOMRe.Style.make(~color="#444444", ~fontSize="68px", ())
)/>

Style objects are a valid base, but I just want styled-components link

  • Same as ReactJS, but adding props use case would be covered by currying (??? ....via JSX I assume...)
  • *Do need for data- **
<div data-payload=1 aria-label="click me" className="foo" /> /* INVALID function label

For example, data-* and aria-* attributes aren't syntactically valid as a function label

source

??? labelled arguments = function label (or function signature?)

let myElement =
  ReasonReact.cloneElement(
    <div className="foo" />,
    ~props={"data-payload": 1, "aria-label": "click me"},
    [||]
  );
  • exact control over children type
  • Some restrictions on type constraints:
    • DOM elements must use array, lest wierd data types get fwded to underlying ReactJS div/span
    • User elements, default to array. Silly constraint of JSX syntax

Silly constraint

Auto array wrapping. Need to spread.

``

<MyForm> ...(<div />, <div />) </MyForm>
<Motion> ...((name) => <div className=name />) </Motion>
<Layout> ...(ThreeRows(<div />, child2, child3)) </Layout> /* TwoRows |  ThreeRows |  FourColumns ???? slightly strange */

This doesnt work for dom elements

et children = [| <div /> |];
<div> ...children </div>; /* <--- this line */

(bindings related constraints)

ReasonReact's DOM module = ReactDOMRe

Has helpers, e.g.:

  • render: (ReasonReact.reactElement, Dom.element) => unit
  • unmountComponentAtNode:Dom.element => unit
  • findDOMNode: ReasonReact.reactRef => Dom.element
  • objToDOMProps:Js.t({...}) => reactDOMProps use case
  • domElementToObj: turns into js object which allows u to dangerously access And:
  • renderToElementWithClassName:(ReasonReact.reactElement, string) => unit finds first el of provided class and render to it????
  • renderToElementWithId:(ReasonReact.reactElement, string) => unit finds element and render(s???) to it

If you have:

  • legacy or interop data sources from outside React/ReasonReact tree
  • a timer
  • browser event handling You'd listen and react to these changes by, for instance, updating state

i.e. with Js.Global.setIntervalwith ref(option(Js.Global.intervalId)), self.state.timerId^ <carat

A bit messy

let component = ReasonReact.statelessComponent("Todo");

let make = (_children) => {
  ...component,
  subscriptions: (self) => [
    Sub(
      () => Js.Global.setInterval(() => Js.log("hello!"), 1000), /* fn returns "subscription token" */
      Js.Global.clearInterval /* fn, given the token (??? it executes the fn), does side-effect - probs clean sub
    )
  ],
  render: ...
}

Simple, thin, etc

Uses pattern matching

Can use subscriptions to observe url "location"

let component = ReasonReact.reducerComponent("TodoApp");

let make = (_children) => {
  ...component,
  reducer: (action, state) =>
    switch (action) {
    /* router actions */
    | ShowAll => ReasonReact.Update({...state, nowShowing: AllTodos})
    | ShowActive => ...
    /* todo actions */
    | ChangeTodo(text) => ...
    },
  subscriptions: (self) => [
    Sub(
      () =>
        ReasonReact.Router.watchUrl(
          url => {
            switch (url.hash, MyAppStatus.isUserLoggedIn) {
            | ("active", _) => self.send(ShowActive)
            | ("completed", _) => self.send(ShowCompleted)
            | ("shared", true) => self.send(ShowShared)
            | ("shared", false) when isSpecialUser => ... /* handle this state */
            | ("shared", false) => ... /* handle this state */
            | _ => self.send(ShowAll)
            }
          }
        ),
      ReasonReact.Router.unwatchUrl
    )
  ],
  render: ...
}

ReactJS idiom equivalents

Prop names that are invalid in Reason

<input _type

type is reservered word

<div data-blah aria-rah

Not valid with hyphen, use cloneElement

Props spread

Can't

Can fight the system and use cloneElement

Component as prop

Because modules are in another layer of language, cant simply assign in JSX.

Split into variable

let A = (~name) => <div> name </div>

<AlphaBet a=A />;
  • Owner of AlphaBet in control, yay!
  • No pressure to prop spread in A (??? from wanting to be terse)

Conditions in render (Ternary)

Don't Button && <Button />

Do showButton ? <Button /> : ReasonReact.nullElement

Mixins

Mixins = no, Composing = yes

Custom Class/Component property

static e.g. static defaultProps, just use standalone value in module

instance If doesn't refer to component (ReactJS this), use normal value in module. If it does, still try to turn into normal let value that takes in an argument instead of reading the component's this (???self)

FAQ

  • Look at terminal

<div onCLick={_e => self.send(Click)} /> - Error might be due to self not in scope <div onClick={_e => self.ReasonReact.send(Click)} /> yay info

Probably passed self.send to helper function, that uses send ref twice

For complex reasons this doesn't type; you'd have to pass in the whole self to the helper.

element type is invalid... You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports

Probably:

  • wrapping js comp w/ReactReact.wrapJsForReason
  • JS comp uses **export default MyComp (or missing)
  • using babel to compile es6 modules

So not:

[@bs.module] external myJSReactClass : ReasonReact.reactClass = "./myJSReactClass";

but:

[@bs.module "./myJSReactClass"] external myJSReactClass : ReasonReact.reactClass = "default";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment