Code Monkey home page Code Monkey logo

redux-object's Introduction

redux-object

npm version Downloads Build Status Coverage Status

Builds complex JS object from normalized redux store. Best works with json-api-normalizer.

DEMO - https://yury-dymov.github.io/json-api-react-redux-example/

Demo sources and description - https://github.com/yury-dymov/json-api-react-redux-example

API

Library provides build function, which takes 4 parameters: redux state part, object type, ID or an array of IDs or null, and options.

If ID is provided in a form of array, multiple objects are fetched. If ID is null, all objects of selected type are fetched.

Option Default Description
eager false Controls lazy loading for the child relationship objects. By default, lazy loading is enabled.
ignoreLinks false redux-object doesn't support remote objects. This option suppresses the exception thrown in case user accesses a property, which is not loaded to redux store yet.
includeType false Include the record type as a property 'type' on each result. This is particularly useful for identifying the record type returned by a polymorphic relationship.
import build from 'redux-object';

/*
state:
{
  data: {
    post: {
      "2620": {
        attributes: {
          "text": "hello",
          "id": 2620
        },
        relationships: {
          daQuestion: {
            id: "295",
            type: "question"
          },
          liker: [{
              id: "1",
              type: "user"
            }, {
              id: "2",
              type: "user",
            }, {
              id: "3",
              type: "user"
            }
          ],
          comments: []
        }
      }
    },
    question: {
      "295": {
        attributes: {
          text: "hello?"
        }
      }
    },
    user: {
      "1": {
        attributes: {
          id: 1,
          name: "Alice"
        }
      },
      "2": {
        attributes: {
          id: 2,
          name: "Bob"
        }
      },
      "3": {
        attributes: {
          id: 3,
          text: "Jenny"
        }
      }
    },
    meta: {
      'posts/me': {
        data: {
          post: '2620'
        }
      }
    }
  }
};
*/

const post = build(state.data, 'post', '2620');

console.log(post.id); // -> 2620
console.log(post.text); // -> hello
console.log(post.daQuestion); // -> { id: 295, text: "hello?" }
console.log(post.liker.length); //-> 3
console.log(post.liker[0]); // -> { id: 1, name: "Alice" }

// Other examples

const post = build(state.data, 'post', '2620', { eager: true });
const post = build(state.data, 'post', '2620', { eager: false, ignoreLinks: true });

Child objects are lazy loaded unless eager option is explicitly provided.

License

MIT (c) Yury Dymov

redux-object's People

Contributors

apsavin avatar dcodrin avatar dependabot[bot] avatar garcha avatar gaultierq avatar gnapse avatar gregpeden avatar jakedluhy avatar lvauvillier avatar marcusg avatar minusoneman avatar yury-dymov 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

redux-object's Issues

Ability to do reflection on what is a relationship and what's not

I'm concerned with the inverse operation of this library, which is to convert back an object to its jsonapi form. On its face, it's not that difficult, if there was a way to tell which properties from the object returned by build were originally relationships, and which ones weren't.

One could assume that properties that return either an array or another object were originally relationships (arrays were one-to-many relationships and objects were one-to-one relationships). However, jsonapi allows for objects to have attributes that return arbitrary json as well. Therefore this solution is not generic enough.

Then I thought about the fact that relationships are encoded in the resulting object as defined properties (if eager is false). If there was a way to detect which properties from the object are defined with a getter instead of directly holding the value, that would be great, but I haven't found a way to detect that. And also this would not work when eager is true.

Which brings me to my last resort: raising the issue here. Is there today a way to differentiate between the properties of the object returned by build, and know which ones were relationships in the original jsonapi payload, and which ones weren't? And if not, could this information be encoded somehow in the returned object, and make that a feature of this library?

PS: This is what I'm using now in my project for this purpose, which is far from ideal, but that's what I had to come up with for the short term: https://gist.github.com/gnapse/27655f4dfc1533903f70383f8e145a38

Undefined behavior in case of absent entity

const post = build(state.data, 'post', '2620');

What if there is no post 2620 in state.data?
According to implementation, build will return { id: 2620 }, but I think it's incorrect - I would expect null in such case.

There is no tests for the case, as I can see.

Remove the core-js dep

Hello,

core-js is a bit huge and IMO not necessary anymore. Is it possible to remove it from dep?

Suggestion: Remove Lodash dependencies from the production build

I don't think the Lodash methods used in the package are really required. They have ES6 equivalents. Removing Lodash as a production dependency would be nice.

It is used very deliberately in the unit testing, so it should still be retained as a development dependency.

