catalyst-redux v0.1.5
Catalyst Redux
State management utilities for Redux.
⚠️ Definitely a WIP ⚠️
Operations
The simplest Operation is just an action type and a reducer:
import { createOperation } from 'catalyst-redux';
const increment = createOperation({
actionType: 'INCREMENT',
reducer: state => state + 1
});You can customize the action creator if you want:
import { createOperation } from 'catalyst-redux';
const increment = Operation({
actionType: 'INCREMENT',
actionCreator: amount => ({ payload: { amount } }),
reducer: (state, action) => state + action.payload.amount
});Asynchronous Operations
Sometimes an Operation needs to handle some asynchronous logic (e.g. making a request to your API and then storing the response). This is almost as simple to write as a synchronous operation:
import { createAsyncOperation } from 'catalyst-redux';
const fetchArticles = createAsyncOperation({
actionType: 'FETCH_ARTICLES',
reducer: (state, action) => {
switch (action.status) {
case 'pending': {
return {
...state,
fetchStatus: 'pending'
};
}
case 'success': {
return {
...state,
fetchStatus: 'success',
articles: action.payload.data
};
}
case 'error': {
return {
...state,
fetchStatus: 'error'
};
}
}
return state;
},
request: () => axios.get('/api/articles').then(({ data }) => data)
});Modules
Operations can be composed into a Module. A Module contains the reducer, saga, action creators, and selectors for a specific "slice" of your application state.
Here's a counter Module with an initial state of 0:
import { createModule, createOperation } from 'catalyst-redux';
const Counter = createModule({
initialState: 0,
operations: {
increment: createOperation({
actionType: 'INCREMENT',
reducer: state => state + 1
}),
decrement: createOperation({
actionType: 'DECREMENT',
reducer: state => state - 1
})
},
selectors: {
getState: state => state
}
})('counter');The created module has the following API:
Counter.reducer(state, action);
Counter.saga();
Counter.actions.increment();
Counter.actions.decrement();
Counter.selectors.getState(state);Resource Modules
Resource modules consist of a predefined group of operations and selectors which allow you to interact with a JSON API.
Resource modules require information about the types of resources you want to interact with and how to construct requests for them:
import { ResourcesModule, Resource, relationships } from 'catalyst-redux';
class Post extends Resource {
static type = 'posts';
static relationships = {
comments: relationships.hasMany('comments', {
inverse: 'post'
})
};
}
class Comment extends Resource {
static type = 'comments';
static relationships = {
post: relationships.hasOne('post', {
inverse: 'comments'
})
};
}
const Resources = new ResourcesModule([Post, Comment]);This would provide you with the following API for interacting with posts and comments:
Resources.findAllAction(resourceClass);
Resources.find(resourceClass, resourceID, options);
Resources.create(resourceClass, attributes, options);
Resources.update(resourceClass, resourceID, attributes, options);
Resources.destroy(resourceClass, resourceID);
Resources.getResources(resourceClass);
Resources.getResource(resourceClass, resourceID);
Resources.getResources(resourceClass);
Resources.getResourceStatus(resourceClass, resourceID);In this case, the "resourceClass" argument could be either Post or
Comment.
Request Options
The requests made and responses received by a resource module can be transformed
by passing an options object to ResourcesModule:
import { ResourcesModule } from 'catalyst-redux';
const Resources = new ResourcesModule(
[
// Resource classes
],
{
transformRequest({ data, headers }) {
headers = { ...headers, Authorization: 'Bearer 1234567890' };
return { data, headers };
},
transformResponse(data) {
return { ...data };
}
}
);Actions
findAllAction(resourceClass: Class, ?options: Object)
Example:
Resources.findAllAction(Post, {
filter: {
authorId: 7
},
include: ['comments']
});createAction(resourceClass: Class, attributes: Object, ?options: Object)
Example:
Resources.createAction(
Comment,
{ content: 'First!' },
{
relationships: {
post: { type: 'posts', id: '1' }
}
}
);updateAction(resourceClass: Class, resourceID: number | string, attributes: Object, ?options: Object)
Example:
Resources.actions.update(
Comment,
6,
{ content: 'Something meaningful.' },
{
relationships: {
post: { type: 'posts', id: '1' }
}
}
);The connectResource HOC
This
higher-order component
allows you to easily build components which can both create new resources and
update/destroy existing resources. The wrapped component (CommentForm in the
following example), is passed these props by connectResource:
resource: Object | null
resourceStatus: string | null
onCreate: (attributes: Object, options?: Object) => void Dispatches an
action to create a resource using the passed attributes.
onUpdate: (attributes: Object, options?: Object) => void Dispatches an
action to update the resource using the passed attributes.
onDestroy: () => void Dispatches an action to destroy the resource.
import { ResourcesModule, Resource, connectResource } from 'catalyst-redux';
import type { ResourceProvidedProps } from 'catalyst-redux';
class Comment extends Resource {
static type = 'comments';
}
const Resources = new ResourcesModule([Comment]);
type Props = ResourceProvidedProps;
class CommentForm extends React.PureComponent<Props> {
state = { content: '' };
handleChange = event => {
this.setState({ content: event.currentTarget.value });
};
handleSubmit = event => {
event.preventDefault();
this.props.onCreate({
content: this.state.content
});
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<textarea value={this.state.content} onChange={this.handleChange} />
<button>Add Comment</button>
</form>
);
}
}
const ConnectedCommentForm = connectResource(Resources, Comment)(CommentForm);The connectResourceForm HOC
This HOC makes it easy to store temporary state for a resource's attributes
before the resource is created/updated. The wrapped component is passed these
props in addition to the ones provided by connectResource:
onSave: (eventOrOptions?: SyntheticEvent<*> | Object = {}, options?: Object = {}) => void Calls either onSave or onCreate depending on the state of
the resource. Optionally accepts an event as the first argument and calls
preventDefault (which allows it to be used directly in a form's onSubmit).
onReset: () => void Clears the stored attribute values, effectively
resetting the form.
inputProps: (key: string) => { value: string, onChange: (event: SyntheticInputEvent<*>) => void } Given the name of an attribute, it
returns an object with value and onChange props which can be spread onto an
<input />, <select />, etc.
import { ResourcesModule, Resource, connectResourceForm } from 'catalyst-redux';
import type { ResourceFormProvidedProps } from 'catalyst-redux';
class Post extends Resource {
static type = 'posts';
}
const Resources = new ResourcesModule([Post]);
type Props = ResourceFormProvidedProps;
const PostForm = ({ onSave, onReset, inputProps }: Props) => {
return (
<form onSubmit={onSave}>
<label for="title">Title</label>
<input id="title" type="text" {...inputProps('title')} />
<label for="content">Content</label>
<textarea id="content" {...inputProps('content')} />
<button type="submit">Save</button>
<button type="button" onClick={onReset}>
Reset
</button>
</form>
);
};
const ConnectedPostForm = connectResourceForm(Resources, Post)(PostForm);The ConnectedPostForm component can now be used to either create a new post:
<ConnectedPostForm />or to edit an existing one:
<ConnectedPostForm resourceID={4} />8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago