简体   繁体   English

SO Relay QueryRenderer - TypeError:this.props.render 不是函数

[英]SO Relay QueryRenderer - TypeError: this.props.render is not a function

I have a project in React using Redux and Relay .我在React 中有一个使用ReduxRelay的项目。 The client connects to an API Server using GraphQL.客户端使用 GraphQL 连接到 API 服务器。 I was trying to use the component QueryRenderer and I'm getting the following error:我试图使用组件QueryRenderer ,但出现以下错误:

TypeError: this.props.render is not a function
render
src/react-landing/node_modules/react-relay/lib/ReactRelayQueryRenderer.js:164

  161 |   if (process.env.NODE_ENV !== 'production') {
  162 |     deepFreeze(renderProps);
  163 |   }
> 164 |   return this.props.render(renderProps);
  165 | };
  166 | 
  167 | return ReactRelayQueryRenderer;

View compiled
finishClassComponent
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:13193

  13190 | } else {
  13191 |   {
  13192 |     ReactDebugCurrentFiber.setCurrentPhase('render');
> 13193 |     nextChildren = instance.render();
  13194 |     if (debugRenderPhaseSideEffects || debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) {
  13195 |       instance.render();
  13196 |     }

View compiled
updateClassComponent
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:13155

  13152 |   } else {
  13153 |     shouldUpdate = updateClassInstance(current, workInProgress, renderExpirationTime);
  13154 |   }
> 13155 |   return finishClassComponent(current, workInProgress, shouldUpdate, hasContext, renderExpirationTime);
  13156 | }
  13157 | 
  13158 | function finishClassComponent(current, workInProgress, shouldUpdate, hasContext, renderExpirationTime) {

View compiled
beginWork
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:13824

  13821 | case FunctionalComponent:
  13822 |   return updateFunctionalComponent(current, workInProgress);
  13823 | case ClassComponent:
> 13824 |   return updateClassComponent(current, workInProgress, renderExpirationTime);
  13825 | case HostRoot:
  13826 |   return updateHostRoot(current, workInProgress, renderExpirationTime);
  13827 | case HostComponent:

View compiled
performUnitOfWork
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:15863

  15860 |   startBaseRenderTimer();
  15861 | }
  15862 | 