If you agree, I will implement, test and submit a PR after you publish the previous PR, but I am just checking that you agree first before committing time to it.

[Feat. Request] "resolved" optional?

Hello,

It could sound weird to you but I'm currently using this lib for my JSON:API with json-api-normalizer, which is really useful, but I don't use redux however.

That's why I would like to know if we could remove "resolved" via an option?

Thanks!

It's not possible for resources and relationships to live under different reducers

First off thanks for the great library, it's been working great for us in tandem with json-api-normalizer. Only issue for us is that we'd like to be able to have resources live under reducers responsible for different domains.

It's simple enough for us to do API requests, normalize them with json-api-normalizer and then update different reducer's state from a single response action, picking the relevant parts of the normalized data. The issue is the build function expects all relationships to live on the same reducer as the initial resource. It would be fairly trivial to add additional options to the build function so that relationships could be pulled from different reducers.

Are you open to this idea? If so, we could probably work on a PR if you had guidance on how you'd best like the arguments passed? Our thought was as part of the options a map could be passed like so:

Where the foo resource has a relationship with type bar at a different reducer:

const opts = {
  relationshipReducers: {
    bar: barReducer
  }
}

build(fooReducer, 'foo', null, opts);

Then inside build something like this could work:

const relationshipReducer = opts.relationshipReducers[relationship] || reducer;
ret[relationship] = buildRelationship(relationshipReducer, target, relationship, options, cache);

Cyclical state

Thanks for the great library. I'm having error cannot process cyclical state. I have a scenario in which a team has a coach in relationship property(based on jsonapi specification) and that coach has the same team in its relationship and the cycle continues. How do we build such a response?
Sorry if this is confusing, but this is real blocker for serializing state which redux does.

Should not return empty array for relationships without data

Why does build return an empty array for relationships that have no data? It makes it impossible to determine from a built object if the relationship data was present and indeed empty, versus when the relationship data was not present at all.

I think this line should return either null or undefined, perhaps most preferably the latter.

Update:

To be more precise, this only occurs when the relationship has links and no data, and ignoreLinks: true is given in the options. In this case, the error is not thrown, but the buildRelationship function returns []. I think the fix should be to return undefined if the error is not thrown. I can make a PR for this case.

Lazy loading for remote objects is not supported

More details here: yury-dymov/json-api-normalizer#2.

Currently, redux-object is a very simple and lightweight implementation. Even redux is not required as redux-object can handle any JS object with a certain structure (json-api-normalizer provides such).

Implementing remote object loading support adds an enormous amount of complexity: fetching remote data taking in account variety of runtime environments and different kinds of authentication, error handling, loading indicators, async code, and so on.

Another important point is that I personally don't need this feature. Generally speaking, I find this approach to be an anti-pattern as it breaks data consistency between client and server โ€” by the time you lazily fetch nested objects from the server, the original one might be changed in the backend.

I still believe that remote lazy loading for certain cases might be useful and even the only option though. So if it is exactly your case, feel free to share ideas here regarding approaches and APIs, and we might develop it together.

serializeObject (inverse of buildObject)

I'm curious how to deal with merging (redux) objects back into the store.

A typical Update action for me involves:

  1. Fetch Object from Redux (buildObject)
  2. Modify said Object (for example, through a form)
  3. Call an API to persist the update server-side
  4. On Success, merge changes back into Redux

