SO Relay QueryRenderer - TypeError:this.props.render 不是函数
Posted
技术标签:
【中文标题】SO Relay QueryRenderer - TypeError:this.props.render 不是函数【英文标题】:SO Relay QueryRenderer - TypeError: this.props.render is not a function 【发布时间】:2018-12-09 16:26:12 【问题描述】:我在 React 中有一个使用 Redux 和 Relay 的项目。客户端使用 GraphQL 连接到 API 服务器。我试图使用组件 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.
这是源文件:
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
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
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
import graphql from 'react-relay';
/**
* Gets all the featured stores.
*/
export default graphql`
query FeaturedStoresQuery
featuredStores
URI
name
category
address
city
image
`;
如何解决这个问题并使用 QueryRenderer 渲染我的组件?
【问题讨论】:
很难弄清楚,但是错误指向根索引组件错误,所以尝试只使用没有翻译的简单组件,然后尝试使用翻译的简单组件,我不认为 graphql 中继有问题,它可能是 t =>渲染是错误的,但它可能不会...先尝试简单的版本 【参考方案1】:QueryRenderer
非常明确地要求 render
属性必须是:
函数类型 (error, props, retry) => React.Node.https://relay.dev/docs/en/query-renderer.html#props
如果查看relay源码,可以看到这个render
prop被调用为函数here。
这会在尝试将 redux 连接的组件、类组件或除函数之外的任何东西传递给 QueryRenderer
的 render
属性时导致问题。
例如,react-redux
中的 connect
函数不返回函数。它返回一个对象。因此,如果您将 redux 连接的组件传递给 QueryRenderer
,当它尝试调用此 render
属性时,您看到的 "TypeError: this.props.render is not a function"
错误将被抛出。尝试传递类组件会导致类似的错误,"TypeError: Cannot call a class as a function"
。
解决这个问题的一个简单方法是包装有问题的connected/class/etc。一个简单的渲染函数中的组件,如下所示:
const ConnectedComponent =
connect(mapStateToProps, mapDispatchToProps)(Component);
const QueryComponent = () => (
<QueryRenderer
environment=environment
query=query
render=(props) => <ConnectedComponent ...props />
/>
);
在您的情况下,那个杂散的尖括号导致根本没有 render
属性被传递给 QueryRenderer
,但如上所述,通过许多其他方式也很容易遇到此问题。
【讨论】:
【参考方案2】:问题现在解决了! 我删除了 Redux 并修复了 query 和渲染道具。
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>
);
我还修改了渲染功能。似乎第二个参数必须按字面意思命名为 props。不能叫别的名字! (stores 是之前在我的示例代码中使用的名称)。
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>
);
【讨论】:
【参考方案3】:在添加 Redux 和 Relay 之前,翻译工作正常。有人告诉我 Relay 可以管理中心化状态,所以我会在尝试其他方法之前尝试删除 Redux。也许两者都在折叠渲染。
【讨论】:
以上是关于SO Relay QueryRenderer - TypeError:this.props.render 不是函数的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Relay QueryRenderer 道具更新状态