Giter Club home page Giter Club logo

repress's Introduction

Repress
Connect your Redux store to the WordPress REST API.
A Human Made project. Maintained by @rmccue.

Repress is a tiny library which takes control of part of your Redux state and handles talking to the WordPress REST API. Repress only owns a piece of your Redux state (called the substate), allowing you to incrementally add it to existing projects and remain in control of your store.

Repress requires Redux and Redux Thunk.

Keep reading for a simple introduction, or dive into the documentation.

Installation

Repress is available on npm, simply add it to your project to get started:

npm install --save @humanmade/repress

# Or, if you're using Yarn
yarn add @humanmade/repress

You'll then need to connect it to your store.

The Basics

This library is a reusable tool that you can gradually add to your codebase. You simply create "handlers" for every top-level object (posts or CPTs, comments, terms, and users) you'd like to keep in your Redux store, and the handler takes care of dispatching.

The handler "owns" a piece of your global Redux state called the substate, and handles dispatching and reducing any actions related to it. You can keep the substate wherever you want in your Redux store, allowing you to incrementally adopt the library.

Setting up a handler is a three-step process:

  1. Instantiate a handler with options for the type
  2. Add the reducer to the store
  3. Create actions and helpers using the handler's methods

A Simple Example

Typically, you'll want to have a single types.js file containing the setup for all your types. You can then use in your regular actions.js and reducer.js files used in Redux.

// types.js
import { handler } from '@humanmade/repress';

export const posts = new handler( {
	// `type` (required): used to derive the action names, and typically should
	// match the object type (post type, taxonomy, etc).
	type: 'posts',

	// `url` (required): base URL for the type.
	url:   window.wpApiSettings.url + 'wp/v2/posts',
} );

// Register any static archives up-front.
posts.registerArchive( 'home', {} );
// reducer.js
import { combineReducers } from 'redux';

import { posts } from './types';

export default combineReducers( {
	// Any regular reducers you have go in here just like normal.

	// Then, create a substate for your handlers.
	posts: posts.reducer,
} );

In your connected components, you can use the higher-order components (withSingle and withArchive) to pull out data from the substate easily:

// Post.js
import { withSingle } from '@humanmade/repress';
import React from 'react';

import { posts } from './types';

const Post = props => <div>
	<h1>{ props.post.title.rendered }</h1>
</div>;

export default withSingle(
	// Handler object:
	posts,

	// getSubstate() - returns the substate
	state => state.posts,

	// mapPropsToId - resolve the props to the post ID
	props => props.id
)( Post );

// Then just use <Post id={ 42 } /> !

Alternatively, implement it with hooks (requires react-redux 7.1+):

// Post.js
import { useSingle } from '@humanmade/repress';
import React from 'react';

import { posts } from './types';

const Post = props => {
	const postData = useSingle( posts, state => state.posts, props.id );

	return (
		<div>
			<h1>{ postData.post.title.rendered }</h1>
		</div>
	);
}

About

Copyright Human Made. Licensed under the ISC License.

Credits

Created by Human Made to make building React apps with the WordPress REST API even easier. Repress is built using what we've learnt building and scaling React sites. Hire us to work with you!

Written and maintained by Ryan McCue.

Interested in joining in on the fun? Join us, and become human!

repress's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

repress's Issues

Correctly sort archives

Right now, data is pulled out of the store in the order the store contains it. This leads to the sorting not matching the archive.

Deeply-merge fetch options

Currently, fetchOptions is merged with options directly, however this means you can't set common headers without them being overridden. We should deeply-merge these options, as well as any other properties used in Repress that need to be deeply merged.

Add hierarchical handler

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

indexOf returns -1, not null, for missing posts

repress/src/handler.js

Lines 162 to 165 in fe825b9

const position = ids.indexOf( post.id );
if ( position === null ) {
return null;
}

The above code can result in a posts arrays containing elements with index value of -1 if the post.id is not found. I think the if statement should probably be:

if ( position === -1 ) {
  return null;
}

Repress Next

This is a tracking issue for some design changes to Repress.

As we've worked with Repress more, some of the initial design decisions have turned out to be suboptimal. I'll attempt to summarise these below.