In Step 4. I currently update the 'attributes' hash manually (I use Immutable.js, but that shouldn't matter), but I'm thinking there could be a better way.

I wouldn't mind taking a stab at extending redux-object with a serializeObject (inverse of buildObject) function, if you think it would make sense to include that functionality in the library.

Thoughts?

Suggestion: Optionally return array of results

So... as a suggestion, please consider making the 'id' argument optional. If omitted (or 'null'), return an array of a results for the given data type.

Similarly, optionally accept an array of ID numbers as the 'id' argument. When an array, return all results for the given ID numbers as an array and throw a warning or error for any missing.

This and your normalizer are both a great, tidy package to augment API-driven state management, I look forward to utilizing them both. Thanks!

Suggestion: option to eager load relationships

Basically, I am interested in using this in Vuex and Vue, however when Vue components read the store data in to localized computed properties, they take the raw object value at that time and don't take the lazy loader getter. Since Vue is basically caching that result, if I try to access the relationship on the computed property result it comes back as "undefined".

I can think of hacky ways to get around this (ie. touch the relationships within the computed property method just to invoke the loader) however this gets sloppy fast.

So... how about some way to force eager loading of all available relationship data in the local store at build time? Thoughts?

Preserve type information for each object

@yury-dymov I've found a couple of situations where it would have been useful to still have inside the object the type information from the jsonapi. I propose including it as a special $type attribute, which is guaranteed not to interfere with normal existing attributes, given that the jsonapi spec does not allow $ as a valid character in a member name.

With this change in place build for this jsonapi object

{
  id: '1',
  type: 'items',
  attributes: { name: 'Hello World' },
}

would become this:

{
  id: '1',
  name: 'Hello World',
  $type: 'items',
}

Makes sense? I can make the change and submit a pull request.

Update: my bad, I see that there's already an option to do this. It fits me as it is, and I'll use it. But I'd argue that the use of the name type is not ideal. An object could perfectly have an attribute named type, and this option would override it. Granted, it's an edge case, but it could technically happen.

Change properties to be enumerable

When building an object from a normalized structure, properties that originate from a relationship in the normalized structure are non-enumerable. When iterating over the properties of the built object or when using Object.assign on it, those properties are lost.

Is there a specific reason for not using enumerable: true in line 85?

Suggestion: Optionally include 'type' attribute in results

OH HELLO AGAIN.

So, I've run in to an issue when working with polymorphic relationships. When using redux-object to build an asset for production, the data type of a polymorphic relationship is lost. There are probably various hacky ways to resolve this... but one clean way is to include an attribute "type" on the build output objects. This would probably be default off but could be turned on with an option control, and it would first check to see if the native object has an attribute or relationship called "type" before attempting to overwrite it. This does means that 'type' becomes special and developers should avoid using 'type' in their model design... but, since we're talking about JSON API spec here, everyone should already be treating it as reserved if they are smart. But, that's why it should be optional.

Do you agree with this? If yes, I'll submit a PR. If no, I'll do it on my own branch. Thanks.

Accessing related object not in the store

If I have users that have many books, and I make a request for a user object without sideloading the related books, then I might have the following structure in my store:

{
    users: {
        '1': {
            id: '1',
            type: 'users',
            attributes: {},
            relationships: {
                books: {
                    data: {
                        [{
                            id: '1',
                            type: 'books'
                        }, {
                            id: '2',
                            type: 'books'
                        }]
                    }
                }
            }
        }
    },
    books: {}
}

If I run const user = build(state.entities, 'users', '1') then I get the user back. However if I then run user.books I get [null, null]. I would expect that I should get

[{
    id: '1',
    type: 'books'
}, {
    id: '2',
    type: 'books'
}]

I have experienced many use cases where I don't need the full related object, just the id (to provide a link to that object, for instance) and so I won't include the related resources.

I'd be happy to make the changes if you would accept a PR!

Selecting data when fetching from relationship JSONAPI endpoint

Hello @yury-dymov

When using your json-api-normalizer and redux-object to select data from relationship endpoints like /posts/1/comments it is not possible to select comments related to this post.

This code won't work if you fetch data from mentioned endpoint.

let post = build(state, 'posts', 1);
let comments = post.comments;

This is because build builds object with a getter for relations based on meta.relationships which was constructed by json-api-normalizer, but only if you fetch data by /posts/1?include=comments endpoint
Would you consider to extend/change or accept a PR to select related data also based on

meta['/posts/1/comments']

If so please answer and would be nice to talk about ideas.

How do I make a change?

This library seems only meant for reading. How do you suggest making a change to the object and saving back to the redux store?

'meta' properties on relationships

With reference to this Issue / proposed PR on json-api-normalizer: yury-dymov/json-api-normalizer#25

I am looking to affect something similar for a PR to this package as well, however in this space it's left a bit more open to discussion with regards to how to address meta properties on relationships, or if it should be done at all. I just want to start a conversation, in the meantime I am going to experiment a bit and see if I can come up with something which works well.

Types for working with TypeScript

Hello Yury.
I appreciate using your lib. But I have some feature requests.
Please add types for the ability to work with TypeScript.

Creating new objects across subsequent (identical) calls

If I have something like the following:

const mapStateToProps = (state, ownProps) => ({ user: build(state.data, 'user', ownProps.userId) })

The resulting component is going to re-render at every possible opportunity, because build returns what is technically a new object on any state change.

I see that there's a cache parameter (but assumed it was an internal used in the recursion), so perhaps this is a case of not documenting the last parameter, rather than new functionality.

Suggestion: Include unminified + minified bundles in dist

Per the subject... if a "bundle.js" and a "bundle.min.js" are included in the dist, then the unminified version can be used during development which will permit better testing and debugging.

I am running in to a performance issue I am investigating. For now I got around this by explicitly including the source script.

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.