exivity / react-orbitjs Goto Github PK
View Code? Open in Web Editor NEWπ« React bindings for Orbit.js. Inspired by react-redux.
Home Page: https://exivity.github.io/react-orbitjs/
License: MIT License
π« React bindings for Orbit.js. Inspired by react-redux.
Home Page: https://exivity.github.io/react-orbitjs/
License: MIT License
When mapRecordToProps depends on ownProps, queryStore/updateStore are not injected on subsequent renders
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}/>
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...
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 :)
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));
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?
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.
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 π
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()
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.
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
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
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):
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:
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.
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
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).
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.
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 π¦π
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:
general query
(similar to withData
, but causes a network request: https://github.com/sillsdev/appbuilder-portal/blob/master/source/SIL.AppBuilder.Portal.Frontend/src/data/query.tsx (unsure how to cache these, and at what layer caching should happen, (in the component, orbit, etc)
withCurrentUser (uses fetch, cause I couldn't figure out how to get orbit to make a one-off request to a custom endpoint on my users controller) https://github.com/sillsdev/appbuilder-portal/blob/master/source/SIL.AppBuilder.Portal.Frontend/src/data/with-current-user.tsx
I'm not the world's greatest React developer, but it seems to me that this is only compatible with class components? The closest analog I can find is React Context, which does provide a wrapper component for use with function components. Anything similar to that possible, here?
This issue provides visibility into Renovate updates and their statuses. Learn more
This repository currently has no open or pending branches.
When Node 12 enters EOL (2022-04-30):
.github/workflows/ci.yml
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?
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'),
}
}
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.
When Node 18 is released, add support
Changes to orbit.js upstream were made which require changes in this library; namely, the expression property of the QueryTerm
object is now protected and has a getter.
orbitjs/orbit@77f3b4c#diff-875444b4ced651019e2ecc0588722aa628efba610a8bbcc4a52a736552724a5fL17-R17
This needs to be changed at
react-orbitjs/src/components/withData.js
Lines 163 to 164 in 6b2f598
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.
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
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...)
my withCurrentUser
hoc is rendered twice, once before the datastore is setup
though, throwing a console in my render method shows something different:
so I wonder if the callback on setState runs after render. hmm
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.