The project is inspired from react-unstated and couple of other articles.(https://github.com/jamiebuilds/unstated). It is only intended for educational purpose as of now.
ReStated is a state management library using more of an object oriented approach.
Right now the entire source code lives withing the below folder (https://github.com/rajeshpillai/react-restated/tree/master/src/ReStated/index.js)
I will create an NPM package once I test this enough as I don't want to pollute the npm repository.
The library is very simple to use. First import Consumer and Container in your main application. The below example is of a simple task/todo app wherein we demonstrate how to do the basic CRUD part of the app.
import {Consumer, Container} from './ReStated';
Create a Provider. The provider should extends from the Container class. The provider exposes state and the actions. By default all actions defined here is available to the consumer/subscriber.
In case you don't want any specific action to be available just begin the name of the action/method with an underscore,'_'
class MyProvider extends Container {
state = {
tasks: [
{id: 1, title: "New React Context API"},
{id: 2, title: "Learn VueJS"},
{id: 3, title: "Master NodeJS"},
],
notifications: [
{taskId: 1, message: "Message 1 TaskID 1"},
{taskId: 1, message: "Message 2 TaskID 1"},
{taskId: 2, message: "Message 1 for TaskID 2"},
]
}
actions = {
onAddTask: (title) => {
console.log("adding...");
let maxId = Math.max.apply(Math,
this.state.tasks.map((task)=>{return task.id}));
let task = {
id: maxId + 1 ,
title: title
}
this.setState({
tasks: [task, ...this.state.tasks]
})
},
onDeleteTask: (taskId) => {
console.log("onDeleteTask...");
let tasks = this.state.tasks.filter((task) => {
return task.id !== taskId
})
this.setState({
tasks
}, ()=> {
console.log("after update: ",this.state.tasks);
});
}
}
render () {
console.log("About to call parent render..");
return super.render();
}
}
All state/data should go into the state property of the Provider class. And all actions should go into the action object.
In the render() of the provider ensure to call the render of the base class by calling super.render();
Now your main App class can be coded as below.
class App extends Component {
render() {
return (
<MyProvider>
<div className="container">
<h1>Task Management App</h1>
<TaskApp />
</div>
</MyProvider>
);
}
}
Please note, in whichever component you need state, wrap the component within the Consumer.
Let's take a look at the TaskApp component.
import React from 'react';
import TaskForm from './TaskForm';
import TaskList from './TaskList';
const TaskApp = () => (
<React.Fragment>
<TaskForm />
<ul className = "task-list">
<TaskList/>
</ul>
</React.Fragment>
)
export default TaskApp;
The TaskApp component as such doesn't need any state information.
Now let's have a look at TaskForm and TaskList component. The TaskForm component needs access to the context as it has to invoke the actions on the provider. So, import the {Consumer} from the library and wrap your component within the component. The context is available as part of a render props function.
import React from 'react';
import {Consumer } from '../ReStated';
const TaskForm = () => (
<Consumer>
{(context) => (
<div>
<input className="input-title" ref={(title)=>{this.taskTitle = title}}
type="text" placeholder="what do you want to do today?" />
<button className="button-add" type="submit"
onClick={(e) => {context.onAddTask(this.taskTitle.value)}}>
✚
</button>
</div>
)}
</Consumer>
)
export default TaskForm;
The TaskForm needs the context, as it has to invoke the onAddTask method when the add button is clicked.
The below is the code for the TaskList component. The TaskList component also needs the context as it needs both the state info as well as the actions.
import React from 'react';
import {Consumer} from '../ReStated';
const TaskList = () => {
const renderUI = (context) => {
return context.state.tasks.map((task) => {
return (
<li className="task-item" key={task.id}>
<span>{task.title}</span>
<button className="todo-delete-button"
onClick={(e) => context.onDeleteTask(task.id)}>
❌
</button>
</li>
)
})
}
return (
<Consumer>
{(context) => (
renderUI(context)
)}
</Consumer>
)
}
export default TaskList;
We can also create multiple providers if need be. For e.g. let's create a TimeProvider which supplies time and also a Time component that needs data from the TimeProvider.
The below is the code for TimeProvider. Just extend any class from the Component and it has all the features required to become a Provider.
import React from 'react';
import {Container} from '../ReStated';
export default class TimeProvider extends Container {
state = {
time: new Date()
}
render () {
return super.render();
}
}
The TimeProvider above exposes only a state object with one attribute time. But you can use this as per your requirements add add methods, more properties etc.
Now lets create a component that uses TimeProvider.
import React from 'react';
import {Consumer} from '../ReStated';
const Time = () => {
return (
<Consumer>
{({state}) => (
<span className="time">{state.time.toString()}</span>
)}
</Consumer>
);
}
export default Time;
To get the context we just have to wrap our component within the component.
Now let's see how we can integrate this in our App component.
class App extends Component {
render() {
return (
<MyProvider>
<div className="container">
<h1>Task Management App</h1>
<TaskApp />
<Consumer>
{(context) => (
<Notification context={context} />
)}
</Consumer>
</div>
<TimeProvider>
<Time />
</TimeProvider>
</MyProvider>
);
}
}
In the App component we just wrap our Time component within the TimeProvider and voila we are done.
import React, { Component } from 'react';
// Create a context
export const StateContext = React.createContext(null);
export const {Provider, Consumer} = StateContext;
export class Container extends Component {
constructor() {
super();
this.setup = this.setup.bind(this);
}
setup() {
let map = {};
map.state = this.state;
map.actions = this.actions;
return map;
}
render () {
let map = this.setup();
return (
<Provider value={map}>
{this.props.children}
</Provider>
)
}
}
I will be updating this demo as this is still WIP. Hope you like this approach. There could be many open queries, which I will address as I add more features to this and probable unit test cases as well.
We are always open to your feedback.