Coder Social home page Coder Social logo

exivity / react-orbitjs Goto Github PK

View Code? Open in Web Editor NEW
32.0 32.0 14.0 4.23 MB

πŸ’« React bindings for Orbit.js. Inspired by react-redux.

Home Page: https://exivity.github.io/react-orbitjs/

License: MIT License

JavaScript 98.39% Dockerfile 1.61%
orbit react

react-orbitjs's People

Contributors

bradjones1 avatar dependabot[bot] avatar gitter-badger avatar hongaar avatar julfla avatar michiel87 avatar nullvoxpopuli avatar renovate[bot] avatar semantic-release-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-orbitjs's Issues

Change mapRecordsToProps to use `store.query` by default.

As discussed on Gitter:

Support both live and cache queries with something like this:

const mapRecordsToProps = {
  moons: q => q.findRecords("moon"), // Uses store.query by default, or store.cache.query if DataProvider.props.cached is set to true
  planets: {cached: q => q.findRecords("planet")},
  asteroids: {live: q => q.findRecords("asteroid")},
}

Add new prop to DataProvider (defaults to false):

<DataProvider dataStore={store} cached={true}/>

Feature Request: relationships getters and setters

in relation with the OrbitJS official documentation at https://github.com/orbitjs/ember-orbit#updating-records I'm trying to access the attributes of a related record of a model retrieved with react-orbitjs but it looks like react-orbitjs doesn't expose the getter/setters functions the way ember-orbit does, as I get the error .get is not a function

is there any way to lazy load a relationship or otherwise access a given relationship attributes? the relationship identificator --> querying for the given id isn't applicable for my usecase...

Typescript Conversion

This library has some pretty involved cache checking/busting mechanisms. It'd be great to have a type-system have contributors' backs as they make changes. :)

I can do this and submit a PR, but imo: #16 should be merged first :)

Props don't update when Minified

I'm still digging into this trying to figure out what the exact problem is, but thought I'd post an issue in case someone else runs into this.

I have a coordinator setup to use a remote JSON API source. It works as expected when not minified. When I minify a production build of my app using babel-minify-webpack-plugin, my component does not update. The request for the remote resource is still fired and has the correct response, but the component does not update its props.

My component looks like this

class LocationsList extends Component {
  state = {};

  componentDidMount() {
    interceptClient.store.query(q => q.findRecords('node--location'));
  }

  render() {
    const locations = this.props.locations
      ? this.props.locations.map(location => <p key={location.id}>{location.attributes.title}</p>)
      : <p>No locations have been loaded.</p>;

    return (
      <div className="locationsList">
        {locations}
      </div>
    );
  }
}

const mapRecordsToProps = (ownProps) => {
  return {
    locations: q => q.findRecords("node--location").sort('title')
  }
}

LocationsList.propTypes = {
  locations: PropTypes.array.isRequired,
};

export default LocationsList = withData(mapRecordsToProps)(withStyles(styles)(LocationsList));

example app?

I'm looking to use orbitjs for jsonapi, and am very unclear on the interaction between the Store, the JSONAPISource, and coordinators.

Like, would I pass a coordinator to the DataProvider component?

Is this something you'd be ok with as a part of react-orbitjs?

been working on a query HOC over here: https://github.com/sillsdev/appbuilder-portal/blob/master/source/SIL.AppBuilder.Portal.Frontend/src/data/query.tsx

import * as React from 'react';
import { withData as withOrbit, WithDataProps } from 'react-orbitjs';

import { ErrorMessage } from '@ui/components/errors';

interface IState {
  result: object;
}

