Skip to content
This repository has been archived by the owner on Oct 10, 2023. It is now read-only.

Fix update props and connectWpPost slug entity resolution #49

Merged
merged 8 commits into from
Feb 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- Added `debug` config option, if true lib logs useful lifecycle information to the console.
- Big refactor of library to make codebase more maintainable, manageable.

---
---

- __v3.2.0__ - _23/09/16_

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Get data from WordPress and into components with ease...

```js
// e.g. Get a post by its slug
@connectWpPost('Post', 'spongebob-squarepants')
@connectWpPost('post', 'spongebob-squarepants')
export default class extends React.Component () {
render () {
const { post: spongebob } = this.props.kasia
Expand Down Expand Up @@ -273,7 +273,7 @@ export default connectWpPost(Page, (props) => props.params.slug)(Post)
Connect a component to the result of an arbitrary WP-API query. Query is always made with `?embed` query parameter.

- __queryFn__ {Function} Function that accepts args `wpapi`, `props`, `state` and should return a WP-API query
- __shouldUpdate__ {Function} Called on `componentWillReceiveProps`, return true to run query again
- __shouldUpdate__ {Function} Called on `componentWillReceiveProps` with args `thisProps`, `nextProps`, `state`

Returns a connected component.

Expand Down
59 changes: 30 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kasia",
"version": "4.0.20",
"version": "4.0.22",
"description": "A React Redux toolset for the WordPress API",
"main": "lib/index.js",
"jsnext:main": "src/index.js",
Expand Down Expand Up @@ -35,39 +35,40 @@
"bugs": "https://github.com/outlandishideas/kasia/issues",
"homepage": "https://github.com/outlandishideas/kasia#readme",
"dependencies": {
"humps": "^2.0.0",
"is-node-fn": "^1.0.0",
"lodash.merge": "^4.3.5",
"normalizr": "^2.0.1",
"pick-to-array": "^1.0.0",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"redux": "^3.4.0",
"redux-saga": "^0.13.0",
"wpapi": "^0.11.0"
"humps": "2.0.0",
"is-node-fn": "1.0.0",
"lodash.merge": "4.3.5",
"normalizr": "2.0.1",
"pick-to-array": "1.0.0",
"react": "15.0.1",
"react-dom": "15.0.1",
"react-redux": "5.0.2",
"redux-saga": "0.13.0",
"wpapi": "0.11.0"
},
"devDependencies": {
"babel-cli": "6.18.0",
"babel-core": "6.18.2",
"babel-eslint": "^7.1.1",
"babel-jest": "^17.0.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-polyfill": "^6.7.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
"check-node-version": "^1.1.2",
"coveralls": "^2.11.15",
"enzyme": "^2.2.0",
"in-publish": "^2.0.0",
"jest": "^18.1.0",
"jest-cli": "^17.0.3",
"babel-eslint": "7.1.1",
"babel-jest": "17.0.2",
"babel-plugin-add-module-exports": "0.2.1",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-polyfill": "6.7.4",
"babel-preset-es2015": "6.6.0",
"babel-preset-react": "6.5.0",
"babel-preset-stage-0": "6.5.0",
"check-node-version": "1.1.2",
"coveralls": "2.11.15",
"enzyme": "2.2.0",
"in-publish": "2.0.0",
"jest": "18.1.0",
"jest-cli": "17.0.3",
"lerna": "2.0.0-beta.31",
"react-addons-test-utils": "^15.3.0",
"rimraf": "^2.5.2",
"snazzy": "^5.0.0",
"standard": "^8.6.0"
"react-addons-test-utils": "15.3.0",
"redux": "3.4.0",
"rimraf": "2.5.2",
"snazzy": "5.0.0",
"standard": "8.6.0"
},
"standard": {
"env": [
Expand Down
96 changes: 53 additions & 43 deletions src/connect.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import { connect as reduxConnect } from 'react-redux'

import debug from './util/debug'
import contentTypesManager from './util/contentTypesManager'
Expand Down Expand Up @@ -34,10 +35,18 @@ export function wrapQueryFn (queryFn, props, state) {
return (wpapi) => queryFn(wpapi, props, state)
}

/** Wrap component in react-redux connect. */
function connect (cls) {
return reduxConnect(({ wordpress }) => {
invariants.hasWordpressObject(wordpress)
return { wordpress }
})(cls)
}

const base = (target) => {
const displayName = target.displayName || target.name

return class extends React.Component {
return class KasiaConnectedComponent extends React.Component {
static __kasia__ = true

static WrappedComponent = target
Expand All @@ -46,24 +55,18 @@ const base = (target) => {
store: React.PropTypes.object.isRequired
}

_getState () {
const state = this.context.store.getState()
invariants.hasWordpressObject(state.wordpress)
return state
}

/** Make request for new data from WP-API. Allow re-use of `queryId` in case of no query on mount. */
/** Make request for new data from WP-API. */
_requestWpData (props, queryId) {
const action = this._getRequestWpDataAction(props)
action.id = queryId
this.queryId = queryId
this.context.store.dispatch(action)
this.props.dispatch(action)
}

/** Find the query for this component and its corresponding data and return props object containing them. */
_reconcileWpData (props) {
const query = this._getState().wordpress.queries[this.queryId]
const data = this._makePropsData(query, props)
const query = this._query()
const data = this._makePropsData(props)
const fallbackQuery = { complete: false, OK: null }

if (query) {
Expand All @@ -89,8 +92,12 @@ const base = (target) => {
})
}

_query () {
return this.props.wordpress.queries[this.queryId]
}

componentWillMount () {
const state = this._getState().wordpress
const state = this.props.wordpress
const numQueries = Object.keys(state.queries).length - 1
const nextCounterIndex = queryCounter.current() + 1

Expand All @@ -112,20 +119,16 @@ const base = (target) => {
const queryId = this.queryId = queryCounter.next()
const query = state.queries[queryId]

if (query && query.prepared) {
// We found a prepared query matching `queryId` - use it.
debug(`found prepared data for ${displayName} at queryId=${queryId}`)
} if (!query) {
// Request new data and reuse the queryId
this._requestWpData(this.props, queryId)
} else if (!query.prepared) {
// Request new data with new queryId
this._requestWpData(this.props, queryCounter.next())
}
// We found a prepared query matching `queryId` - use it.
if (query && query.prepared) debug(`found prepared data for ${displayName} at queryId=${queryId}`)
// Did not find prepared query so request new data and reuse the queryId
else if (!query) this._requestWpData(this.props, queryId)
// Request new data with new queryId
else if (!query.prepared) this._requestWpData(this.props, queryCounter.next())
}

componentWillReceiveProps (nextProps) {
const willUpdate = this._shouldUpdate(this.props, nextProps)
const willUpdate = this._shouldUpdate(this.props, nextProps, this.context.store.getState())
if (willUpdate) this._requestWpData(nextProps, queryCounter.next())
}

Expand Down Expand Up @@ -171,7 +174,7 @@ export function connectWpPost (contentType, id) {

invariants.isNotWrapped(target, displayName)

return class connectWpPostComponent extends base(target) {
class KasiaConnectWpPostComponent extends base(target) {
constructor (props, context) {
super(props, context)
this.dataKey = contentType
Expand All @@ -191,32 +194,36 @@ export function connectWpPost (contentType, id) {
return createPostRequest(contentType, realId)
}

_makePropsData (query, props) {
_makePropsData (props) {
const query = this._query()

if (!query || !query.complete || query.error) return null

const entities = this.props.wordpress.entities[typeConfig.plural]
const keys = Object.keys(entities)
const realId = identifier(displayName, id, props)
const entities = this._getState().wordpress.entities[typeConfig.plural]
return entities[realId] || null

for (let i = 0, len = keys.length; i < len; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed earlier, can't we use the result of the query to know which ID(s) were returned, and then look them up in the entities array?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some content types, e.g. tag, don't have an id so for these types we fall back on their slug, and vice versa. Maybe there's a better way still?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is part of KasiaConnectWpPostComponent, so won't the data always be a post, so have an id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no, sorry... confusingly post is synonymous with content type here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't realise that. In that case, this should do until (unless) we find a better solution

const entity = entities[keys[i]]
if (entity.id === realId || entity.slug === realId) return entity
}

return null
}

_shouldUpdate (thisProps, nextProps) {
const state = this._getState().wordpress
const query = state.queries[this.queryId]
const entity = this._makePropsData(query, nextProps)

// Make a request for new data if..
return (
// ..we can't find the entity in store using next props
!entity &&
// ..the identifier has changed
identifier(displayName, id, nextProps) !== identifier(displayName, id, thisProps)
)
// Make a request for new data if entity not in store or the identifier has changed
const entity = this._makePropsData(nextProps)
return !entity && identifier(displayName, id, nextProps) !== identifier(displayName, id, thisProps)
}

componentWillMount () {
invariants.isValidContentType(typeConfig, contentType, `${displayName} component`)
super.componentWillMount()
}
}

return connect(KasiaConnectWpPostComponent)
}
}

Expand Down Expand Up @@ -264,7 +271,7 @@ export function connectWpQuery (queryFn, shouldUpdate) {

invariants.isNotWrapped(target, displayName)

return class connectWpQueryComponent extends base(target) {
class KasiaConnectWpQueryComponent extends base(target) {
constructor (props, context) {
super(props, context)
this.dataKey = 'data'
Expand All @@ -281,15 +288,18 @@ export function connectWpQuery (queryFn, shouldUpdate) {

_getRequestWpDataAction (props) {
debug(displayName, 'connectWpQuery request with props:', props)
const wrappedQueryFn = wrapQueryFn(queryFn, props, this._getState())
const wrappedQueryFn = wrapQueryFn(queryFn, props, this.context.store.getState())
return createQueryRequest(wrappedQueryFn)
}

_makePropsData (query) {
_makePropsData () {
const query = this._query()
const state = this.props.wordpress
if (!query || !query.complete || query.error) return {}
const state = this._getState().wordpress
return findEntities(state.entities, state.keyEntitiesBy, query.entities)
else return findEntities(state.entities, state.keyEntitiesBy, query.entities)
}
}

return connect(KasiaConnectWpQueryComponent)
}
}
2 changes: 1 addition & 1 deletion src/redux/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function completeReducer (normalise) {
normalise(action.data)
)

// The action id would be null the preloadQuery method has initiated
// The action id would be null if the preloadQuery method has initiated
// the completeRequest action as they do not need a query in the store
// (there is no component to pick it up).
if (typeof action.id === 'number') {
Expand Down
21 changes: 21 additions & 0 deletions test/__mocks__/components/ExplicitIdentifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* global jest:false */

jest.disableAutomock()

import React, { Component } from 'react'

import { ContentTypes } from '../../../src/constants'
import { connectWpPost } from '../../../src/connect'

export const target = class extends Component {
render () {
const { query, post } = this.props.kasia
if (!query.complete || !query.OK) return <div>Loading...</div>
return <div>{post.title.rendered}</div>
}
}

export default connectWpPost(
ContentTypes.Post,
'architecto-enim-omnis-repellendus'
)(target)
2 changes: 1 addition & 1 deletion test/__mocks__/states/initial.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default (keyEntitiesBy) => ({
export default (keyEntitiesBy = 'id') => ({
wordpress: {
keyEntitiesBy,
queries: {},
Expand Down
1 change: 1 addition & 0 deletions test/__mocks__/states/multipleEntities.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const post2 = Object.assign({}, postJson, {

export default {
wordpress: {
keyEntitiesBy: 'id',
queries: {},
entities: {
posts: {
Expand Down
Loading