> 15863 | next = beginWork(current, workInProgress, nextRenderExpirationTime);
  15864 | 
  15865 | if (workInProgress.mode & ProfileMode) {
  15866 |   // Update "base" time if the render wasn't bailed out on.

View compiled
workLoop
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:15902

  15899 | if (!isAsync) {
  15900 |   // Flush all expired work.
  15901 |   while (nextUnitOfWork !== null) {
> 15902 |     nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  15903 |   }
  15904 | } else {
  15905 |   // Flush asynchronous work until the deadline runs out of time.

View compiled
callCallback
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:100

   97 |   // nested call would trigger the fake event handlers of any call higher
   98 |   // in the stack.
   99 |   fakeNode.removeEventListener(evtType, callCallback, false);
> 100 |   func.apply(context, funcArgs);
  101 |   didError = false;
  102 | }
  103 | 

View compiled
invokeGuardedCallbackDev
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:138

  135 | // Synchronously dispatch our fake event. If the user-provided function
  136 | // errors, it will trigger our global error handler.
  137 | evt.initEvent(evtType, false, false);
> 138 | fakeNode.dispatchEvent(evt);
  139 | 
  140 | if (didError) {
  141 |   if (!didSetError) {

View compiled
invokeGuardedCallback
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:187

  184 |  * @param {...*} args Arguments for function
  185 |  */
  186 | invokeGuardedCallback: function (name, func, context, a, b, c, d, e, f) {
> 187 |   invokeGuardedCallback$1.apply(ReactErrorUtils, arguments);
  188 | },
  189 | 
  190 | /**

View compiled
replayUnitOfWork
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:15310

  15307 | // Replay the begin phase.
  15308 | isReplayingFailedUnitOfWork = true;
  15309 | originalReplayError = thrownValue;
> 15310 | invokeGuardedCallback$2(null, workLoop, null, isAsync);
  15311 | isReplayingFailedUnitOfWork = false;
  15312 | originalReplayError = null;
  15313 | if (hasCaughtError()) {

View compiled
renderRoot
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:15962

  15959 | 
  15960 | var failedUnitOfWork = nextUnitOfWork;
  15961 | if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
> 15962 |   replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
  15963 | }
  15964 | 
  15965 | // TODO: we already know this isn't true in some cases.

View compiled
performWorkOnRoot
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16560

  16557 |   // This root is already complete. We can commit it.
  16558 |   completeRoot(root, finishedWork, expirationTime);
  16559 | } else {
> 16560 |   finishedWork = renderRoot(root, expirationTime, false);
  16561 |   if (finishedWork !== null) {
  16562 |     // We've completed the root. Commit it.
  16563 |     completeRoot(root, finishedWork, expirationTime);

View compiled
performWork
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16482

  16479 |   }
  16480 | } else {
  16481 |   while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
> 16482 |     performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
  16483 |     findHighestPriorityRoot();
  16484 |   }
  16485 | }

View compiled
performSyncWork
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16454

  16451 | }
  16452 | 
  16453 | function performSyncWork() {
> 16454 |   performWork(Sync, false, null);
  16455 | }
  16456 | 
  16457 | function performWork(minExpirationTime, isAsync, dl) {

View compiled
requestWork
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16354

  16351 | 
  16352 | // TODO: Get rid of Sync and use current time?
  16353 | if (expirationTime === Sync) {
> 16354 |   performSyncWork();
  16355 | } else {
  16356 |   scheduleCallbackWithExpiration(expirationTime);
  16357 | }

View compiled
scheduleWork$1
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16218

  16215 | !isWorking || isCommitting$1 ||
  16216 | // ...unless this is a different root than the one we're rendering.
  16217 | nextRoot !== root) {
> 16218 |   requestWork(root, nextExpirationTimeToWorkOn);
  16219 | }
  16220 | if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
  16221 |   invariant(false, 'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.');

View compiled
scheduleRootUpdate
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16785

  16782 |   }
  16783 |   enqueueUpdate(current, update, expirationTime);
  16784 | 
> 16785 |   scheduleWork$1(current, expirationTime);
  16786 |   return expirationTime;
  16787 | }
  16788 | 

View compiled
updateContainerAtExpirationTime
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16812

  16809 |     container.pendingContext = context;
  16810 |   }
  16811 | 
> 16812 |   return scheduleRootUpdate(current, element, expirationTime, callback);
  16813 | }
  16814 | 
  16815 | function findHostInstance(component) {

View compiled
updateContainer
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16839

  16836 |   var current = container.current;
  16837 |   var currentTime = recalculateCurrentTime();
  16838 |   var expirationTime = computeExpirationForFiber(currentTime, current);
> 16839 |   return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback);
  16840 | }
  16841 | 
  16842 | function getPublicRootInstance(container) {

View compiled
./node_modules/react-dom/cjs/react-dom.development.js/ReactRoot.prototype.render
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:17122

  17119 |   if (callback !== null) {
  17120 |     work.then(callback);
  17121 |   }
> 17122 |   updateContainer(children, root, null, work._onCommit);
  17123 |   return work;
  17124 | };
  17125 | ReactRoot.prototype.unmount = function (callback) {

View compiled
legacyRenderSubtreeIntoContainer/<
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:17262

  17259 |     if (parentComponent != null) {
  17260 |       root.legacy_renderSubtreeIntoContainer(parentComponent, children, callback);
  17261 |     } else {
> 17262 |       root.render(children, callback);
  17263 |     }
  17264 |   });
  17265 | } else {

View compiled
unbatchedUpdates
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:16679

  16676 |       isUnbatchingUpdates = false;
  16677 |     }
  16678 |   }
> 16679 |   return fn(a);
  16680 | }
  16681 | 
  16682 | // TODO: Batching should be implemented at the renderer level, not within

View compiled
legacyRenderSubtreeIntoContainer
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:17258

  17255 |   };
  17256 | }
  17257 | // Initial mount should not be batched.
> 17258 | unbatchedUpdates(function () {
  17259 |   if (parentComponent != null) {
  17260 |     root.legacy_renderSubtreeIntoContainer(parentComponent, children, callback);
  17261 |   } else {

View compiled
render
src/react-landing/node_modules/react-dom/cjs/react-dom.development.js:17317

  17314 |   return legacyRenderSubtreeIntoContainer(null, element, container, true, callback);
  17315 | },
  17316 | render: function (element, container, callback) {
> 17317 |   return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
  17318 | },
  17319 | unstable_renderSubtreeIntoContainer: function (parentComponent, element, containerNode, callback) {
  17320 |   !(parentComponent != null && has(parentComponent)) ? invariant(false, 'parentComponent must be a valid React Component') : void 0;

View compiled
./src/index.js
src/react-landing/src/index.js:15

  12 | import '../node_modules/font-awesome/css/font-awesome.min.css';
  13 | 
  14 | 
> 15 | ReactDOM.render(
  16 |   <Provider store={ store }>
  17 |     <I18nextProvider i18n={ i18n }>
  18 |       <App />

View compiled
▶ 6 stack frames were collapsed.

This are the source files:这是源文件:

src/components/HomePage/Header/Header.jsx src/components/HomePage/Header/Header.jsx

import React from 'react';
import { connect } from "react-redux";
import { I18n } from 'react-i18next';
import { QueryRenderer } from 'react-relay';

import environment from '../../../relay/environment';
import featuredStores from './FeaturedStores';
import SearchBox from '../../SearchBox/SearchBox';

import './Header.css';


const mapStateToProps = state => {
  return {
    query: state.storeService.getAllFeatured()
  };
};

const Header = ({ query }) => (
  <I18n>
    {
      (t) => (
        <div className="background">
          <ul className="cb-slideshow">
            <li><span>Image 01</span></li>
            <li><span>Image 02</span></li>
            <li><span>Image 03</span></li>
          </ul>
          <div className="banner">
            <div className="container">
              <div className="banner-info">
                <h2>{ t('home-page.header.title') }</h2>
                <p>{ t('home-page.header.description') }</p>
              </div>
              <div className="banner-grads">
                <QueryRenderer environment={ environment } query={ query }> render={ featuredStores }></QueryRenderer>

                <div className="clearfix"></div>

                <SearchBox />
              </div>
            </div>
          </div>
        </div>
      )
    }
  </I18n>
);

export default connect(mapStateToProps)(Header);

src/components/HomePage/Header/FeaturedStores.jsx src/components/HomePage/Header/FeaturedStores.jsx

import React from 'react';
import Spinner from 'react-spinkit';

/**
 * FeaturedStores component.
 */
export default ({ error, stores }) => {
  if (error) {
    return <div>Error!</div>;
  }

  if (!stores) {
    return <Spinner name="line-scale" color="blue" />;
  }

  return (
    <div>
      {
        stores.map((store, key) => {
          return (
            <div className="col-md-4 banner-grad" key={ key }>
              <div className="banner-grad-img">
                <img src={ store.image } alt={ store.name } />
                <h4>{ store.name }</h4>
                <p>
                  <span className="storeDescription">{ store.description }</span>
                  <br /> { store.address }, { store.city }
                </p>
              </div>
            </div>
          );
        })
      }
    </div>
  );
}

src/relay/services/StoreService.jsx源代码/中继/服务/StoreService.jsx

import storesQuery from '../queries/StoresQuery';
import featuredStoresQuery from '../queries/FeaturedStoresQuery';
import storeQuery from '../queries/StoreQuery';
import storesByMenuItemQuery from '../queries/StoresByMenuItemQuery';


/** Limit of stores per request. */
const LIMIT = 24

/**
 * class :: StoreService
 *
 * Service for Store types.
 */
class StoreService {
  /**
   * Constructor.
   */
  constructor() {
    this.storesQuery = storesQuery;
    this.storeQuery = storeQuery;
    this.featuredStoresQuery = featuredStoresQuery;
    this.storesByMenuItemQuery = storesByMenuItemQuery;
    this.searchFrom404 = false
    this.skipCounter = 0
  }

  /**
   * Resets the skip counter.
   */
  resetSkipCounter() {
    this.skipCounter = 0
  }

  /**
   * Gets all the stores using pagination.
   *
   * @returns {any} GraphQL query for retrieving the stores from the API server.
   */
  getAll() {
    this.skipCounter += LIMIT

    return this.storesQuery;
  }

  /**
   * Gets all the featured stores.
   *
   * @returns {any} GraphQL query for retrieving the featured stores from the API server.
   */
  getAllFeatured() {
    return this.featuredStoresQuery;
  }

  /**
   * Gets an store from the API server by its URI.
   *
   * @returns {any} GraphQL query for retrieving the store from the API server.
   */
  getStore() {
    return this.storeQuery;
  }

  /**
   * Gets all the stores from the API server that have the given item in their menues.
   *
   * @param {Boolean} searchFrom404 True if the search was performed from the SearchBox component.
   * @returns {any} GraphQL query for retrieving the stores from the API server.
   */
  getAllByMenuItem(searchFrom404) {
    this.searchFrom404 = searchFrom404 || false

    return this.storesByMenuItemQuery;
  }
}

/**
 * Singleton implementation.
 */
export default (function () {
  /** StoreService instance reference. */
  let instance = null

  return {
    /**
     * Gets a unique instance of StoreService.
     *
     * @returns {StoreService} A unique instance of StoreService.
     */
    getInstance: function () {
      if (!instance) {
        instance = new StoreService()
      }
      return instance
    }
  }
})()

src/relay/queries/FeaturedStoresQuery.js src/relay/queries/FeaturedStoresQuery.js

import { graphql } from 'react-relay';


/**
 * Gets all the featured stores.
 */
export default graphql`
  query FeaturedStoresQuery {
    featuredStores {
      URI
      name
      category
      address
      city
      image
    }
  }
`;

How can I solve this issue and render my component with QueryRenderer ?如何解决此问题并使用QueryRenderer呈现我的组件?

The problem is solved now!现在问题解决了! I removed Redux and fixed that extra " > " between query and render props.我删除了Redux并修复了查询渲染道具之间的额外“ > ”。

src/components/HomePage/Header/Header.jsx src/components/HomePage/Header/Header.jsx

import React, { Component } from 'react';
import { I18n } from 'react-i18next';
import { QueryRenderer } from 'react-relay';

import environment from '../../../relay/environment';
import query from '../../../relay/queries/FeaturedStoresQuery';
import featuredStores from './FeaturedStores';
import SearchBox from '../../SearchBox/SearchBox';

import './Header.css';


export default class Header extends Component {
  /**
   * Renders the component.
   * 
   * @returns {string} The component's JSX code.
   */
  render() {
    return (
      <I18n>
        {
          (t) => (
            <div className="background">
              <ul className="cb-slideshow">
                <li><span>Image 01</span></li>
                <li><span>Image 02</span></li>
                <li><span>Image 03</span></li>
              </ul>
              <div className="banner">
                <div className="container">
                  <div className="banner-info">
                    <h2>{ t('home-page.header.title') }</h2>
                    <p>{ t('home-page.header.description') }</p>
                  </div>
                  <div className="banner-grads">
                    <QueryRenderer environment={ environment } query={ query } render={ featuredStores } />

                    <div className="clearfix"></div>

                    <SearchBox />
                  </div>
                </div>
              </div>
            </div>
          )
        }
      </I18n>
    );
  }
}

I've also modified the rendering function.我还修改了渲染功能。 It seems that the second param has to be named as props literally.似乎第二个参数必须从字面上命名为道具 It can't be named differently!它不能有不同的命名! ( stores was the named used before in my example code). 商店是我之前在示例代码中使用的名称)。

src/components/HomePage/Header/FeaturedStores.jsx src/components/HomePage/Header/FeaturedStores.jsx

import React from 'react';
import Spinner from 'react-spinkit';

/**
 * FeaturedStores component.
 */
export default ({ error, props }) => {
  if (error) {
    return <div>Error!</div>;
  }

  if (!props) {
    return <Spinner name="line-scale" color="blue" />;
  }

  return (
    <div>
      {
        props.featuredStores.map((store, key) => {
          return (
            <div className="col-md-4 banner-grad" key={ key }>
              <div className="banner-grad-img">
                <img src={ store.image } alt={ store.name } />
                <h4>{ store.name }</h4>
                <p>
                  <span className="storeDescription">{ store.description }</span>
                  <br /> { store.address }, { store.city }
                </p>
              </div>
            </div>
          );
        })
      }
    </div>
  );
}

The translations was working just fine before adding Redux and Relay.在添加 Redux 和 Relay 之前,翻译工作得很好。 Someone told me that Relay can manage centralized states so I'll try to remove Redux before trying something else.有人告诉我 Relay 可以管理集中状态,所以我会在尝试其他东西之前尝试删除 Redux。 Maybe both together are collapsing the rendering.也许两者都在折叠渲染。

QueryRenderer very specifically requires that the render prop must be a: QueryRenderer非常明确地要求render道具必须是:

Function of type ({error, props, retry}) => React.Node.函数类型 ({error, props, retry}) => React.Node。
https://relay.dev/docs/en/query-renderer.html#props https://relay.dev/docs/en/query-renderer.html#props

If you look at the relay source code, you can see that this render prop is called as a function here .如果您查看中继源代码,您可以看到此render道具在此处作为函数调用。

This causes problems when trying to pass a redux-connected component, or a class component, or anything other than a function to QueryRenderer 's render prop.这会在尝试将与 redux 连接的组件、类组件或函数以外的任何内容传递给QueryRendererrender道具时出现问题。

As an example, the connect function from react-redux does not return a function.例如,来自react-reduxconnect函数不返回函数。 It returns an object.它返回一个对象。 And so, were you to pass a redux-connected component to QueryRenderer , when it would attempt to call this render prop, the "TypeError: this.props.render is not a function" error that you're seeing would be thrown.因此,如果您将与 redux 连接的组件传递给QueryRenderer ,当它尝试调用此render道具时,将"TypeError: this.props.render is not a function"您看到的"TypeError: this.props.render is not a function"错误。 Attempting to pass a class component would result in a similar error, "TypeError: Cannot call a class as a function" .尝试传递类组件会导致类似的错误, "TypeError: Cannot call a class as a function"

An easy solution to this problem is to wrap the problematic connected/class/etc.这个问题的一个简单解决方案是包装有问题的连接/类/等。 component in a simple render function, like so:一个简单的渲染函数中的组件,如下所示:

const ConnectedComponent =
    connect(mapStateToProps, mapDispatchToProps)(Component);

const QueryComponent = () => (
    <QueryRenderer
        environment={environment}
        query={query}
        render={(props) => <ConnectedComponent {...props} />}
    />
);

In your case, that stray angle bracket was resulting in no render prop at all being passed to QueryRenderer , but it's also very easy to encounter this issue in a number of other ways, as outlined above.在您的情况下,该杂散尖括号导致根本没有render道具被传递给QueryRenderer ,但QueryRenderer ,也很容易以其他多种方式遇到此问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM