Michael
Michael

Reputation: 111

React Redux & Wordpress API - how to load single post from Redux store?

I'm using the Wordpress API in combination with React and Redux. I managed to get several posts from the Wordpress Blog (from another URL) over the API. All works wonderful, I can get the posts cross domain and load them into the Redux store, render them on the page etc.

But one thing I can't get my head around: For the single post on one page, how can I access and render just one post from the store?

I think I'm quite close, I get the posts already when I straight dispatch the action.

But now for the single page, I have already my posts in the store. I connect to the store and want to get just the one post by the slug of the URL.

My idea is the following:

import { withRouter } from 'react-router-dom';
import { connect, mapStateToProps } from 'react-redux';    

@connect((store) => {
    return {
        posts: store.posts, 
        postsFetched: store.posts.fetched,
        users: store.users.users,
        usersFetched: store.users.fetched
    };
})

class SinglePost extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            title: 'Some test title',
            singlePost: '',
            pathname: ''
        }
    }


    componentWillMount()  {
        //Get the pathname
        var {pathname} = this.props.location;
        //remove the / in front of pathname and put pathname into parantheses
        if(pathname.charAt(0) === "/")
        var pathname = pathname.slice(1);
        console.log('pathname is:', pathname); 
        this.setState({
            pathname: pathname
        }); 

        for(var i = 0; i < this.props.posts.posts.length; i++) {
            if(this.props.posts.posts[i].slug == this.state.pathname){
                this.setState((prevState, props) => ({
                    singlePost: this.props.posts.posts[i],
                    title: this.props.posts.posts[i].title.rendered
                }));
                const singlePost = this.props.posts.posts[i];
                console.log('singlePost was fired');
            } else {
                console.log('singlePost was not fired');
            }
        }     
    }

    render() {   
        return (
            <div className="container">
                        <h2 className="text-center">{ this.state.singlePost.title }</h2>
            </div>
        );        
    }
}

export default connect(mapStateToProps) (SinglePost);

I get btw the pathname. I tried several other places for the for-loop (shouldComponentUpdate, componentWillReceiveProps etc), but either the component did not render or I got the singlePost.title but in an infinite loop.

Obviously I don't want to do anything with the API again when I already have loaded everything into the store, just get this on post out of the store.

Any ideas, things I could look into, suggestions?

Upvotes: 1

Views: 985

Answers (2)

Michael
Michael

Reputation: 111

Thanks to the answer of @Anthony I got a better basic idea to rather get one post by it's slug from the API (instead of filtering it out from all posts in the array)

Here is how my code looks:

import { withRouter } from 'react-router-dom';
import { connect, mapStateToProps } from 'react-redux';
import { getPostByURL } from 'redux/actions/postsActions.js';

@connect((store) => {
    return {
        singlePost: store.singlePost,
        singlePostFetched: store.singlePost.fetched,
        users: store.users.users,
        usersFetched: store.users.fetched
    };
})

class SinglePost extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            pathname: ''
        }
    }


    componentWillMount() {        
        //Get the pathname
        var {pathname} = this.props.location;

        //remove the / in front of pathname and put pathname into parantheses
        if(pathname.charAt(0) === "/")
        var pathname = pathname.slice(1);

        this.setState({
            pathname: pathname
        });     

        this.props.dispatch(getPostByURL(pathname));  
    }   


    render() {            
        const { users, singlePost } = this.props;

        return (
            <div className="container">   
               {singlePost.singlePost.map(post => {
                        return(
                            <div>
                                 <h1>{post.title.rendered}</h1>
                            </div>                                        
                        );
                })}
            </div>
        );        
    }
}

export default connect() (SinglePost);

And the part of my postsActions.js:

export function getPostByURL(pathname) {
    return function(dispatch){
        console.log(pathname);
        axios.get(`http://example.com/wp-json/wp/v2/posts/?filter[name]=${pathname}&_embed`)
            .then((response) => {       
                dispatch({type: 'FETCH_POST_BY_URL_FULFILLED', payload: response.data});          
            })
            .catch((err) => {
                dispatch({type: 'FETCH_POST_BY_URL_REJECTED', payload: err})
            })
        }
}

Upvotes: 1

Anthony O&#39;Neill
Anthony O&#39;Neill

Reputation: 107

Try keying your incoming posts data by the slug so the shape of your post data is like this.

posts: {
 slug_1: { ... },
 slug_2: { ... }
};

This allows you to grab it directly by the slug when you need it without having to loop through a potential large array of objects.

state.posts[slug]

You could reshape your incoming posts data like so in your reducer:

return data.map(post => ({[post.slug]: post}))

Personally I recommend writing a small custom function in your functions.php which handles a slug query and returns just that post thus removing the need for this sort of client side work.

This would look something like:

function get_post_from_slug($data) {
  $slug = $data['slug'];
  $post = get_page_by_path($slug, OBJECT, array('post'));
  return new WP_REST_Response($post, 200);
}

add_action( 'rest_api_init', function() {
  $version = '/v1';
  $namespace = 'my-awesome-api' . $version;

  register_rest_route( $namespace, '/post' , array(
    'methods' => 'GET',
    'callback' => 'get_post_from_slug',
  ));
});

// Endpoint
../wp-json/my-awesome-api/v1/post?slug=slug_1

Hopefully that helps!

Upvotes: 1

Related Questions