Giter Club home page Giter Club logo

react-mercury's Introduction

Build status Coverage Status Quality Gate

NPM dependencies Last commit Last release

NPM downloads License

Mercury Logo React Mercury

Overview

This package provides reactivity to Mercury data using a React high order component. It also dispatchs automatically the read method of the Mercury sources.

Connect the desired sources with your components and this library will retrieve the data and re-render your components when corresponds.

Install

npm i @xbyorange/react-mercury --save

Examples

In the next example, the books will be retrieved automatically from server if they are not cached. The component will be rendered again if a new book is added to the collection by another component, or deleted, etc.

Components can be connected to Mercury origins or selectors. The interface is the same in all cases.

Use connect

import React, { Component } from "react";
import { connect } from "@xbyorange/react-mercury";

import { booksCollection } from "data/books"; //booksCollection is a Mercury origin

export class Books extends Component {
  render() {
    const books = this.props.books.value || [];
    const loading = this.props.books.loading;
    const error = this.props.books.error;

    if (error) {
      return <div>ERROR</div>;
    }

    if (loading) {
      return <div>LOADING...</div>;
    }

    return (
      <div>
        <ul>
          {books.map(book => (
            <li key={book._id}>
              {book.title}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export const mapDataSourceToProps = () => ({
  books: booksCollection.read
});

export const ConnectedBooks = connect(mapDataSourceToProps)(Books);

Querying Mercury sources using component "props"

In the next example, the component will render only books of author "cervantes", and will request them to the server using the correspondant query (if not cached previously):

export const mapDataSourceToProps = props => ({
  books: booksCollection.ofAuthor(props.author).read
});

export const ConnectedModule = connect(mapDataSourceToProps)(Books);

Connect component to many Mercury sources

The connect function accepts connecting as many sources as you want. Any change on any of the sources will produce a new render of the component:

export const mapDataSourceToProps = () => ({
  books: booksCollection.read,
  authors: authorsCollection.read
});

export const ConnectedModule = connect(mapDataSourceToProps)(Books);

Connect component to specific properties of a Mercury source

You can use the property getters available in sources to pass an specific property to components instead of the full object. This will improve and simplify the usage of props inside the component, because each component prop will be equivalent to an specific source property:

import { connect } from "@xbyorange/react-mercury";

export const BooksList = ({ books, booksLoading, booksError }) => {
  if(booksError) {
    return (<div>Error loading books</div>);
  }
  if(booksLoading) {
    return (<div>Loading books...</div>);
  }
  return (
    <div>
      <ul>
        {books.map(book => (
          <li key={book.id}>
            {book.title}
          </li>
        ))}
      </ul>
    </div>
  );
};

export const mapDataSourceToProps = () => ({
  booksError: booksCollection.read.getters.error,
  books: booksCollection.read.getters.value,
  booksLoading: booksCollection.read.getters.loading
});

export const ConnectedModule = connect(mapDataSourceToProps)(BooksList);

Pass any other type of properties using connect:

The connect function can pass any other types of properties to the Component, not only Mercury sources:

export const mapDataSourceToProps = props => ({
  createBook: booksCollection.create,
  books: booksCollection.read,
  authors: authorsCollection.read,
  myProperty: true,
  anotherProperty: 5,
  isLoadingAny: props.loading || props.anotherLoading || props.anotherOneLoading,
  callback: () => {
    console.log("Callback called");
  }
});

export const ConnectedModule = connect(mapDataSourceToProps)(Books);

Using connect parser function to prepare data synchronously to component

In some scenarios, the component is not prepared to handle asynchronous data, and could fail rendering if, for example, the source value is not already fullfilled because it is loading. A Selector could be used to ensure data in component properties, but, for more specific behaviors related with loading or error properties, a parser function is available in the connector. This function, if defined, will be executed passing all component properties before rendering it, and the returned object will be applied as properties to the Component.

The parser function has to be defined as second argument of the connector:

export const mapDataSourceToProps = () => ({
  books: booksCollection.read.getters.value,
  booksLoading: booksCollection.read.getters.loading,
  authorsLoading: authorsCollection.read.getters.loading
});

export const parseProps = props => ({
  ...props,
  loading: props.booksLoading || props.authorsLoading
});

export const ConnectedModule = connect(
  mapDataSourceToProps,
  parseProps
)(Books);

This parser function should not be used as a replacement to Mercury Selectors. As a good practice, the preference is to use Selectors if possible instead of this function.

Prefetching data on server side rendering

Methods for prefetching data on server side rendering are available too. When data is prefetched in server side, the connect function will pass the value property calculated on server side to the components directly. It will not modify the loading property until the first load on client side is finished (At first client-side load, the resource will not be considered as loading to maintain the server-side value in the component until it finish loading).

It is important to define custom an unique "uuids" for your mercury sources when are going to be read on server side. Otherwise, the readServerSide method will trace a warning if detects duplicated "ids".

Server side data methods and components

  • readOnServerSide(source)
    • Alias - addServerSideData
    • Arguments
      • source - <Object> or <Array> of <Objects> Mercury source or sources that should be read when readServerSide method is executed. Can be Mercury origins or selectors of any type, queried or not.
  • readServerSide([source])
    • Alias - readServerSideData
    • Arguments
      • source - <Object> or <Array> of <Objects> Mercury source or sources. Will be added to be read with the readOnServerSide method.
    • Returns
      • <Object> This method is asynchronous, and, when resolved, it returns an object containing all server side data ready to be set on the <ServerSideData> context component.
  • <ServerSideData data={data} clientSide={true}><App/></ServerSideData> Component that sets the result of the readServerSide method in a context to make it available from all mercury connected children components.
    • Props
      • data - <Object> Object containing the result of the readServerSide method.
      • clientSide - <Boolean> If false, the connect method will not dispatch automatically the read method of the sources marked as "server-side", so, for example, api requests will not be repeated on client side, and data retrieved in server side will be always passed to connected components.

Example of server side prefecth implementation in a Next project:

In the next example, the data of the "myDataSource" mercury source will be fetched on server side and request will not be repeated on client side. The component will be rendered directly with server side data, and no loading state will be set:

// src/home.js
import { readOnServerSide, connect } from "@xbyorange/react-mercury";
import { myDataSource } from "src/data";

readOnServerSide(myDataSource); // source is marked to be read when readServerSide method is executed.

export const HomeComponent = ({data}) => {
  if(data.loading) {
    return <div>Loading...</div>
  }
  return <div>{data.value}</div>
};

export const mapDataSourceToProps = () => ({
  data: myDataSource.read
});

export const Home = connect(mapDataSourceToProps)(HomeComponent)
// pages/index.js
import { readServerSide, ServerSideData } from "@xbyorange/react-mercury";
import { Home } from "src/home";

const Page = ({ serverSideData }) => (
  <ServerSideData data={serverSideData} clientSide={false} >
    <Home/>
  </ServerSideData>
);

Page.getInitialProps = async () => {
  return {
    serverSideData: await readServerSide()
  }
}

export default Page;

Demo

To run a real implementation example in a React app, you can clone the project and execute the provided demo:

git clone [email protected]:XbyOrange/react-mercury.git
cd react-mercury
npm i
npm run build
cd demo
npm i

Now, start mocks server:

npm run mocks

And start the demo app in another shell while mocks are running:

npm start

Contributing

Contributors are welcome. Please read the contributing guidelines and code of conduct.

react-mercury's People

Contributors

javierbrea avatar juanmagit avatar methadata avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

react-mercury's Issues

Modify structure of the demo code

The structure of the code inside the demo folder does not fully accomplish with the @XbyOrange front end architecture guidelines. As this demo is very useful as an introduction to the architecture for new developers, it should follow the architecture guidelines exactly.

Then, it is desirable to change the structure of the code to make it compliant with the company front-end good practices.

Crashes when a module exports resources in a different way

Module index.js files are usually exporting resources this way:

import { MainRouter } from "./routers/main"; export default MainRouter;

With alternative methods like:

export { MainRouter } from "./routers/main";

or:

import { MainRouter } from "./routers/main"; export { MainRouter };

the plugin crashes when executing the rules factory avoidMultipleExportsInSubPiece:

TypeError: Cannot read property 'type' of null Occurred while linting /home/juan/Documents/workspaces/ORANGE/react-mercury/demo/src/modules/books/index.js:6 at checkExportType (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/@nex/eslint-plugin-modular-react/lib/rules-factories/avoidMultipleExportsInSubPiece.js:44:30) at checkDeclarations (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/@nex/eslint-plugin-modular-react/lib/rules-factories/avoidMultipleExportsInSubPiece.js:55:9) at listeners.(anonymous function).forEach.listener (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/util/safe-emitter.js:45:58) at Array.forEach (<anonymous>) at Object.emit (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/util/safe-emitter.js:45:38) at NodeEventGenerator.applySelector (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/util/node-event-generator.js:251:26) at NodeEventGenerator.applySelectors (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/util/node-event-generator.js:280:22) at NodeEventGenerator.enterNode (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/util/node-event-generator.js:294:14) at CodePathAnalyzer.enterNode (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/code-path-analysis/code-path-analyzer.js:632:23) at nodeQueue.forEach.traversalInfo (/home/juan/Documents/workspaces/ORANGE/react-mercury/demo/node_modules/eslint/lib/linter.js:752:32)

Release server side data stable version

To release version 1.1.0 stable version it is necessary to complete next tasks:

  • Develop unit tests to achieve 100% of coverage.
  • Increase coverage thresholds to 100% again.
  • Write server side data documentation, and examples.
  • Enable coveralls command in CI.

serverSideData identifiers not working

Currently serverSideData of a mercury source is not being properly identified during client side execution. It is probably related with the identifier being generated for mercury sources instances, which is not enough consistant from server side to render side.

It is needed to improve the process of generating mercury instances unique identifiers.

First public release

Migrate project from gitlab private repository and publish it into NPM public registry.

Add more "mercury" use cases to demo app

Currently, the examples of mercury use cases in the demo application don't cover complex cases and new mercury features. It is desirable to add more examples of how the library can be used to solve complex behaviors.

Some of the features should include use cases related to a...

  • Selector querying dinamically a source, based on its own query.
  • Selector returning another selector or origin.
  • Selector querying a source based on previous source results.
  • Selector reading sources in parallel.
  • Selector catching a source error and returning a fixed value when it happens.
  • Selector catching a source error and returning another source when it happens.

Support for server side data

When using Mercury in projects with server side render, it should be useful to provide an initial data to Mercury sources. The connect function should return the server side data until client-side data finish loading, and meanwhile, it should return loading property as false.
Server side data loading should be able to be disabled.

Add unit tests to demo app

The demo application should have unit tests. Doing this, it could be used as a guideline for the unit tests development implying mercury libraries.
The demo unit tests should be executed in the pipeline too.

Error installing demo dependencies

An npm error is returned when trying to install dependencies inside "demo" folder.

It is probably related to an issue in the package-lock.json file.

This issue has been reported by @juanmagit, who maybe can apport more details about the operating system, nodejs and npm versions in which it was experimented.

Do not throw error in read-server-side when duplicated id is detected

In some scenarios, the "readServerSide" method is throwing a "duplicated id" error even when the source id is a custom uuid and it is not duplicated.

It occurs in Next.js applications, when the page is refreshed. It seems that Next.js is "reloading" some js resources while keep another ones in memory. So, probably, the same "source" is being added to be "read on server side" more than once.

This Next.js behavior is hard to solve, so maybe the best solution should be to not throw an error, but to trace a warning instead.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.