Skip to content

Latest commit

 

History

History
561 lines (422 loc) · 29.6 KB

File metadata and controls

561 lines (422 loc) · 29.6 KB

Components

If you go declarative, you have composability.

Basically treat the app as a game engine, and use a lot of the same techniques.

You can easily build completely isolated, reusable, and composable UI components. Nested views just become a natural extension of the way React works.

UI are fundamentally tree. Dealing with REST endpoint introduces complexity. Whatever comes out from that endpoint is typically not in the shape my UI expected.

UI components should define what they need. Use a recursive description (JSON, EDN, Transit, etc). Falcor, JSON Graph.

Any Cycle.js app can be reused as a component in a larger Cycle.js app.

Cycle.js treat every component as a mini-program of its own. We should also do that in React. Every React component we built, we should treat it as our main() program!

Remember, components in React are pretty much like functions, you can spit a function into many and compose or combine in any way you want.

By having taken the data out and put all actions elsewhere, your components should be pure function to take in props and render HTML output. That's it!

// This is a quick component in file Note.jsx
import React from 'react'
export default () => <div>A very simple component</div>

// To use
<Note />

// Essentially it is just returning a React Element
export default function() {
  return <div>A very simple component</div>;
};

A good rule of thumb in React is that everything that can be expressed as a component, should be. Think React-Router and DragDropContext wrapping - Component vs Mixin

// FluxComponent is the wrapper component
render() {
  <div>
    <SiteNavigation />
    <MainContentArea>
      <FluxComponent connectToStores={{}}>
        <BlogPost />
      </FluxComponent>
    </MainContentArea>
    <SiteSidebar />
    <SiteFooter />
  </div>
}

Try passing in components to other components if you have similar components that have slightly different displays. Or you can use this.props.children and put it inside.

State Machines

Components are just state machines. React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.

DOM is stateful. Input focus and selection, scroll position, etc.

Unintentional side effects are the bane of code reuse. They occur when multiple functions depend on and manipulate the values of the same variables or object properties. In this situation, it becomes much more difficult to refactor code, because your functions assume too much about the state of the program.

Remember that components don't have to emit DOM. They only need to provide composition boundaries between UI concerns. - See Smart and Dumb components

A state machine always have initial state as model by getInitialState.

Mistakes and Best Practices

Do not use platform components, always use <PrimaryButton> instead of plain platform <button> component. For iOS, don't use <View>, use your own abstracted components.

For EVA mistake, the <Editor> is too heavy and we did not break it down into even smaller components. The <Editor> itself is the DnD container which should not be the case. Whenever there is a drag event, the whole <Editor> will be affected and the <AddPreview> will be unnecessarily wasted in its rendering effort.

Always break your component down into single responsibility! And break it down further after that!

  • Components are just views, don't place business logic in it. For example, in a Todo app, if a button creates a Todo item for each click event, you are doing it wrong. A better way is to ship that code off to another object or Action. Searching through components to determine behaviour is not really something we should be doing anyway.
  • A component shouldn't worry about networking.
  • Composable. Self-contained and stateless. In order to be self-contained, the component cannot rely on any other specific components. But of course it can be and must "composed" of other components.
  • Component should be stateless. Most components should not maintain its own state. Instead it should defer state-management to a parent container sometimes called container components.
  • Container components should not have any styling! Leave it to the presentation components.
  • Don't be clever and pass a giant object which is consumed by a child component.
  • Don't pass in whole collection object, pass in the fetch method as prop and call that prop within componentWillMount instead. Then you can do spinner at that component instead of knowing when to spin from the parent.

Remember, components don't have to emit DOM. They only need to provide composition boundaries between UI concerns.

How small should a component be? Take a look at this:

const LatestPostsComponent = props => (
  <section>
    <h1>Latest posts</h1>
    { props.posts.map(post => <PostPreview key={post.slug} post={post} />) }
  </section>
);

Controller View? Container Components? Presentation Components?

My biggest pet peeve is it is harder to compose, reuse, nest, and generally move around container components because there are two independent hierarchies at the same time (views and reducers). It is also not entirely clear how to write reusable components that either use Redux as implementation detail or want to provide Redux-friendly interface. (There are different approaches.) I’m also not impressed about every action having to go “all the way” upwards instead of short-circuiting somewhere. In other words, I would like to see something like React local state model but backed by reducers, and I would like it to be highly practical and tuned to real use cases rather than a beautiful abstraction. - Issue#1385

You will often hear React developers refer to controller views - a React component that typically sits at or near the top of a section of the page, which listens to one or more stores for changes in their state. As stores emit change events, the controller view updates with the new state and passes changes down to its children via props.

Controller view must be capable of:

  • Publishing actions
  • Listening to stores

Creating a good container components will take you awhile to really get the hang of.

If different parts of your app require fetching a model, create one container component for fetching data, then pass that state down into any number of different presentation components. From there, handle any interactions your user might cause.

Container component separates data-fetching and rendering concerns. Or components can't be concerned with both presentation and data-fetching.

If you decide to split your components into Data Component and Presentational Component, you might as well write functional stateless components.

Lifecycle

componentWillMount and componentDidMount will get called whenever state or props changed, so if you only want to respond to state change, you need to use the callback at this.setState({}, callback).

Initialization

defaultProps

componentWillMount or constructor

Invoked once, both on the client and server, immediately before the initial rendering occurs.

It's important to note that calling this.setState within this method will not trigger a re-render. So , don't do any data fetching here.

Do not call this.setState here

componentDidMount

Invoked only on the client! Great for setting up interval.

Warning: Be careful that once it has mounted and if the props is going to change, this lifecycle methods won't be invoked again, please see componentWillReceiveProps

componentDidMount() {
  this.interval = setInterval(this.tick, 1000);
}

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

componentWillReceiveProps(newProps)

Use this to react to props transition before render() is called. Calling this.setState will not trigger an additional re-render and we can access old props via this.props.

componentWillUpdate(nextProps, nextState)

Call immediately before rendering. Great for triggering animation. You cannot use this.setState() here!

componentWillUpdate(nextProps, nextState) {
  if (nextProps.visibility) {
    $(??).velocity('slideFadeIn');
  } else {
    $(??).velocity('reverse');
  }
}

Do not call this.setState here

componentDidUpdate(prevProps, prevState)

Happens every time the element is re-rendered. Good for maintaining scroll position. You can do paranoid validation here also, but admittedly is not needed (like text length limiting)

Also good when you need to update 3rd-party JavaScript library like Select2/Chosen.

componentDidUpdate() {
  var node = this.getDOMNode();
  node.scrollTop = node.scrollHeight;
}

// Limiting the text should really be done at event handler, but
// here we are just being paranoid and do additional validation
componentDidUpdate(prevProps, prevState) {
  if (this.state.text.length > 3) {
    this.replaceState(prevState);
  }
}
// Good for doing input focus
componentDidUpdate(prevPros) {
  const value = this.props.value;
  const prevValue = prevProps.value;
  
  if (this.isMounted && value.errors && value.errors != prevValue.errors) {
    if (value.errors.email) {
      this.refs.email.focus();
    }
  }
}

Owner-Ownee and Parent-Child

Child component receive immutable properties from their parent. Such an relationship is called ownership. Components that set props of other components are owners but not necessarily direct parents in terms of DOM structure. This is one-way data flow.

It is important to draw a distinction between ownership and parent-child relationship.

Playground

Render function

Think of React as a snapshot in time, not "over" time. You only think of the "now".

  1. render should be idempotent
  2. render should not cause side-effects.
  3. render must be pure, meaning it does not change the state or modify the DOM output.
  4. You can return null or false if you do not want to render anything at all.

You must remember that the result returned from render() is not the actual DOM or any action being taking place. If you have a console.log() in your render(), it will be printed on the console, but do not make the mistake of thinking that your render() has been completed, it has not! The returned virtual DOM is just sitting there waiting for its chance to truly render on the screen for the next available clock tick (RAF). While waiting for the next clock tick, React will diff the virtual DOM to determine if it even need to update the real DOM or not.


It is generally recommended that you funnel all your complex processing through the render() method. Event handlers just setState() while render() is the hub for all of your core logic.

