Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(swagger-ui-react): rewrite into SSR compatible function component #9855

Merged
merged 8 commits into from
Apr 22, 2024
227 changes: 121 additions & 106 deletions flavors/swagger-ui-react/index.jsx
Original file line number Diff line number Diff line change
@@ -1,101 +1,148 @@
import React from "react"
/**
* @prettier
*/
"use client"
char0n marked this conversation as resolved.
Show resolved Hide resolved
import React, { useEffect, useCallback, useState, useRef } from "react"
import PropTypes from "prop-types"
import SwaggerUIConstructor from "#swagger-ui"

class SwaggerUI extends React.Component {
constructor (props) {
super(props)
this.SwaggerUIComponent = null
this.system = null
}
const SwaggerUI = ({
spec = SwaggerUIConstructor.defaultOptions.url,
char0n marked this conversation as resolved.
Show resolved Hide resolved
url = SwaggerUIConstructor.defaultOptions.url,
layout = SwaggerUIConstructor.defaultOptions.layout,
requestInterceptor = SwaggerUIConstructor.defaultOptions.requestInterceptor,
responseInterceptor = SwaggerUIConstructor.defaultOptions.responseInterceptor,
supportedSubmitMethods = SwaggerUIConstructor.defaultOptions
.supportedSubmitMethods,
queryConfigEnabled = SwaggerUIConstructor.defaultOptions.queryConfigEnabled,
plugins = SwaggerUIConstructor.defaultOptions.plugins,
displayOperationId = SwaggerUIConstructor.defaultOptions.displayOperationId,
showMutatedRequest = SwaggerUIConstructor.defaultOptions.showMutatedRequest,
docExpansion = SwaggerUIConstructor.defaultOptions.docExpansion,
defaultModelExpandDepth = SwaggerUIConstructor.defaultOptions
.defaultModelExpandDepth,
defaultModelsExpandDepth = SwaggerUIConstructor.defaultOptions
.defaultModelsExpandDepth,
defaultModelRendering = SwaggerUIConstructor.defaultOptions
.defaultModelRendering,
presets = SwaggerUIConstructor.defaultOptions.presets,
deepLinking = SwaggerUIConstructor.defaultOptions.deepLinking,
showExtensions = SwaggerUIConstructor.defaultOptions.showExtensions,
showCommonExtensions = SwaggerUIConstructor.defaultOptions
.showCommonExtensions,
filter = SwaggerUIConstructor.defaultOptions.filter,
requestSnippetsEnabled = SwaggerUIConstructor.defaultOptions
.requestSnippetsEnabled,
requestSnippets = SwaggerUIConstructor.defaultOptions.requestSnippets,
tryItOutEnabled = SwaggerUIConstructor.defaultOptions.tryItOutEnabled,
displayRequestDuration = SwaggerUIConstructor.defaultOptions
.displayRequestDuration,
withCredentials = SwaggerUIConstructor.defaultOptions.withCredentials,
persistAuthorization = SwaggerUIConstructor.defaultOptions
.persistAuthorization,
oauth2RedirectUrl = SwaggerUIConstructor.defaultOptions.oauth2RedirectUrl,
onComplete = null,
}) => {
const systemRef = useRef(null)
const SwaggerUIComponentRef = useRef(null)
const [, forceUpdate] = useState(undefined)
char0n marked this conversation as resolved.
Show resolved Hide resolved

componentDidMount() {
const ui = SwaggerUIConstructor({
plugins: this.props.plugins,
spec: this.props.spec,
url: this.props.url,
const handleComplete = useCallback(() => {
if (typeof onComplete === "function") {
onComplete()
}
}, [onComplete])

useEffect(() => {
const system = SwaggerUIConstructor({
plugins,
spec,
url,
dom_id: null,
domNode: null,
layout: this.props.layout,
defaultModelsExpandDepth: this.props.defaultModelsExpandDepth,
defaultModelRendering: this.props.defaultModelRendering,
presets: [SwaggerUIConstructor.presets.apis, ...this.props.presets],
requestInterceptor: this.props.requestInterceptor,
responseInterceptor: this.props.responseInterceptor,
onComplete: this.onComplete,
docExpansion: this.props.docExpansion,
supportedSubmitMethods: this.props.supportedSubmitMethods,
queryConfigEnabled: this.props.queryConfigEnabled,
defaultModelExpandDepth: this.props.defaultModelExpandDepth,
displayOperationId: this.props.displayOperationId,
tryItOutEnabled: this.props.tryItOutEnabled,
displayRequestDuration: this.props.displayRequestDuration,
requestSnippetsEnabled: this.props.requestSnippetsEnabled,
requestSnippets: this.props.requestSnippets,
showMutatedRequest: this.props.showMutatedRequest,
deepLinking: this.props.deepLinking,
showExtensions: this.props.showExtensions,
showCommonExtensions: this.props.showCommonExtensions,
filter: this.props.filter,
persistAuthorization: this.props.persistAuthorization,
withCredentials: this.props.withCredentials,
...(typeof this.props.oauth2RedirectUrl === "string" ? { oauth2RedirectUrl: this.props.oauth2RedirectUrl} : {})
layout,
defaultModelsExpandDepth,
defaultModelRendering,
presets: [SwaggerUIConstructor.presets.apis, ...presets],
requestInterceptor,
responseInterceptor,
onComplete: handleComplete,
docExpansion,
supportedSubmitMethods,
queryConfigEnabled,
defaultModelExpandDepth,
displayOperationId,
tryItOutEnabled,
displayRequestDuration,
requestSnippetsEnabled,
requestSnippets,
showMutatedRequest,
deepLinking,
showExtensions,
showCommonExtensions,
filter,
persistAuthorization,
withCredentials,
...(typeof oauth2RedirectUrl === "string"
? { oauth2RedirectUrl: oauth2RedirectUrl }
: {}),
})
const SwaggerUIComponent = system.getComponent("App", "root")

this.system = ui
this.SwaggerUIComponent = ui.getComponent("App", "root")

this.forceUpdate()
}
systemRef.current = system
SwaggerUIComponentRef.current = SwaggerUIComponent
forceUpdate(true)

render() {
return this.SwaggerUIComponent ? <this.SwaggerUIComponent /> : null
}

componentDidUpdate(prevProps) {
const prevStateUrl = this.system.specSelectors.url()
if(this.props.url !== prevStateUrl || this.props.url !== prevProps.url) {
// flush current content
this.system.specActions.updateSpec("")
return () => {
systemRef.current = null
SwaggerUIComponentRef.current = null
}
}, [])

if(this.props.url) {
// update the internal URL
this.system.specActions.updateUrl(this.props.url)
// trigger remote definition fetch
this.system.specActions.download(this.props.url)
useEffect(() => {
if (systemRef.current) {
const prevStateUrl = systemRef.current.specSelectors.url()
if (url !== prevStateUrl) {
systemRef.current.specActions.updateSpec("")
if (url) {
systemRef.current.specActions.updateUrl(url)
systemRef.current.specActions.download(url)
}
}
}

const prevStateSpec = this.system.specSelectors.specStr()
if(this.props.spec && (this.props.spec !== prevStateSpec || this.props.spec !== prevProps.spec)) {
if(typeof this.props.spec === "object") {
this.system.specActions.updateSpec(JSON.stringify(this.props.spec))
} else {
this.system.specActions.updateSpec(this.props.spec)
const prevStateSpec = systemRef.current.specSelectors.specStr()
if (spec && spec !== prevStateSpec) {
const updatedSpec =
typeof spec === "object" ? JSON.stringify(spec) : spec
systemRef.current.specActions.updateSpec(updatedSpec)
}
}
}
}, [url, spec])

onComplete = () => {
if (typeof this.props.onComplete === "function") {
return this.props.onComplete(this.system)
}
}
return SwaggerUIComponentRef.current ? (
<SwaggerUIComponentRef.current />
) : null
}

SwaggerUI.propTypes = {
spec: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
spec: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
url: PropTypes.string,
layout: PropTypes.string,
requestInterceptor: PropTypes.func,
responseInterceptor: PropTypes.func,
onComplete: PropTypes.func,
docExpansion: PropTypes.oneOf(["list", "full", "none"]),
supportedSubmitMethods: PropTypes.arrayOf(
PropTypes.oneOf(["get", "put", "post", "delete", "options", "head", "patch", "trace"])
PropTypes.oneOf([
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
])
),
queryConfigEnabled: PropTypes.bool,
plugins: PropTypes.oneOfType([
Expand All @@ -112,10 +159,7 @@ SwaggerUI.propTypes = {
deepLinking: PropTypes.bool,
showExtensions: PropTypes.bool,
showCommonExtensions: PropTypes.bool,
filter: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
filter: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
requestSnippetsEnabled: PropTypes.bool,
requestSnippets: PropTypes.object,
tryItOutEnabled: PropTypes.bool,
Expand All @@ -125,35 +169,6 @@ SwaggerUI.propTypes = {
oauth2RedirectUrl: PropTypes.string,
}

SwaggerUI.defaultProps = {
spec: SwaggerUIConstructor.defaultOptions.spec,
url: SwaggerUIConstructor.defaultOptions.url,
layout: SwaggerUIConstructor.defaultOptions.layout,
requestInterceptor: SwaggerUIConstructor.defaultOptions.requestInterceptor,
responseInterceptor: SwaggerUIConstructor.defaultOptions.responseInterceptor,
supportedSubmitMethods: SwaggerUIConstructor.defaultOptions.supportedSubmitMethods,
queryConfigEnabled: SwaggerUIConstructor.defaultOptions.queryConfigEnabled,
plugins: SwaggerUIConstructor.defaultOptions.plugins,
displayOperationId: SwaggerUIConstructor.defaultOptions.displayOperationId,
showMutatedRequest: SwaggerUIConstructor.defaultOptions.showMutatedRequest,
docExpansion: SwaggerUIConstructor.defaultOptions.docExpansion,
defaultModelExpandDepth: SwaggerUIConstructor.defaultOptions.defaultModelExpandDepth,
defaultModelsExpandDepth: SwaggerUIConstructor.defaultOptions.defaultModelsExpandDepth,
defaultModelRendering: SwaggerUIConstructor.defaultOptions.defaultModelRendering,
presets: SwaggerUIConstructor.defaultOptions.presets,
deepLinking: SwaggerUIConstructor.defaultOptions.deepLinking,
showExtensions: SwaggerUIConstructor.defaultOptions.showExtensions,
showCommonExtensions: SwaggerUIConstructor.defaultOptions.showCommonExtensions,
filter: SwaggerUIConstructor.defaultOptions.filter,
requestSnippetsEnabled: SwaggerUIConstructor.defaultOptions.requestSnippetsEnabled,
requestSnippets: SwaggerUIConstructor.defaultOptions.requestSnippets,
tryItOutEnabled: SwaggerUIConstructor.defaultOptions.tryItOutEnabled,
displayRequestDuration: SwaggerUIConstructor.defaultOptions.displayRequestDuration,
withCredentials: SwaggerUIConstructor.defaultOptions.withCredentials,
persistAuthorization: SwaggerUIConstructor.defaultOptions.persistAuthorization,
oauth2RedirectUrl: undefined,
}

SwaggerUI.System = SwaggerUIConstructor.System
SwaggerUI.presets = SwaggerUIConstructor.presets
SwaggerUI.plugins = SwaggerUIConstructor.plugins
Expand Down