amplitude / redux-query

A library for managing network state in Redux
https://amplitude.github.io/redux-query
Other
1.1k stars 67 forks source link

Feature request: `update` key of query config as a function #83

Closed jwindridge closed 7 years ago

jwindridge commented 7 years ago

First off, thanks for the library - it's brilliant!

Here's my use case as a motivating example:

I'm using normalizr for my schema definitions, and in attempt to prevent boilerplate code, have written the following utility functions to dynamically generate the update & transform keys for my query definitions:


import { schema, normalize } from 'normalizr'

const BarSchema= new schema.Entity('bars', {})
const FooSchema = new schema.Entity('foos', { bar: BarSchema })

function isSchema (target) {
  if (Array.isArray(target)) {
    return target.length && isSchema(target[0])
  } else {
    return target.hasOwnProperty('schema')
  }
}

const recursiveCollect = (target, visited = []) => {

  const entities = []
  const visitedSchemas = [...visited]

  if (isSchema(target)) {
    entities.push(target.key)
    visitedSchemas.push(target)
  }

  if (Array.isArray(target) || target instanceof schema.Array) {
    /* 
     * If the current target is an ArraySchema, call `recursiveCollect` 
     * on the underlying entity schema 
     */
    return recursiveCollect(target.schema, visitedSchemas)
  }

  Object.keys(target.schema).filter(x => x[0] !== '_').forEach(definition => {

    const childSchema= target.schema[definition]
    const alreadyVisited = visitedSchemas.includes(childSchema)

    if (isSchema(childSchema) && !alreadyVisited) {
      /* Only call `recursiveCollect` on the child schema if it hasn't 
       * already been encountered
       */
      const result = recursiveCollect(childSchema, visitedSchemas)

      result.entities.forEach(x => entities.push(x))
      result.visitedSchemas.forEach(x => visitedSchemas.push(x))
    }
  })

  return { entities, visitedSchemas }
}

export function collectEntityDefinitions (target) => {
  const { entities } = recursiveCollect(target)
  return entities
}

const getTransform = schema => data => normalize(data, schema).entities

function getUpdate (schema) {
  const entities = collectEntityDefinitions(schema)

  return Object.assign(
    {},
    ...entities.map(key => ({ [key]: (prev, next) => merge({}, prev, next)
  )
}

const queryConfig = {
  ...,
  transform: getTransform(fooSchema),
  update: getUpdate(fooSchema),
  ...
}

This implementation of collectEntityDefinitions, while it works, seems a bit hacky/ugly and therefore I was wondering if there was scope for update to optionally take a function instead of an object, where that function would be called with the whole entities state tree & the output from transform?

ryanashcraft commented 7 years ago

Hey @authentik8. Glad you're getting value out of this project! And thanks for the suggestion.

I see your use case here, but I am concerned how this will complicate the API when there's a decent user-space solution. I'm also hesitant to encourage dynamically generating entities schemas, as it can make things seem more magical and confusing than it already is.

ryanashcraft commented 7 years ago

Closing this issue for now but if you'd like to continue discussing feel free to reopen. :)