Giter Site home page Giter Site logo

Hydrate scalar fields on client about relay HOT 29 OPEN

facebook avatar facebook commented on May 7, 2024 19
Hydrate scalar fields on client

from relay.

Comments (29)

Hubro avatar Hubro commented on May 7, 2024 11

So is this issue dead? This is still pretty much the only discussion I can find on this topic through Google. I have a bunch of "date" leafs in my GraphQL schema and it's a real pain to repeatedly have to convert them to Date objects in the views they are needed.

from relay.

benkuhn avatar benkuhn commented on May 7, 2024 7

@wincent I notice this issue is still closed. Does that mean there are no plans to work on this? Custom scalars are a pretty awesome feature of GraphQL and are currently super type-unsafe in Relay since they can't be typechecked by Flow due to #2162.

from relay.

josephsavona avatar josephsavona commented on May 7, 2024 7

This feature request is tricky; it's come up often enough over the years that it is clearly useful to some folks, but it doesn't appear to be a major point for too many people. At present we aren't planning to work on this, but I agree that this is a conceptually missing feature and it would be great if we could find a clean way to address it.

In terms of implementation, my instinct is that it makes sense for the store to contain the serialized form of the value (e.g. a DateTime as a string), and then deserialize into a custom object at the point that we read data out of the store to vend to components. The catch is that any type used in this way would have to implement some form of equality check, so that we could determine whether the value has changed. Another aspect is how users would enable this special behavior: there it makes sense to either have schema directives or compiler config that allows one to map specific scalar types to a custom resolver module, such that Relay Compiler could automatically detect instances of e.g. DateTime fields and enable deserialization automatically in all fragments that select DateTime fields.

The above sketch is not intended to act as a blueprint for a PR immediately. It's just a sketch of some thoughts right now. We have a few things in the works that touch on the relevant code here so if you're interested in contributing from the community, reach out here or (preferably) on Slack first.

from relay.

steveluscher avatar steveluscher commented on May 7, 2024 5

I've talked this over with some people and have written a draft specification. We're not pursuing this internally, but if folks are motivated to get this working, we'd be happy to take a look at something similar to this proposed spec:

A method for serializing/deserializing scalar fields on the client

Given a scalar value sent from the server, like ‘1980-03-27’, you might want to interact with it as a JavaScript Date object on the client (eg. by creating a Date instance with new Date('1980-03-27')). What follows is a proposal to implement custom, runtime serializers/deserializers on the client.

Custom scalar types as deserializable

The idea is to consider all fields of type GraphQLScalarType as in need of a serializer/deserializer to be used with Relay. Given a field with this .graphql definition:

scalar ISODate

…which was, for instance, generated with this graphql-js schema:

const ISODate = new GraphQLScalarType({
  name: 'ISODate',
  serialize: date => date.toISOString().toJSON(),
  parseValue: isoString => new Date(isoString),
  parseLiteral: ast => new Date(ast.value),
});

…Relay should look up a custom serializer/deserializer at runtime to use to deal with values of this type.

Specification

  1. Map typenames to their serializer/deserializer using the following API:

    relayEnvironmentInstance.registerTypeHandler('ISODate', {deserialize, serialize});
  2. Check to see if you have a registered deserializer when you go to read a scalar value (see readRelayQueryData#_readScalar())

    • if no deserializer is registered, return the raw value
    • if a deserializer is registered use it to produce the value
  3. Memoize the deserialized values and clean up the memo when those fields are no longer used (see, for instance _updateGarbageCollectorSubscriptionCount in GraphQLStoreQueryResolver)

  4. When printing mutation queries, serialize the values for transport over the wire. See printArgument of printRelayOSSQuery.

from relay.

taion avatar taion commented on May 7, 2024 2

Someone mentioned seeing related performance issues with repeatedly instantiating Moment.js objects on the component.

Would it be possible to re-open this issue? I understand that this is unlikely to be a priority, especially given adequate user-side workarounds, but this is something that needs to be handled... somewhere.

from relay.

wincent avatar wincent commented on May 7, 2024 2

Going to close this as Relay 2 will have support for arbitrary "handle" fields, for which you can register a handler in a way similar to what @steveluscher has suggested. As we're unlikely to undertake the work required to implement something like that on Relay 1, I'm going to mark this one as closed for now.

Thanks to everybody for their input!

from relay.

mxck avatar mxck commented on May 7, 2024 2

@wincent can you reopen ticket?
In our project we needed scalar type hydration. And as we understand the only solution is to allow to save in store JS objects, not only JSON types (strings, numbers, arrays, objects). Thanks!

from relay.

kassens avatar kassens commented on May 7, 2024 1

I'll reopen this as it might be interesting to explore how we could for example hydrate a custom URL scalar into an URL instance or a timestamp into a Date on the client.

I'm not sure about all the implications of this on Mutations (we should still validate that people don't set a deep structure on what should be a linked record) or other places this would have.