Too Post-Specific

Repress can deal with any standard REST API data that follows the pattern of /nouns?{query}, /nouns/{id}, and where the object looks like { "id": <int> }. However, the terminology throughout Repress refers to posts all the time, which makes things very weird when you have a list of e.g. categories.

#2 suggested changing this to a more generic items for withArchive. We need to change a fair few other bits too:

  • In withSingle, rename the following props:
    • postId -> itemId
    • post -> item
  • In withArchive, rename the following props:
    • posts -> items
  • In handler, rename the following methods:
    • isPostLoading -> isItemLoading
    • isPostSaving -> isItemSaving
    • isPostCreating -> isItemCreating
    • isPostDeleting -> isItemDeleting
    • Consider renaming fetchSingle and friends to fetchItem
  • In the state, rename the following keys:
    • loadingPost -> loadingItem
    • posts -> items

Too Repetitive

getSubstate is currently a function passed into methods as an argument. While this allows you to reuse the handler for multiple bits of state, the actions are already tied to the state location, so it turns out to be useless.

To reduce the amount you need to repeat yourself, we should move this to the handler instead: #1

Should getSubstate be moved into the constructor?

Rather than passing getSubstate into each helper method, should it instead be registered in the constructor? This would simplify it, but also removes some of the separation. Is that a good thing?

Repress returns undefined on posts

Hey,
I've just started to use repress to get the posts from our website and display them with react.
My problem is that I followed the tutorial 1:1 and still all posts and archives return undefined in the Component withSingle() and withArchive().
Although the store contains the post / archive with correct ID.

Completely stuck here and wondering if I made a typo or there's a small problem in the tutorial.

// Post.tsx
import {withSingle} from '@humanmade/repress';
import * as React from 'react';
import {posts} from "../types";

const Post = (props) => <article>
    <h3>{props.post.title}</h3>
</article>;

export default withSingle(
    posts,
    state => state.posts,

    props => props.id
)(Post);
// types.tsx
import {handler} from '@humanmade/repress';
import {config} from './config';

export const posts = new handler({
    type: 'posts',
    url: config.api.url + 'wp-json/wp/v2/posts'
});

posts.registerArchive( 'home', {} );
posts.registerArchive( 'stickied', { sticky: '1' } );

Returns TypeError: props.post is undefined.

Thanks for your help in advance!

Add manual page handling

Right now, we have hasMore/loadingMore/onLoadMore(). This allows implementing a "more posts" feature, however, there's no way to implement manual pagination.

Add ability to short-circuit archive requests

In some cases, you may be able to short-circuit archive requests locally if you know how to match items on the client side.

For example, say you have a handler for posts, and you have dynamic archives using the slug:

props => {
    const query = {
    	slug: props.slug,
    };
    posts.registerArchive( `slug/${ props.slug }`, query );
    return `slug/${ props.slug }`;
}

As a developer, you know that WordPress de-duplicates slugs, so you can generate an authoritative search and skip the backend request if you happen to already have the post loaded in (from another archive/single request, e.g.).

It'd be good to have the ability to write a local matcher, which would still fall back to the backend. This local matcher would be treated as authoritative by Repress, so it should only be used when you know for certain that you're going to match.

Something like this:

    posts.registerArchive( `slug/${ props.slug }`, query, {
        localMatcher: posts => posts.filter( post => post.slug === props.slug,
    } );

(Naming tbd)

withArchive() HOC not passing new archive ID to handler.fetchArchive after receiving new props

When a withArchive-wrapped component receives new props that necessitate a change to the archive ID, such as a change in route, how do you handle fetching the new post(s) if needed? It seems like the HOC's props.onLoad function might be using a resolvedId argument evaluated when the component initializes, so not sure how to get the new ID to handler.fetchArchive when the component is updating.

Store loading state as array

If you try to load in multiple items at the same time, you'll run into race conditions as isLoading will return false. We should instead store loading state as an array (or object), and check existence in the array instead.

Use shared request promise pool

When connecting multiple components, avoid dispatching repeated requests. Instead, we should keep track of the current requests, and return the existing promise.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.