Decide everything in the render() functions. This means all calculations and conditionals! Make use of helper functions if you can like renderFullName() etc. Of course for CPU intensive calculations, use memoization function.

var memoize = require('lodash.memoize');

Data binding is a hack around re-rendering.

Re-rendering and shouldComponentUpdate

When Parent need to re-render, it will also automatically re-render any Child. This is the most basic functionality of React re-rendering logic.

When setState is called, the component rebuilds the virtual DOM for its children. If you call setState on the root element, then the entire React app is re-rendered. All the components, even if they didn't change, will have their render method called. This may sound scary and inefficient but in practice, this works fine because we're not touching the actual DOM.

Another important point is that when writing React code, you usually don't call setState on the root node every time something changes. You call it on the component that received the change event or couple of components above. You very rarely go all the way to the top. This means that changes are localized to where the user interacts

If you do not want Children to be re-render, you have to use Immutable and compare props at shouldComponentUpdate.

Note: forceUpdate skip shouldComponentUpdate, essentially a full hard refresh.

Once again:

When each component re-renders the children it renders re-render too (unless prevented by shouldComponentUpdate).

Re-rendering on every change is impractical unless all your component have very strict shouldComponentUpdate implementations like those provided by Om.

Note: PureRender/shouldComponentUpdate really is considered an escape hatch for performance and is not necessarily something that should be blindly applied to every component.

Key

Libraries

See http://davidtheclark.com/modular-approach-to-interface-components/ for a nice way to modularize other libraries.


Date-picker and Calendar

Drag and Drop

Resizable

Validation

Pagination & Infinite scroll

Text editor

D3 or just Victory.js

Just use Victory.js for new project.

import React, { Component } from 'react';
import d3 from 'd3';

export default class Sparkline extends Component {
  render() {
    const { width, data, height, interpolation } = this.props;

    const x = d3.scale.linear()
      .range([0, width])
      .domain(d3.extent(data, (d, i) => i))

    const y = d3.scale.linear()
      .range([height, 0])
      .domain([0, 100])

    const line = d3.svg.line()
      .x((d, i) => x(i))
      .y((d) => y(d))
      .interpolate(interpolation)

    return (
      <svg className="sparkline" width={width} height={height}>
        <path d={line(data)} />
      </svg>
    );
  }
}

Refs

Q: Curious why string refs look to be deprecated in a future version of React. Is it simply that callbacks can already handle basic references / are more versatile? Subtle way to discourage the use of ref's as an escape hatch for component communication? - BradColthurst

A: not to discourage people, though that could be a nice side-effect? a handful of reasons reasons: (1) string refs make it less clear that there's a timing aspect involved and that the ref only becomes available at a certain point in the mount cycle, whereas function refs make it a little clearer, (2) function refs are more flexible and you could read some property from or call a method on the node without storing it, (3) if you have a helper function outside your class that returns a component with a string ref, it's unclear where that ref will be attached, and (4) we track which component is the "owner" to attach refs to using a stateful module within the 'react' package, which is intellectually gross but can also complicate things in a practical sense and means you it's harder to use multiple versions of the 'react' package (which should otherwise work correctly)

https://github.com/reactiflux/q-and-a/blob/master/ben-alpert_react-core.md

Integrate with jQuery

var Datepicker = React.createClass({
  render() {
    return React.createElement('input', {type: 'text'});
  }
  	
  runJQ() {
    $(React.findDOMNode(this)).datepicker(this.props);
  }
  
  componentDidMount() {
    this.runJQ();
  }
  
  componentDidUpdate(prevProps) {
    if (!propsEqual(this.props, prevProps)) {
    	this.runJQ();
    }
  }
});
$.fn.react = function(component, props) {
  React.render(
    React.createElement(component, props),
    this.get(0)
  );
};

$('body').react(TodoList, {items: ['Get milk']});

Examples

class link { // No need extends?
  render() {
    var className = this.props.highlighted ? 'highlight' : '';
    var children = this.props.children;
    
    return (
      <a
        className={'link-button ' + className}
        href={this.props.href}
        rel="prefetch">
        {children}
      </a>
    );
  }
}

// Use it as
<Link highlighted />