from relay.

tlvenn avatar tlvenn commented on May 7, 2024 1

@wincent it would be great to have an update on this topic when you get the chance. Thanks.

from relay.

wincent avatar wincent commented on May 7, 2024 1

I'm not working on Relay anymore.

from relay.

josephsavona avatar josephsavona commented on May 7, 2024

This isn't currently supported, and you'd have to manually convert complex types e.g. in your components given the plain data from Relay. This is definitely a use-case worth considering.

I'll leave this open to gather feedback about the idea and examples of data types people may need.

from relay.

fson avatar fson commented on May 7, 2024

The most common use case that comes to mind is indeed DateTime (that was the first custom scalar type we added in our own GraphQL server). Another example that would be useful in apps that deal with location data could be a geo point, for example hydrated to google.maps.LatLng.

Because these scalar types and their client-side representation are application specific, if this is going to be added to Relay it should probably be fully extendable, e.g. you could define a mapping from your GraphQL types to your JavaScript object builders. Transit-js might be a good place to look for an example on how to provide extension points for this.

I think this is could be an interesting idea. While it can also be implemented separately in the transport layer, e.g. with Transit, the GraphQL schema already has the necessary type information for hydrating types, meaning no extra metadata about the object type would need to be passed in the responses. This might save lots of bytes in apps that handle large amounts of data.

On the other hand this could add complexity to the framework, so maybe it would be worth it to consider if this could be done as a separate library outside Relay? A custom RelayNetworkLayer gets access to the runtime representation of each query in sendQueries and sendMutation. Maybe if the Babel plugin would store enough information about the types in the tree, the hydration of responses and serialization of requests could happen in the network layer using that information about the query?

from relay.

KyleAMathews avatar KyleAMathews commented on May 7, 2024

Yeah, Transit-js was what I was thinking of when I wrote this issue.

from relay.

taion avatar taion commented on May 7, 2024

This would be useful for having fields that represent plain JSON objects as well.

from relay.

josephsavona avatar josephsavona commented on May 7, 2024

The transport layer is probably the wrong place to introduce this as it couples too much of the system together; in particular, the transpiler (to annotate types), the query representation (to expose this metadata), and any network implementation. It also increases the responsibility of a network layer, putting a burden on all implementors.

The best way to approach this is to change babel-relay-plugin to annotate the types of leaf fields, and add support in the payload processing phase (RelayQueryWriter) for deserializing "complex" values. For each leaf field, check if it has a non-scalar (Int, String, Boolean) type and if so, determine if there is an injected handler for that type. Pseudo code:

// In `RelayQueryWriter`
visitField(field, value) {
  var type = field.getType();
  if (type && !isScalarType(type)) {
    var handler = Relay.getInjectedTypeHandler(type);
    value = handler.deserialize(value);
  }
}

// define handler with:
Relay.injectTypeHandler("DateTime", {
  deserialize: (value) => ...,
  serialize: (value) => ...,
});

cc @yungsters

from relay.

josephsavona avatar josephsavona commented on May 7, 2024

Edit: summarizing on offline discussion with @yungsters - given the complexity of the "ideal" solution I described above, we're inclined to not add support for deserializing complex values right now. Doing so could conflict with other planned improvements such as querying local/device data and representing the cache as an immutable object.

