humanmade / repress

Connect your Redux store to the WordPress REST API.
Other
83 stars 11 forks source link

Add hierarchical handler #3

Open rmccue opened 6 years ago

rmccue commented 6 years ago

I find myself having to repeat the dynamic-archive style for things like pages all the time. We should build this in.

rmccue commented 6 years ago

The code I'm using currently:


export const pages = new handler( {
    nonce: API_NONCE,
    type:  'page',
    url:   `${ API_ROOT }wp/v2/pages`,
    query: {
        _embed: '1',
    },
} );

// Add our extra tools.
export const normalizePath = path => path.replace( /^\/+|\/+$/g, '' );
const pathForPage = page => normalizePath( page.link.substr( SITE_HOME.length ) );
pages.archiveForPath = path => `pages/${ normalizePath( path ) }`;
pages.fetchPageByPath = path => {
    // Query by slug for the final path component.
    const normalized = normalizePath( path );
    const components = normalized.split( '/' );
    const id = pages.archiveForPath( path );
    pages.registerArchive( id, {
        slug: components.slice( -1 )[0],
    } );

    return pages.fetchArchive( id );
};
pages.getPageByPath = ( state, path ) => {
    const normalized = normalizePath( path );
    const allMatching = pages.getArchive( state.pages, pages.archiveForPath( normalized ) );
    if ( ! allMatching ) {
        return null;
    }

    // Whittle down to the only one that matches fully.
    return allMatching.find( page => pathForPage( page ) === normalized );
};
janicak commented 6 years ago

Would you be able to produce an extended example of how one can implement the dynamic-archive approach to slug-based routing? In this case, I'm not sure how to approach wiring the repress handler with react-router and the rendering page component. Many thanks for the cool library!

janicak commented 6 years ago

Nevermind, I think I figured it out! I was attempting to connect the rendering component with the redux state via the withArchive() HOC, but I'm guessing it's not designed to be extended to handle dispatching your custom action creator, pages.getPageByPath. In case it's helpful for anyone else:

Page.js:

import { connect } from 'react-redux';
import React, { Component } from 'react';

// rmccue's code above: https://github.com/humanmade/repress/issues/3#issuecomment-373575556
// renaming handler to disambiguate from redux state "pages"
import { pages as pagesHandler } from '../reducers/wp_types'; 

class Page extends Component {
  constructor(props){
    super(props);
    const page = pagesHandler.getPageByPath(props, props.location.pathname);
    if (!page){
      props.fetchPageByPath(props.location.pathname);
    }
    this.state = { page };
  }
  static getDerivedStateFromProps(nextProps, prevState){
    return { page: pagesHandler.getPageByPath(nextProps, nextProps.location.pathname) };
  }
  render(){
    const { page } = this.state;
    return (
      <div className="Page">
        { page ? <h1 dangerouslySetInnerHTML={ { __html: page.title.rendered } } /> : ''}
      </div>
    )
  }
}
const mapStateToProps = ({ pages }) => ({
  pages // redux store pages
});

const mapDispatchToProps = (dispatch) => ({
  fetchPageByPath: (path) => { dispatch(pagesHandler.fetchPageByPath(path)) }
});

export default connect(mapStateToProps, mapDispatchToProps)(Page);

index.js (router detail):

const AppContainer = () => (
  <ConnectedSwitch>
    <Route exact path="/" component={HomePosts}/>
    <Route path="/post/:id" component={Post} />
    <Route path="/:slug" component={Page} />
  </ConnectedSwitch>
);
rmccue commented 6 years ago

Yeah, the way to use it with withArchive is to build it into your component a bit more; pseudo-code is something like:

const ConnectedComponent = props => {
    const { loading, path, posts } = props;
    if ( loading ) {
        return <p>Loading</p>;
    }
    if ( ! posts ) {
        return <p>404</p>;
    }

    const matched = posts.find( post => post.url === SITE_BASE + path );
    if ( ! matched ) {
        return <p>Also 404</p>;
    }

    return <Page data={ matched } />;
};

export default withArchive(
    pages,
    state => state.pages,
    props => {
        const id = `_page/${ props.path }`;
        posts.registerArchive( id, {
            slug: props.path.split( '/' ).slice( -1 )[0],
        } );
        return id;
    }
);