// Example Usage
//
// import { query } from '@data';
//
// const mapRecordsToProps = (passedProps) => {
//
//   return {
//     someKey: q => q.findRecord(...),
//     someOtherKey: [q => q.findRecord, { /* source options */ }]
//   }
// }
//
// // ......
//
// export default compose(
//    query(mapRecordsToProps)
// )(SomeComponent);
//
//
// TODO: tie in to react-orbitjs' cache handling.
// TODO: investigate why we would use react-orbitjs' cache over orbit's
// TODO: what if we just use orbit directly? do we need react-orbitjs?
export function queryApi<T>(mapRecordsToProps) {
  let map;

  if (typeof mapRecordsToProps !== 'function') {
    map = (props) => mapRecordsToProps;
  } else {
    map = mapRecordsToProps;
  }

  return InnerComponent => {
    class DataWrapper extends React.Component<T, IState> {
      state = { result: {}, error: undefined };

      componentDidMount() {
        this.fetchData();
      }

      fetchData = async () => {

        const result = map(this.props);
        const { queryStore } = this.props;

        const responses = {};
        const requestPromises = Object.keys(result).map(async (key: string) => {
          const query = result[key];
          const args = typeof query === 'function' ? [query] : query;

          const queryResult = await queryStore(...args);

          responses[key] = queryResult;

          return queryResult;
        });

        try {
          await Promise.all(requestPromises);
        } catch (e) {
          this.setState({ error: e });
        }

        this.setState({ result: responses });
      }

      render() {
        const { result, error } = this.state;
        const dataProps = {
          ...result
        };

        if (error) {
          return <ErrorMessage error={error} />;
        }

        return <InnerComponent { ...dataProps } { ...this.props } />;
      }
    }

    return withOrbit({})(DataWrapper);
  };
}

it's still a WIP, but it's gettin there.

a currently outstanding issue is that it doesn't refetch when props update... obviously it could over-render... so we'd need to watch that, and I'm sure that's what most of withData does -- all that logic.

withData is not rerendered after a replaceRelatedRecord operation with null

Hi,

I have been using react-orbitjs for a couple of days now, I really like it's simplicity! πŸ‘

It seems that withData component is not receiving updates when replacing a related record by null:

store.update(t => t.replaceRelatedRecord(
  {type: "todo", id: "my-first-todo"},
   "owner",
   null,
))

I've opened PR #11 with a new test case, and what could a fix.

Hope it can be of any help πŸ™‚

Silently fail when removing record

I have an issue when withData does not update props if I remove a record.

I think that the problem is in handleTransform function:

case 'removeRecord':
    // If the removed record had some relationships, inverse relationships
    // are modified too. As operation.record does not contain any relationships
    // we have to assume that all its inverse relationships defined
    // in the schema could be impacted and must be added to operationModels.
    operationModels.push(operation.record.type)
    const relationships = this.dataStore.schema.models[operation.record.type].relationships

    Object.keys(relationships)
      .map(k => relationships[k])
      .forEach(relationship => {
         console.log('withData-handleTransform-removeRecord', relationships)
         operationModels.push(relationship.model)
       })
    break

In case relationships is undefined it just silently falls and don't call forceUpdate()

Keep references to records if not transformed

Currently, if a transform for one of the records used in mapRecordsToProps is received, all record props are recalculated. Instead, only the records (of the models) in the received transform should be updated, keeping the references to the non-transformed records.

Huge performance problem with lots of subscriptions

I've been digging through the handleTransform code in withData, and it seems like when a record changes, is added / removed, etc,
all HoC's watching the type of object are re-rendered.

So, for example in: https://github.com/sillsdev/appbuilder-portal/
I have a user management table, and I can (re)assign roles/groups for permission reasons.
If I change a role on a user that isn't me, I would expect that the list of userRoles (join model) is updated for just that user.

what I'm experiencing is that, all components subscribing to userRoles, are updated, so this includes my withCurrentUser Hoc, which is used everywhere.

Making this simple change to a user that isn't the current user causes 200+ dom updates by default.
I'm tweaking things atm, and exploring options.
I'll update this issue as I make discoveries

removeRecord not rerendering

I have a component displaying all resources of a given type. When I addRecord the component is rerendered with the new record added, but when I removeRecord the component is not rerendered.

I have a demo repo here: https://github.com/CodingItWrong/react-orbitjs-test

Actual Behavior

Say I run it and add todos 1, 2, and 3. I will see the component update each time and ultimately show (in some sort order):

  • 1
  • 2
  • 3