We'd be curious to see how you approach this in a custom network layer and see about integrating that work down the road.

from relay.

KyleAMathews avatar KyleAMathews commented on May 7, 2024

For me not super high as a) it'll be a month or so before I start
implementing relay and b) it's pretty easy to just hydrate in views for now.
On Tue, Aug 18, 2015 at 12:56 PM Joseph Savona [email protected]
wrote:

How would you rate the priority of this?


Reply to this email directly or view it on GitHub
#91 (comment).

from relay.

taion avatar taion commented on May 7, 2024

It's of very low importance to me as well - it's easy enough to handle this in my own code for now.

from relay.

josephsavona avatar josephsavona commented on May 7, 2024

Thanks for the feedback. I'll close this for now but feel free to reopen later!

from relay.

taion avatar taion commented on May 7, 2024

On using this pattern a bit, I find that there's a bit of a complication in that naively dehydrating data from Relay on-the-fly causes issues with breaking reference equality, which breaks using shallow equality for pure components.

I've taken to doing something like https://gist.github.com/taion/df126b9252825927b037 to avoid e.g. breaking referential equality on blob1 when e.g. blob2 changes because arg changes.

from relay.

taion avatar taion commented on May 7, 2024

Here's an additional problem.

Suppose I have a field that deserializes into a non-scalar type. If I use this field in multiple nested components, I have a choice of either deserializing this in each component, or doing this once at top-level and passing through the deserialized value.

Neither is ideal. If I deserialize at top-level, the non-scalar prop defeats the Relay container shouldComponentUpdate check, and I get excessive re-renders. If I deserialize in each component, then I'm doing work multiple times for no good reason.

from relay.

miracle2k avatar miracle2k commented on May 7, 2024

So I tried to use this to implement a JSON scalar. I managed to hook up a handler like this:

fragment {
   config @__clientField(handle: "json"),
  ...
}

And pass handlerProvider to the Relay Environment, which looks like this:

import {ConnectionHandler} from 'relay-runtime';

function updateJSON(proxy, payload) {
  const record = proxy.get(payload.dataID);
  if (!record) {
    return;
  }
  const parsed = JSON.parse(record.getValue(payload.fieldKey));

  // Whatever the value you set for payload.handleKey is what the React component will see.
  record.setValue(parsed, payload.handleKey);
}

function handlerProvider(handle) {
  if (handle === 'json')
    return {update: updateJSON};

  if (handle === 'connection')
    return ConnectionHandler;
}

But this doesn't work, because Record.setValue() doesn't allow objects:

 RelayRecordProxy#setValue(): Expected a scalar value, got ...

This means we can also not parse Dates.

from relay.

apalm avatar apalm commented on May 7, 2024

Should this issue be reopened? As @miracle2k mentioned, it seems it's not currently possible to handle the cases discussed in this thread (e.g. Date, JSON) via handlerProvider because of the scalar values restriction.

from relay.

taion avatar taion commented on May 7, 2024

JSON "just works" even with Relay Modern w/graphql-type-json, BTW.

from relay.

miracle2k avatar miracle2k commented on May 7, 2024

@taion I use the same setup as graphql-type-json, and it largely seemed to work at first, but if you try to update such a JSON field that is already in the store you'll get errors (for example, through a second query, or by reading a mutation payload). There is code in Relay Modern that tries to, I think, merge the new objects into the old ones to keep as much as possible their identify if they didn't change, and it recurses into the JSON structures.

I don't have the exact file/line saved unfortunately.

from relay.

wincent avatar wincent commented on May 7, 2024

Thanks for the input here. We're currently going through old issues that appear to have gone stale (ie. not updated in about the last 3 months) because the volume of material in the issue tracker is becoming hard to manage. If this is still important to you please comment and we'll re-open this.

Thanks once again!

from relay.

anhthiencao avatar anhthiencao commented on May 7, 2024

asdasda

from relay.

var-outlook-ie avatar var-outlook-ie commented on May 7, 2024

test va

from relay.

Charlotte-z avatar Charlotte-z commented on May 7, 2024

awd

from relay.

Related Issues (20)

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.