Giter Club home page Giter Club logo

data-client's Introduction

Reactive Data Client

CircleCI Coverage Status Percentage of issues still open bundle size npm version PRs Welcome Chat

Define your async methods. Use them synchronously in React. Instantly mutate the data and automatically update all usages.

For REST, GraphQL, Websockets+SSE and more

🌎 Website

Installation

npm install --save @data-client/react @data-client/rest @data-client/test @data-client/hooks

For more details, see the Installation docs page.

Usage

class User extends Entity {
  id = '';
  username = '';

  pk() {
    return this.id;
  }
}

class Article extends Entity {
  id = '';
  title = '';
  body = '';
  author = User.fromJS();
  createdAt = Temporal.Instant.fromEpochSeconds(0);

  pk() {
    return this.id;
  }

  static schema = {
    author: User,
    createdAt: Temporal.Instant.from,
  };
}
const UserResource = createResource({
  path: '/users/:id',
  schema: User,
  optimistic: true,
});

const ArticleResource = createResource({
  path: '/articles/:id',
  schema: Article,
  searchParams: {} as { author?: string },
  optimistic: true,
  paginationField: 'cursor',
});

One line data binding

const article = useSuspense(ArticleResource.get, { id });
return (
  <article>
    <h2>
      {article.title} by {article.author.username}
    </h2>
    <p>{article.body}</p>
  </article>
);
const ctrl = useController();
return (
  <CreateProfileForm
    onSubmit={data => ctrl.fetch(UserResource.getList.push, { id }, data)}
  />
  <ProfileForm
    onSubmit={data => ctrl.fetch(UserResource.update, { id }, data)}
  />
  <button onClick={() => ctrl.fetch(UserResource.delete, { id })}>Delete</button>
);
const price = useLive(PriceResource.get, { symbol });
return price.value;
const ctrl = useController();
ctrl.expireAll(ArticleResource.getList);
ctrl.invalidate(ArticleResource.get, { id });
ctrl.invalidateAll(ArticleResource.getList);
ctrl.setResponse(ArticleResource.get, { id }, articleData);
ctrl.fetch(ArticleResource.get, { id });
const queryTotalVotes = new schema.Query(
  new schema.All(Post),
  (posts, { userId } = {}) => {
    if (userId !== undefined)
      posts = posts.filter(post => post.userId === userId);
    return posts.reduce((total, post) => total + post.votes, 0);
  },
);

const totalVotes = useQuery(queryTotalVotes);
const totalVotesForUser = useQuery(queryTotalVotes, { userId });
class LoggingManager implements Manager {
  getMiddleware = (): Middleware => controller => next => async action => {
    console.log('before', action, controller.getState());
    await next(action);
    console.log('after', action, controller.getState());
  };

  cleanup() {}
}
const fixtures = [
  {
    endpoint: ArticleResource.getList,
    args: [{ maxResults: 10 }] as const,
    response: [
      {
        id: '5',
        title: 'first post',
        body: 'have a merry christmas',
        author: { id: '10', username: 'bob' },
        createdAt: new Date(0).toISOString(),
      },
      {
        id: '532',
        title: 'second post',
        body: 'never again',
        author: { id: '10', username: 'bob' },
        createdAt: new Date(0).toISOString(),
      },
    ],
  },
  {
    endpoint: ArticleResource.update,
    response: ({ id }, body) => ({
      ...body,
      id,
    }),
  },
];

const Story = () => (
  <MockResolver fixtures={options[result]}>
    <ArticleList maxResults={10} />
  </MockResolver>
);

...all typed ...fast ...and consistent

For the small price of 9kb gziped.    🏁Get started now

Features

Examples

  • Todo: GitHub | Sandbox
  • Github: GitHub | Sandbox
  • NextJS: GitHub | Sandbox

API

Reactive Applications

data-client's People

Contributors

0xcaff avatar achalvs avatar atefbb avatar axelboc avatar caseyg1204 avatar christophehurpeau avatar danbotero avatar dependabot[bot] avatar devyboy avatar dgcoffman avatar fat avatar fnky avatar github-actions[bot] avatar goncy avatar jarrettkong avatar kauedm avatar kmartinezmedia avatar ljharb avatar martinezleoml avatar maxencefrenette avatar ntucker avatar omidahourai avatar piotr-cz avatar ranyefet avatar renovate[bot] avatar rtorrente avatar skoging avatar wilhelmwillie avatar zacharyfmarion avatar zizzamia 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  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  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  avatar  avatar  avatar  avatar

Watchers

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

data-client's Issues

Module not found: Error: Can't resolve 'core-js/modules/*'

React version 16.8.5

Concurrent mode no

Describe the bug
Since the latest release and the update to corejs 3, storybook now fails to find corejs

A lot of errors like this one:

ERROR in ./node_modules/rest-hooks/lib/state/selectors.js
Module not found: Error: Can't resolve 'core-js/modules/web.dom-collections.iterator' in '/Users/chris/ornikar/sherwood-react/node_modules/rest-hooks/lib/state'
 @ ./node_modules/rest-hooks/lib/state/selectors.js 9:0-54
 @ ./node_modules/rest-hooks/lib/react-integration/hooks/useCache.js
 @ ./node_modules/rest-hooks/lib/react-integration/hooks/index.js
 @ ./node_modules/rest-hooks/lib/react-integration/index.js
 @ ./node_modules/rest-hooks/lib/index.js
 @ ./.storybook/mockUseResource.js
 @ ./.storybook/config.js
 @ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./.storybook/config.js ./node_modules/webpack-hot-middleware/client.js?reload=true

It is present in rest-hooks's node_modules:

 ls node_modules/rest-hooks/node_modules/core-js/modules/web.*                                                                                                                   
node_modules/rest-hooks/node_modules/core-js/modules/web.dom-collections.for-each.js  node_modules/rest-hooks/node_modules/core-js/modules/web.timers.js
node_modules/rest-hooks/node_modules/core-js/modules/web.dom-collections.iterator.js  node_modules/rest-hooks/node_modules/core-js/modules/web.url-search-params.js
node_modules/rest-hooks/node_modules/core-js/modules/web.immediate.js                 node_modules/rest-hooks/node_modules/core-js/modules/web.url.js
node_modules/rest-hooks/node_modules/core-js/modules/web.queue-microtask.js           node_modules/rest-hooks/node_modules/core-js/modules/web.url.to-json.js

The issue is opened in storybook storybookjs/storybook#6204 but upgrading to 5.1 didn't fix the issue, probably because CRA uses core-js 2 and only rest-hooks uses 3

Consider adding a check for trailing slash in `this.urlRoot`

Is your feature request related to a problem? Please describe.
The API I use doesn't accept trailing slashes. Thus, the default string concatenation in url() doesn't work for me.

https://github.com/coinbase/rest-hooks/blob/17bc481aa2b55ebe087d7fdecd663f6a528f0cf6/src/resource/Resource.ts#L174

Describe the solution you'd like
A simple if can check if the last character of this.urlRoot is a slash and act accordingly.

Describe alternatives you've considered
I'm currently overriding url as a workaround. I think my use case is common enough to be contributed upstream, as explained above.

I can provide a PR if there is interest.

Using Multiple resources crashes chrome

React version 16.8.6

Concurrent mode no

Describe the bug
Passing multiple resources to useResource crashes chrome sometimes

To Reproduce
Steps to reproduce the behavior:

  1. Go to https://rest-hooks-w44uh8.stackblitz.io/multiple
  2. Click on the menu item multiple
  3. Sometimes it will show "10, 11" sometimes the tab will crash
  4. Refresh the page, will crash usually within a few refreshes

Expected behavior
A clear and concise description of what you expected to happen.
It shouldn't crash.

Switching to the other method will mean it never crashes
https://stackblitz.com/edit/rest-hooks-w44uh8?file=issues%2FMultipleResources.tsx

useCache on nested array property

React version (e.g., 16.8.5)
16.8.4

Concurrent mode yes/no
No

Describe the bug
This is more a question on how to handle this kind of use.

Let's say I have a Learner Resource :

export default class LearnerResource extends Resource {
  readonly id: number = 0;
  readonly firstname: string = '';
  readonly lastname: string = '';
  pk() {
    return this.id;
  }
}

Learners are never fetched directly, but are a property of a Teacher Resource.
TeacherResource :

export default class TeacherResource extends Resource {
  readonly id: number = 0;
  readonly firstname: string = '';
  readonly lastname: string = '';
  readonly learners: LearnerResource[]= [];
  pk() {
    return this.id;
  }
  static getEntitySchema() {
     cont schema = super.getEntitySchema();
     schema.define({
       learners: [LearnerResource.getEntitySchema()]
     )}
    return schema;
  }
}

The component which displays the Teacher :

export default function Teacher({id}): React.ReactElement {
  const teacher = useResource(TeacherResource.singleRequest(), {
    id,
  });
  return (
   <div>
     {teacher.firstname}
     <LearnerList learners={teacher.learners} />
   </div>
  )

My question is, how am I supposed to retrieve the learners in the LearnerList with useCache ?

export default function LearnerList({learners}): React.ReactElement {
  const learners = useCache(?);
  return (
   <Table dataSource={learners}/>
  )

I can't make a Learner Component which could retrieve a single Learner. The component library I use forces me to give the whole data array as a prop.

Thank you !

Function from useFetcher() should have params first

Is your feature request related to a problem? Please describe.
The ordering of params and body for useFetcher() is inconsistent with the rest of Rest Hooks library. Having params first seems to also match the general outside world.

Describe the solution you'd like

create({}, { title: 'hi', body: 'this content is great' });
update({ id }, { title: 'ho', body: 'this content is mediocre' });
fetchList({});
fetchDetail({ id });

Switch ordering and now body is optional so we don't have awkward undefined being passed.

Hooks not firing in plain js react projects

React version (e.g., 16.8.5)
^16.8.6
Concurrent mode yes/no
no

Describe the bug

I have created a project using CRA 3.0.1

To Reproduce
Steps to reproduce the behavior:

  1. Create a project using CRA 3.0.1
  2. Ensure the dependencies look like this
    "core-js": "^3.1.4",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-loader-spinner": "^2.3.0",
    "react-scripts": "3.0.1",
    "rest-hooks": "^2.0.2"
  1. Replace the App.js file to be
import React, { Suspense } from "react";
import { NetworkErrorBoundary, useResource } from "rest-hooks";
import { Resource } from "rest-hooks";

class CommentResource extends Resource {
  id = null;
  name = "";
  email = "";
  body = "";
  postId = null;

  pk() {
    return this.id;
  }

  static urlRoot = "http://jsonplaceholder.typicode.com/comments";
}

function CommentList() {
  const comments = useResource(CommentResource.listShape(), {});
  return (
    <section>
      {
        comments ? JSON.stringify(comments): ''
      }
    </section>
  );
}

const Waitr = () => <p>Waiting for data ...</p>
const App = () => (
  <div>
    <h1>Main Title</h1>
    <Suspense fallback={<Waitr />}>
      <NetworkErrorBoundary>
        <CommentList />
      </NetworkErrorBoundary>
    </Suspense>
  </div>
);

export default App;
  1. See error
    The api call is stuck in pending state

image

Expected behavior

The CommentList component prints out the response stringified. The api is http://jsonplaceholder.typicode.com/comments

RestProvider throws on network error when trying to do a partialUpdateRequest

Describe the bug

const patchInstructor = useFetcher(InstructorResource.partialUpdateRequest());

const onSave = (values: Partial<InstructorResource>): Promise<any> => {
    return patchInstructor(values, { id: instructor.id }).catch((err) => {
      console.error(err);
    });
}

An invalid patch request produce this error:

 The above error occurred in the <RestProvider> component:
    in RestProvider (at App.tsx:22)
    in NetworkErrorBoundary (at App.tsx:21)
    in App (at src/index.tsx:6)

Screenshot 2019-03-26 at 19 27 11

Result: the app crashes and we have a blank page

After using the debugger, I could see that dispatch is called by NetworkManager which triggers a render of react and ends up in reducer.ts where it tries to normalize the error:

Screenshot 2019-03-26 at 19 23 46

Error: Cleaning up Network Manager

React version (e.g., 16.8.5)
16.8.5
Concurrent mode yes/no
Yes
Describe the bug
A clear and concise description of what the bug is.
More of a question than a bug
I am getting an error
Error: Cleaning up Network Manager

The app is a basic create react app with route level code splitting although the error seems to be thrown on my dashboard page which is not split into a separate bundle. I'm not sure this is related at all honestly its just something i was investigating.

Any ideas what could cause this error to be thrown?

Here is a basic example of my tree.

// index.tsx

ReactDOM.render(
  <RestProvider>
    <SharedState initialState={initialState}>
      <ThemeProvider theme={defaultTheme}>
        <React.Fragment>
          <GlobalStyles />
          <App />
        </React.Fragment>
      </ThemeProvider>
    </SharedState>
  </RestProvider>,
  document.getElementById("root")
)


// App.tsx

    <Location>
      {({ location }) => (
        <SuspenseLoader>
          <NetworkErrorBoundary
            key={location && location.key}
            fallbackComponent={({ error }) => (
              <CustomNetworkErrorBoundary error={error} />
            )}
          >
            <Router>
              <AuthenticatedApp path="/*" />
              <InitializeApplication path="/init/:token" />
              <NoAccess path="/no-access" />
              <NotFound default />
            </Router>
          </NetworkErrorBoundary>
        </SuspenseLoader>
      )}
    </Location>

// AuthenticatedApp.tsx

const Dashboard = lazy(() => import("./pages/Dashboard"))
const ISIR = lazy(() => import("./pages/ISIR"))
const Settings = lazy(() => import("./pages/Settings"))

interface AuthenticatedProps extends RouteComponentProps {}

const Authenticated: React.FC<AuthenticatedProps> = () => {
  useTokenData()

  return (
    <Suspense fallback={<LoadingPlaceholder />}>
      <Layout>
        <Router>
          <Dashboard path="/" exact />
          <ISIR path="/isir/*" />
          <Settings path="/settings/*" />
          <NotFound default />
        </Router>
      </Layout>
    </Suspense>
  )
}

Request parameters in fetch

Is your feature request related to a problem? Please describe.
I want to include additional parameters which are not included in the server response in the Resource as described in https://resthooks.io/docs/guides/network-transform#case-of-the-missing-id. However parsing them out from the url seems really burdensome and can be impossible, since the request parameters could also be in the body in some cases.

Describe the solution you'd like
As a workaround, I whipped up this static method in my Resource:

  static customRequest<T extends typeof Resource>(
    this: T,
    params: RequestParameters,
  ): ReadShape<SchemaBase<AbstractInstanceType<T>>> {
    const self = this;
    const getUrl = (p: Readonly<object>) => {
      return self.url() + '/' + pk(p);
    };
    const schema: SchemaBase<AbstractInstanceType<T>> = this.getEntitySchema();
    const options = this.getRequestOptions();
    return {
      type: 'read',
      schema,
      options,
      getUrl,
      async fetch(_url: string, _body?: Readonly<object>) {
        const jsonResponse = await self.fetch('post', self.url(), params);
        return {
          ...params,
          ...jsonResponse,
        };
      },
    };
  }

Describe alternatives you've considered
Haven't considered other solutions, but I'm sure there's some cleaner pattern.

Additional context

StateContext entities only use string urlRoot and not the complete root url

Describe the bug

Our resource urls:

/instructors/:id
/instructors/:id/locations
/instructors/:id/administrative-information

All have the same urlRoot:

  static urlRoot = `${process.env.REACT_APP_API_URL}/instructors`;

and the instructor id is set in the url or listUrl:

export default class LocationResource extends Resource {
  static listUrl<T extends typeof Resource>(this: T, params: Readonly<ListUrlParams>) {
    return `${InstructorResource.urlRoot}${params.instructorId}/locations`;
  }
}
export default class AdministrativeInformationResource extends Resource {
  static url<T extends typeof RestHooksResource>(this: T, params?: Readonly<any>): string {
    return `${AdministrativeInformationResource.urlRoot}/${
      params.instructorId
    }/administrative-information`;
  }

The problem is that the entities cache puts them in the same object as shown in the screenshot taken from the react tool:
Capture d’écran 2019-03-26 à 15 45 22

We don't yet have conflicts on ids, but i'm guessing that when it will be the case it could be a problem ?

Should we "trick" the system by setting a different urlRoot or be able to set urlRoot as a function with params ?

Note: as you can see in the image, actually they doesnt all have the same urlRoot: the InstructorResource has instead:

static urlRoot = `${process.env.REACT_APP_API_URL}/instructors/`;

How to deal with partial resources

As far as I can see, there isn't currently a robust way to handle requests that return partial resources.

In particular, I want to do a list request where I only want to request a couple of the fields of a resource. Then I will request the complete resource when I need it. This works as expected, where the detailed resource replaces the existing partial resource. The problem is that if I reverse the order of these requests.
Say I'm at a detail page, and navigate back to the list page, if the list request is stale or not in the cache, the partial resources that are fetched will replace any complete resource that is already cached.

Are there any suggestions for how to deal with this? Or plans for improvements that will make this use case possible?

Way to do optimistic updates for custom fetch shapes?

Is your feature request related to a problem? Please describe.
I'm using API that has a sort patch endpoint. It sorts a list of objects and returns nothing. I successfully added a custom fetch shape for sorting. And I'd like it to be optimistically updated but haven't found a way to do that.

Describe the solution you'd like
I'd like some interface to be able to update resource cache item manually. Fetch could provide an object containing all resource specific data stored in cache and a function to update it. For example:

fetch: async ({collectionId}, body, cache) => {
    const cachedList = cache.getList();
    const optimisticList = body.order.map(itemId => cachedList.find(itemId));
    cache.updateList(optimisticList);

      return this.fetch(
        'patch',
        '/sort',
        body,
      );
    },

Describe alternatives you've considered
I've declared a state hook to store rendered items, and update it manually when either sort is called or useResource result is updated.

Throw error with message when html is returned

The default 'fetch' function assumes a JSON response. In case non-json is returned; explicitly warn as its common for networking mismatches to return html and give 200 which makes it hard to debug the issue.

Denormalization

From what I can see denormalization of resources has been disabled here.

Based on the commit history it appears this was done to fix edge cases. When experimenting with this myself, attempting to denormalize resources fails due to the __ownerID property making it appear as an immutablejs object. Copying the entity instance and deleting the __ownerID property makes denormalization work again.

Making normalizr think the object is an immutablejs object to be intended, but I'm not entirely sure why.

What I'd essentially like is some insight into what the long term plan might be when it comes to denormalization.

I can see the value in exposing normalized entities, perhaps even by default, but right now my issue is that I can't really find a decent way to get the denormalized structure at all.

useRetrieve() should not subscribe to state

Is your feature request related to a problem? Please describe.
This potentially caused unneeded re-renders if used without useCache().

Describe the solution you'd like
Now that full middlewareAPI is provided - NetworkManager can inspect state upon dispatch. This can be used to determine staleness. We can instead send a parameter in the fetch action to determine whether we should care about staleness.

Bundle size incorrect

Hi there, this is an interesting looking package, but I think your bundle size in the readme may be a little misleading.

Given that you have a few dependencies that other people may not have (like superagent and reselect, for example), the bundle size is actually quite a bit higher than advertised: : https://bundlephobia.com/[email protected]

How to deal with resources without pk?

Is your feature request related to a problem? Please describe.

Say we want to represent some singleton-like resource, e.g. oauth service connection. Such things do not have any kind of primary key somewhere in them, there's just one instance of them anyway. How to model such things as a resource?

Currently I have end up this snippet, but this seems not to work with deleting, ending up with Error: Resource not found GET /api/service/connect:

export default class ServiceConnection extends Resource {
  readonly user: User | null = null;

  pk() {
    return 1;
  }

  static urlRoot = '/api/service/connect';

  static url() {
    return ServiceConnection .urlRoot;
  }
}

Describe the solution you'd like

Personally I expected this to work by intuition:

export default class ServiceConnection extends Resource {
  readonly user: User | null = null;

  pk() {
    return null;
  }

  static urlRoot = '/api/service/connect';
}

Notes

I could not find any information about this in the docs. Also I could not find what does pk returning null mean. Either this information looks to be quite hard to find in docs or is just absent :(

Provide a param to useResource or a new hook to fetch a resource without using Suspense

Is your feature request related to a problem? Please describe.
We would need to handle the loading state explicitly in a part of the app: asynchronous loading of the options in the select because we already have the selected value and can display that at first.
Using Suspense with a fallback component doesn't work, because it would recreate the Select.

Describe the solution you'd like
A new hook (useResourceWithLoadingState ?) or a param (useResource(SomeResource.singleRequest(), { id }, { withLoadingState: true })(

Describe alternatives you've considered
Create a hook that does that in our project: something like (untested):

import { ReadShape, Schema, SchemaOf, useCache, useRetrieve } from 'rest-hooks';
import useMeta from 'rest-hooks/lib/react-integration/hooks/useMeta';

export const useOneResourceWithLoadingState = <
  Params extends Readonly<object>,
  Body extends Readonly<object> | void,
  S extends Schema
>(
  selectShape: ReadShape<S, Params, Body>,
  params: Params | null
):
  | { loading: true }
  | { loading: false; error: Error }
  | { loading: false; error: undefined; result: NonNullable<SchemaOf<S>> } => {
  const maybePromise = useRetrieve(selectShape, params);
  const resource = useCache(selectShape, params);
  const meta = useMeta(selectShape, params);

  if (!resource && maybePromise && typeof maybePromise.then === 'function') {
    return { loading: true };
  }

  if (!resource && meta && meta.error) {
    return { loading: false, error: meta.error };
  }

  return {
    loading: false,
    error: undefined,
    result: resource as NonNullable<typeof resource>,
  };
};

Also, useMeta is not exported in rest-hooks

Making a POST request to a specific resource action

This is a great library - thank you!

I have been looking through your docs, and I can't seem to find a good way to send a POST create to a specific resource that has an action.

As an example, if I had a UserResource, I want to:

POST /users/123/make_manager

The POST itself doesn't have a body. Any ideas on how this might be achieved? Thanks!

Force refetch for resources that dont conform to rest standards.

React version (e.g., 16.8.5)
16.8.6
Concurrent mode yes/no
yes
Describe the bug
A clear and concise description of what the bug is.

Hey this is more of a question than a bug, Is there currently a way to force a re-fetch of a resource say after some mutation? I have some rest endpoints that don't return anything besides a 200 after a put / post Id like to

Incase my question isnt clear heres an example of what im doing currently which works but isnt ideal because sometimes i dont know how to update the resource myself and this is kind of a hack.

// Component
     const addCard = useFetcher(BillingResource.createCard());
      <EditCardForm
        billingProfile={billingProfile}
        stripe={{}}
        done={(token: stripe.Token) => {
          const cardId = get(token, "card.id", "New Card");
         // create the updated cache myself
          const updatedProfile = produce(billingProfile, draftState => {
            if (draftState.result && draftState.result.cards) {
              draftState.result.cards[cardId] = `New card added (${moment(
                new Date()
              ).format(
                "MM/DD/YYYY"
              )}) - Details will be updated next time you load the page.`;
            }
          });
        // pass the updated cache as the first argument of the mutation
          addCard(updatedProfile, { token: token.id, applicationName })
            .then(() => {
              setMessage({
                theme: "success",
                message: "Your new credit card information has been saved"
              });
              history.goBack();
            })
            .catch(err => {
              setMessage({ theme: "error", message: "Something went wrong" });
            });
        }}
      />


// RESOURCE
 // Fetch is overriden and in the case of no jsonResponse it just returns the body which is my
 // updated cache in the case of the component above.
  static async fetch<T extends typeof Resource>(
    this: T,
    method: Method = "get",
    url: string,
    body?: Readonly<object>
  ) {
    const jsonResponse = await super.fetch(method, url, body);
    // this looks like a detail response, so let's add the data
    if (jsonResponse && "result" in jsonResponse) {
      jsonResponse.id =
        jsonResponse.result.customerId || jsonResponse.result.tenantId;
    }
    if (!jsonResponse) {
      return body;
    }
    return jsonResponse;
  }

Is there a better way to do this have you thought about this??

This library is bad to the bone by the way thanks for everything you have done so far.

TS2741: Property 'manager' is missing in type '{ children: Element; }' but required in type 'ProviderProps'.

I started to follow the installation guide: https://github.com/coinbase/rest-hooks/blob/master/docs/getting-started/installation.md

At step 1, we need to add a RestProvider without props. Typescript asks to add a prop manager:

screenshot 2019-02-26 at 17 00 11

Also, when I run typescript check, I have these errors:

screenshot 2019-02-26 at 17 02 03

This is a new create-react-app project with typescript.

package.json:

{
  "dependencies": {
    "rest-hooks": "^0.7.3",
  },
  "devDependencies": {
    "typescript": "^3.3.3333"
  }
}

tsconfig.json:

{
  "extends": "./tsconfig.paths",
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": [
    "src"
  ]
}

Pass in fixtures instead of initialState into renderRestHook

Is your feature request related to a problem? Please describe.

I would like to test a hook when data is already populated in the cache, but right now the mechanism for doing this is passing in initialState. This means that I need to format the data how rest-hooks expects it to be formatted internally in order to test.

Describe the solution you'd like

Ideally I would just pass in an array of fixtures (similar to how it is done in MockProvider).

Describe alternatives you've considered

Another option would be to expose mockState in rest-hooks/test. That way I could just pass in mockState(fixtures) as the initialState parameter.

Additional context

How to create an url like `/articles/:articleId/comments` ?

Hello,

I'm struggling to understand the url builder.

It seems we can override the url when using singleRequest()
https://github.com/coinbase/rest-hooks/blob/master/src/resource/Resource.ts#L150
https://github.com/coinbase/rest-hooks/blob/master/src/resource/Resource.ts#L93-L99

But for listRequest, it's really limited:
https://github.com/coinbase/rest-hooks/blob/master/src/resource/Resource.ts#L167
https://github.com/coinbase/rest-hooks/blob/master/src/resource/Resource.ts#L111

Can you add a doc on how to build the url ?
Does a static method to build an url from params in the resource makes sense to you ?

Thank you,

RFC: 2.0 API breaking changes

To keep breaking changes to a minimum we'll track the planned changes here. Please comment with any API suggestions like name changes.

Plan so far:

  • RestProvider -> CacheProvider
  • RequestShape -> FetchShape

Storybook

React version (e.g., 16.8.5) 16.8.6

Concurrent mode no

Describe the bug
Example storybook doesn't work as intended.

I get this error:

Add a component higher in the tree to provide a loading indicator or placeholder to display.
in ArticleList (at articlelist.stories.js:11)
in MockProvider (at articlelist.stories.js:9)

If I add a suspense component then it get stuck at loading

<MockProvider results={options.loading}>
    <Suspense fallback={"loading"}>
        <ArticleList maxResults={10} />
    </Suspense>
</MockProvider>`

To Reproduce
Steps to reproduce the behavior:
Small repo displaying the error:
https://transfernow.net/231vh0a9o5d1

Expected behavior
Working test :)

Improve pagination interface (useResultCache)

Specify defaults in schema:

const schema = { results: [this.getEntitySchema()], nextPage: '', prevPage: '' }

So when using result cache:

  const { nextPage, prevPage } = useResultCache(
    ArticleResource.listRequest(),
    {},
    { nextPage: '', prevPage: '' }
  );

can turn into

const { nextPage, prevPage } = useResultCache(ArticleResource.listRequest(), {});

Blocked on: paularmstrong/normalizr#397

Optimistic query update on create

Is your feature request related to a problem? Please describe.
When creating a new entity, it often is expected to be included in list queries after its creation. However, while it is optimistically added to the entity cache, creations don't currently allow insertion in to query result lists. This forces a subsequent GET fetch for that particular query to include the updates. (Original report here: #32)

Describe the solution you'd like
Resource.createShape() will now take an optional list of searchParams to insert the created entity into. It will use these to construct urls with Resource.listUrl() and those will then be forwarded through the options member of FetchShape.

const createArticle = useFetcher(ArticleResource.createShape([{}, { sortBy: 'asc' }]));

Reducers will then insert at the top (configurable?) the created entity. If no such results exists, it will be created with the sole member being the entity returned. If multiple entities are in createShape's schema, the one matching the Resource provided will be the one inserted.

ExternalCacheProvider: Cannot find module 'types'

React version (e.g., 16.8.5)
16.8.3

Concurrent mode yes/no
no

Describe the bug
Typescript type error:
node_modules/rest-hooks/lib/react-integration/provider/ExternalCacheProvider.d.ts(3,29): error TS2307: Cannot find module 'types'.

To Reproduce
Steps to reproduce the behavior:

  1. Build a Typescript project that imports rest-hooks

Expected behavior
No build errors

This import style is probably the cause

Remove reselect dependency

Is your feature request related to a problem? Please describe.
This library is completely unnecessary since useMemo() exists.

Describe the solution you'd like
useMemo() instead.

best way to conditionally query a resource? maybe adding a `skip` boolean to `useRetrieve`?

Is your feature request related to a problem? Please describe.
I'd like to be able to conditionally query a resource. Often I can move the hook farther down the render tree and just conditionally render the component that calls it, but sometimes it'd be a whole lot easier just to be able to skip a network request.

Describe the solution you'd like
Apollo react hooks have a skip argument that can be passed in. Something similar could work here. The useRetrieve hook could take in optional parameters that skips the fetch call.

Custom shapes with different getFetchKey

I'm trying to upgrade from 1.x to 2.x, where previously I had some custom requests (now shapes), which had different a different url (now getFetchKey). I can't seem to change the url as part of my new shapes. Maybe this is a bug in version 2?

export class ExampleResource extends BaseResource {
  static urlRoot = 'v1/examples/';

  static customShape() {
    return {
      ...super.createShape(),
      getFetchKey: () => 'v1/examples/custom/',
    };
  }
}

Previously this code was:

export class ExampleResource extends BaseResource {
  static urlRoot = 'v1/examples/';

  static customRequest() {
    return {
      ...super.createRequest(),
      getUrl: () => 'v1/examples/custom/',
    };
  }
}

Incorrect React Suspense fallback component is used

Describe the bug
Not sure if it's a bug or me misunderstanding something, but I'm having difficulties locally handling loading state with React's Suspense in my App.
The top level Suspense fallback is used even if there is a closer use of Suspense .

My main App.js is :

    <RestProvider manager={manager}>
      <BrowserRouter>
          <NetworkErrorBoundary fallbackComponent={NetworkErrorPage}>
            <Suspense fallback={<Loader color='blue' />}>
              <AppRouter />
            </Suspense>
          </NetworkErrorBoundary>
      </BrowserRouter>
    </RestProvider>

One of the routes :

const myRouteExample = () => 
(
        <Suspense fallback={<Loader color='red'/>}>
          <AccountManagerSelect onSave={onSave} />
        </Suspense>
)

The account manager component :

const AccountManagerSelect = () => {
  const accountManagers = useResource(AccountManagerResource.listRequest(), {})
  return (
     <div>
        {accountManagers.map(am => <div>{am.firstname}</div>}
     </div>
  ) 
}

While the accountmanagers list is fetched, the Top level Loader is shown :/ ( blue color )

However, if I add a simple toggle to display the component after a button click :

const myRouteExample = () => {
  const [showAM, setShowAM] = useState(false);
return (
     {showAM ? (
        <Suspense fallback={<Loader />}>
          <AccountManagerSelect onSave={onSave} />
        </Suspense>
      ) : (
        <Button
          onClick={() => {
            setShowAM(true);
          }}
        >
          TEST
        </Button>
      )}
}

Upon clicking the test Button, the correct loader is displayed ( red color ) !

Is there something I'm not getting ?
Thank you !

Expected behavior
The closest Suspense fallback should be used.

Typings are broken

React version (e.g., 16.8.5) 16.8.6

Concurrent mode no

Describe the bug
Since 1.6.1 TypeScript fails to infer return type of useResource, it falls back to any:

Capture d’écran 2019-05-27 à 17 38 51

With tsc traceResolution flag, the compiler complains about unresolved modules within the typing files: ~/resource, ~/types, ~/state/NetworkManager, etc.

Looks like the regression happened in 7aecc9a.

To Reproduce

  1. use useResource hook with version 1.6.1.

Expected behavior
Correct inference, like in versions < 1.6.1 :

Capture d’écran 2019-05-27 à 17 38 23

Getting result after 400 <Bad Request> (POST)

I have a simple password change form. When I submit request and, for example, one input value was incorrect, the request will be 400 Bad Request. After that I should get an error object like that:

{
  "status": "error",
  "error": {
    "type":" validation_error",
    "status_code":400, 
    "params": [
      {
        "code": "password_does_not_meet_requirements",
        "message": "Password does not meet requirements"
      }
    ]
  }
}

But I can't get the result. I tried like that using .then():

const post = useFetcher(ChangePasswordResource.createShape());

<Form onSubmit={(values) => {
  return post(values, {}).then(data => console.log(data)
}} />

Based on this, should I create own custom Post and how?

@types/react as dependency

Is it necessary to specify react types as a dependency or would it be sufficient to have it as a devDependency?

The reason for this issue is that having a dependency on the react types (in particular a specific version of it) is likely to at some point cause users to get multiple versions of the react types installed, which usually causes type errors that are difficult to debug.

Alternatively it could be a wildcard version, which is how dependencies between @types packages are speceified:

"@types/react": "*"

(I currently have to use yarn resolutions to force an older version of the @types/react while I wait for this issue in material-ui to be resolved)

Simple, fast, small code replacement for lodash.memoize

Is your feature request related to a problem? Please describe.
Rest Hooks uses memoization to cache certain method calls that aren't expected to change for the lifetime of a given class or object. Lodash.memoize is not even the best general memoizer; but there could be a better special-case method for our uses.

Describe the solution you'd like

  • Not increase the LOC in actual Rest Hooks (i.e., one line wrapper)
  • Have a benchmark to back up claims of good performance
  • As small code add as possible.
  • Key on this. Sometimes it will be static (class) and others the instance (object).

Describe alternatives you've considered
Since we're essentially attaching based on this a simple custom implementation is not out of the question.

Additional context
Stumbled upon https://github.com/anywhichway/nano-memoize recently. Though this is a more general-purpose solution.

Provide a better way to mock in stories

This is in the readme: "📙 Storybook mocking"

We're using a component for now:

import React from 'react';
import { StateContext } from 'rest-hooks/lib/react-integration/context';

export interface MockResultsProps {
  results: Record<string, any>;
  children: React.ReactNode;
}

export default function MockResults({ results, children }: MockResultsProps) {
  const mockState = {
    entities: {},
    results,
    meta: {},
  };
  return <StateContext.Provider value={mockState}>{children}</StateContext.Provider>;
}

Usage:

const mockResults = {
  'http://localhost:5001/api/thing/1': { id: 1 },
};

storiesOf('things', module).add('Thing', () => {
  return (
    <MockResults results={mockResults}>
      <Thing id={1} />
    </MockResults>
  );
});

List ressource handling

Hi,

This is a more a question rather than an issue.

In my App, we display a list of comments ( about a user ) as well as a component allowing you to submit your own comment.

The comment resource is almost identical to the one in your stackblitz example.

In the List component I use : useResource(CommentResource.listRequest(), { id });
In the AddComment component i use : useFetcher(CommentResource.createRequest());

However when I add the comment, the ListComponent does not rerender with the added comment.
I can see the new Comment is added to the entities of the state context, but not in the results.

Screen Shot 2019-04-03 at 14 12 31

What is your recommended way of handling this kind of behaviour ? Should i refetch the Comments List entirely after posting? Is there something I'm missing ?

Thank you :)

Expose APIs for persisting RestProvider state

Is your feature request related to a problem? Please describe.
I'm exploring how to persist <RestProvider /> state between reloads, using localStorage. Unfortunately this provider does not have a way to inform users its state has changed. I managed to get it working via __INTERNAL__.StateContext, but there should be a public API for this.

Describe the solution you'd like
Add a new onStateChange (or similar) prop to <RestProvider/>. Alongside initialState this new prop would allow full customization of the hydration & dehydration logic.

Introducing a cacheManager prop with the following signature could work as well:

interface CacheManager<T> {
  save: (state: State<T>) => void;
  load: () => State<T> | undefined;
}

Not sure how the initialState would behave in this case though.

Describe alternatives you've considered
I've successfully implemented this feature via the redux integration, but I feel like I shouldn't have to bring redux for this specific use case.

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.