If I complete todo 2, calling removeRecord, the component is not rerendered: its function is not called, and the same todos display. (Logging confirms that the removeRecord update operation completes successfully.

But then, if I add a new todo 4, when that causes the component to rerender, the todo I removed no longer shows in the list:

  • 1
  • 3
  • 4

Expected Behavior

In the same way that addRecord causes a component to rerender, I would expect removeRecord would also cause it to rerender components that depend on the relevant data.

Legacy context API implementation - React 17.x readiness

Warning: Legacy context API has been detected within a strict-mode tree.

The old API will be supported in all 16.x releases, but applications using it should migrate to the new version.

Please update the following components: WithData(Photos)

Learn more about this warning here: https://reactjs.org/link/legacy-context

Undefined prop

Awesome that you started this project! Before I tried to hook up orbitjs manually.

I cannot seem to get the props mapping to work. I simply map one query to props:

const mapRecordsToProps = {
  bandsQuery: q => q.findRecords("bands"),
}

And then I try to use it in the render function:

<Async promise={bandsQuery} pending={<b>Waiting</b>} then={(bands) =>
        <i>{bands.count} Bands</i>
}/>

But in my view, the bandsQuery prop is undefined. Even if I look at this.props of my view component, I see bandsQuery as a prop, but its value is undefined. I'm probably misunderstanding how this is supposed to work.

Btw: If I load the data in componentWillMount via the injected queryStore prop, everything works fine (I get a promise which is then resolved).

The automated release is failing 🚨

🚨 The automated release from the main branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can fix this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the main branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here are some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two Factor Authentication for your account, set its level to "Authorization only" in your account settings. semantic-release cannot publish with the default "
Authorization and writes" level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

How do you do a proper network-first then cache approach?

So, in my app, I have a WithCurrentUser HOC that I use it many places per page.
they way I have it implemented at the moment causes a request per usage.
I know this is a concurrency ( / a lack of concurrency handling) issue, but I'm just wondering what this library, and/or orbit + @dgeb recommend for this kind of behavior.

Thanks!

Things I have currently:

How do I specify query options with mapRecordsToProps?

the docs seem to only say that I can specify the first parameter to queryStore by passing a mapRecordsToProps.

What I'm wanting to do is something like this:

image

but I'd like to be able to utilize withData so I don't need to write all that prop/change logic over again, ya know?

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

This repository currently has no open or pending branches.


  • Check this box to trigger a request for Renovate to run again on this repository

Question: Use with Typescript

Thanks again for this great little package. I'm trying to venture more into TypeScript, but I'm getting an inspection in my IDE that has me a bit stumped.

SendIntroduction = withData()(SendIntroduction);

Assigned expression type ConnectedComponentClass<(props: (NativeStackScreenProps<{datingProfile: InitializedRecord}> & WithUpdatableDataProps)) => JSX.Element, Omit<GetProps<(props: (NativeStackScreenProps<{datingProfile: InitializedRecord}> & WithUpdatableDataProps)) => JSX.Element>, keyof {queryStore: (queryOrExpression: QueryOrExpression, options?: object, id?: string) => any, updateStore: (queryOrExpression: QueryOrExpression, options?: object, id?: string) => any} & WithData>> is not assignable to type (props: (NativeStackScreenProps<{datingProfile: InitializedRecord}> & WithUpdatableDataProps)) => JSX.Element 

SendIntroduction is a functional component. I can ignore this with an inline hint for PhpStorm, but I'm curious what I'm missing?

Enhancement: Ability to return anything apart from just a record function

Lets say I use several functions to retrieve multiple types of records. Some of them conditionally. In this case it would be nice if I can return something else then a function based on the condition.

const mapRecordsToProps = ({ type, id }) => {
   return { 
      record: id ? q => q.findRecord({ type, id }) : {}, 
      account: q => q.findRecords('accounts'), 
   }
}

Node 15 & 16 support

Currently, running the tests on node 15 or 16 yield the following error:

yarn run v1.22.10
$ jest && codecov
node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);
          ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "[object Object]".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Process completed with exit code 1.

Fix this so we can support running the tests on node 15 and 16.

Bug: props mapRecordsToProps are not updated when returning empty object

const mapRecordsToProps = ({ type, id }) => {
    if (id) {
      return { record: q => q.findRecord({ type, id }) }
    }

    return {}
  }

If initially provided with id, record is found and passed to props. If subsequently no id is provided and we return an empty object, the record prop remains intact as is.

Add async example to docs

Should help with making it more clear how react-orbitjs related to the store.cache.query and the store.query method is, as well as laying out some best practices of using the store.query (or queryStore) methods.

Also see #8

this.coordinator.activate() is async. So, should there be an example of building a dataStore when you need the dataStore to be built

I currently am working with this:

provider.tsx

