A Redux library to seamlessly connect to any API using first-class models created from true TypeScript classes, with support for customizable serialization, validation, and dependency injection.
- Interact with Redux through a friendly syntax that is simple and familiar to developers
- Maintain an immutable architecture and unidirectional data store without the pain!
- Create a layer of abstraction that is still plain-old Redux under the hood with regular reducers, actions and selectors
- Use TypeScript classes to create type-safe models, validators, serializers, and API adapters
- Provide custom validation and serialization rules on a per-field basis for each model
- Support any API on a per-model basis with configurable serializers and API adapters, with default support for a standard REST API
- Sensible default configuration with plenty of escape hatches to suit your needs
- Feel confident with an open source library that is thoroughly unit tested
- Rendering library/framework agnostic:
- Official support for React - redux-data-service-react
- Vue: TBD
- Angular: TBD
// student/model.ts
import { attr, required, Model, EmailField, NumberField, StringField } from "redux-data-service";
export class Student extends Model {
public readonly string serviceName = "student";
@required()
@attr(StringField)
public string firstName;
@attr(NumberField)
public string age;
@attr(EmailField)
public string email;
}
- Validation and serialization rules are provided using property decorators.
- Models can also support
belongsTo
andhasMany
relationships to other models which are lazy-loaded when they are requested.
import { Student } from "./student";
const student = new Student({ id: "123" });
student.firstName = "Jessica";
student.age = 9;
student.email = "[email protected]";
- In preserving the awesome power of an immutable architecture, the above will not mutate the model.
- Instead, it uses magic setters to dispatch model-specific actions, where a new instance of the model is created each time:
{ type: "student/SET_FIELD", payload: { id: "123", fieldName: "firstName", value: "Jessica" } }
{ type: "student/SET_FIELD", payload: { id: "123", fieldName: "age", value: 9 } }
{ type: "student/SET_FIELD", payload: { id: "123", fieldName: "email", value: "[email protected]" } }
Commit pending changes to the model's corresponding API end-point:
student.save();
Calling the save
method validates the model and dispatches an action to an underlying redux-observable epic:
{ type: "student/UPDATE_RECORD", payload: { id: "123" } }
The epic will then:
- serialize the model into a JSON string (using the default serializer)
- send a
POST
or aPUT
to the API to create or update the model (using the default API adapter):
PUT api/students/123
{"id": "123", "firstName": "Jessica", "age": 9, "email": "[email protected]"}
Although the above example uses the default serializer and API adapter for connecting to a REST API, any ISerializer or IAdapter may be used to connect to any API.
- For React projects, use redux-data-service-react
- Pull Requests are welcome to support other libraries/frameworks, such as Vue or Angular!
import * as React from "react";
import { withModelQuery } from "redux-data-service-react";
export const StudentList = withModelQuery({ modelName: "student" })(
({ items }) => (
<ul>
{items.map(student => (
<li key={student.id}>{student.name}, age {student.age}, email {student.email}</li>
))}
</ul>
)
);
Apollo-like syntax is also supported:
import * as React from "react";
import { Query } from "redux-data-service-react";
export const StudentList = (
<Query modelName="student">
{({ items }) => (
<ul>
{items.map(student => (
<li key={student.id}>{student.name}, age {student.age}, email {student.email}</li>
))}
</ul>
)}
</Query>
);
Simply use your component as you normally would and it will automatically query the API when the component mounts:
<StudentList />
Additional query params may be specified:
<StudentList queryParams={{ page: 1, name: "Jess" }}/>
You may also provide an iterable list of models to render instead of querying the API:
<StudentList items={students}/>
import * as React from "react";
import { withNewModel, ModelForm, ModelField } from "redux-data-service-react";
const Input = (props) => <input {...props />;
export const StudentForm = withNewModel("student")(
({ student, ...props }) => (
<ModelForm model={student} {...props}>
<ModelField name="firstName" component={Input}/>
<ModelField name="age" type="number" component={Input}/>
<ModelField name="email" type="email" component={Input}/>
<Input type="submit" value="Save" />
</ModelForm>
)
);
The above form can be used for creating or editing a student
model:
- Creating a Student:
<StudentForm />
- Editing a Student:
<StudentForm studentId="123"/>
or<StudentForm student={student}/>
When the form is submitted it will:
- validate the model, then
- save the model by committing changes to the API
import * as React from "react";
import { withModel } from "redux-data-service-react";
export const StudentDetail = withModel("student")(
({ student }) => (
<section>
<h1>{student.name}</h1>
<strong>age:</strong> {student.age}
<em>{student.email}</em>
</section>
)
);
The above component, given a model id, will load and subscribe to the requested model.
Working with React-Router
Simply pass the id from the route into the above component, for example:
<Route
path="/students/:studentId"
render={(props) => <StudentDetail studentId={props.match.params.studentId}/>}
/>
Before we can use the Student model defined above, we must wire it into Redux!
The DataService
class provides a layer of abstraction around Redux to create a unidirectional, immutable architecture without the pain!
- Creates a corresponding Redux reducer with model-specific actions and selectors for each model
- Communicates to any API using a configurable API adapter when the model is saved or requested
- Supports serializing/deserializing models when communicating to the API with support for custom serializers
- Leverages RxJS observables to make it simple to subscribe to changes to any model
// student/service.ts
import { DataService } from "redux-data-service";
import { Student } from "./Student";
export class StudentService extends DataService {
public readonly name = "student";
public readonly ModelClass = Student;
}
Add each of your DataService classes to the configure
function to enable dependency injection and create the Redux store
- Configure
redux-data-service
to globally override any default functionality as needed - You may provide a callback as the second parameter, which given the individual reducers and epics, should create the Redux store as desired
import { configure } from "redux-data-service";
import { StudentService } from "./student/service.ts";
export const store = configure({
modules: {
student: StudentService
}
});
Please Read the docs for more detailed information.