import * as React from 'react';
import { DataProvider } from 'react-orbitjs';

import { Bucket } from '@orbit/core';
import Orbit, { Source } from '@orbit/data';
import Store from '@orbit/store';

import LocalStorageBucket from '@orbit/local-storage-bucket';
import IndexedDBBucket, { supportsIndexedDB } from '@orbit/indexeddb-bucket';
import Coordinator, { SyncStrategy, RequestStrategy } from '@orbit/coordinator';
import JSONAPISource from '@orbit/jsonapi';
import IndexedDBSource from '@orbit/indexeddb';


import { api as apiEnv, app as appEnv } from '@env';
import { getToken } from '@lib/auth0';
import * as authenticatedFetch from '@lib/fetch';

import { schema } from './schema';

const BucketClass = (supportsIndexedDB ? IndexedDBBucket : LocalStorageBucket);

interface IState {
  store: Store;
};

export default class APIProvider extends React.Component<{}, IState> {
  state = { store: undefined };
  coordinator: Coordinator;

  constructor(props) {
    super(props);

    this.initDataStore();
  }

  async initDataStore() {
    console.debug('Setting up datastore');
    Orbit.fetch = authenticatedFetch;

    const bucket = new BucketClass({ namespace: 'scriptoria-bucket' });
    const inMemory = new Store({ bucket, schema, name: 'inMemory' });

    const remote = new JSONAPISource({
      schema,
      name: 'remote',
      host: `${apiEnv.protocol}${apiEnv.host}/api`,
      defaultFetchHeaders: {
        Accept: 'application/vnd.api+json',
        Authorization: getToken()
      }
    });

    // For later when we want to persist between refreshes
    // or queue offline things
    const backup = new IndexedDBSource({
      bucket,
      schema,
      name: 'backup',
      namespace: 'scriptoria'
    });

    // We don't want to have to query the API everytime we want data
    this.coordinator = new Coordinator({
      sources: [backup, inMemory, remote]
    });

    // TODO: when there is a network error:
    // https://github.com/dgeb/test-ember-orbit/blob/master/app/data-strategies/remote-push-fail.js


    // Pull query results from the server
    this.coordinator.addStrategy(new RequestStrategy({
      name: 'inMemory-remote-query-pessimistic',
      source: 'inMemory',
      on: 'beforeQuery',
      target: 'remote',
      action: 'pull',
      blocking: true
    }));

    // Push updates to the server
    this.coordinator.addStrategy(new RequestStrategy({
      name: 'inMemory-remote-update-pessimistic',
      source: 'inMemory',
      on: 'beforeUpdate',
      target: 'remote',
      action: 'push',
      blocking: true
    }));

    // sync all remote changes with the inMemory store
    this.coordinator.addStrategy(new SyncStrategy({
      source: 'remote',
      target: 'inMemory',
      blocking: true
    }));

    this.coordinator.addStrategy(new SyncStrategy({
      source: 'inMemory',
      target: 'backup',
      blocking: false
    }));


    // If there is data already stored locally, throw it in memory
    // backup.pull(q => q.findRecords())
    //   .then(transform => {
    //     console.log(transform);
    //     return inMemory.sync(transform)
    //   })
    //   .then(() => this.coordinator.activate());

    await this.coordinator.activate();

    this.setState({ store: inMemory }, () => console.debug('datastore is setup'));

    return inMemory;
  }

  render() {
    const { store } = this.state;

    if (!store) return 'Loading...';

    return (
      <DataProvider dataStore={store}>
        {this.props.children}
      </DataProvider>
    );
  }
}

which is used here:

application.tsx

import * as React from 'react';
import { BrowserRouter } from 'react-router-dom';

import { DataProvider } from '../data';
import { ReduxProvider } from '../redux-store';
import RootRoute from './routes/root';

export default class Application extends React.Component {
  render() {
    return (
      <DataProvider>
        <ReduxProvider>
          <BrowserRouter>
            <RootRoute />
          </BrowserRouter>
        </ReduxProvider>
      </DataProvider>
    );
  }
}

and I need the coordinator to activate before I make any queries (which I don't yet have working somehow...)

I know because in my log:
image

my withCurrentUser hoc is rendered twice, once before the datastore is setup

though, throwing a console in my render method shows something different:
image

so I wonder if the callback on setState runs after render. hmm

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.