74 |
75 |
76 |
77 |
78 | );
79 | }
80 | }
81 |
82 | export default App;
83 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 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.
2 |
3 | ## Table of Contents
4 |
5 | - [Introduction to reStated](#intro-to-restated)
6 | - [Installation](#installation)
7 | - [Usage Examples](#usage-examples)
8 | - [Source Code](#source-code)
9 | - [Sending Feedback](#sending-feedback)
10 |
11 | ## Introduction to reStated
12 |
13 | ReStated is a state management library using more of an object oriented approach.
14 |
15 | ## Installation
16 | Right now the entire source code lives withing the below folder
17 | (https://github.com/rajeshpillai/react-restated/tree/master/src/ReStated/index.js)
18 |
19 | I will create an NPM package once I test this enough as I don't want to
20 | pollute the npm repository.
21 |
22 | ## Usage Examples
23 |
24 | The library is very simple to use. First import Consumer and Container
25 | in your main application. The below example is of a simple task/todo
26 | app wherein we demonstrate how to do the basic CRUD part of the app.
27 |
28 | ```js
29 | import {Consumer, Container} from './ReStated';
30 | ```
31 |
32 | Create a Provider. The provider should extends from the Container class.
33 | The provider exposes state and the actions. By default all actions defined
34 | here is available to the consumer/subscriber.
35 |
36 | In case you don't want any specific action to be available just begin the
37 | name of the action/method with an underscore,'_'
38 | ```js
39 |
40 | class MyProvider extends Container {
41 | state = {
42 | tasks: [
43 | {id: 1, title: "New React Context API"},
44 | {id: 2, title: "Learn VueJS"},
45 | {id: 3, title: "Master NodeJS"},
46 | ],
47 | notifications: [
48 | {taskId: 1, message: "Message 1 TaskID 1"},
49 | {taskId: 1, message: "Message 2 TaskID 1"},
50 | {taskId: 2, message: "Message 1 for TaskID 2"},
51 |
52 | ]
53 | }
54 |
55 | actions = {
56 | onAddTask: (title) => {
57 | console.log("adding...");
58 | let maxId = Math.max.apply(Math,
59 | this.state.tasks.map((task)=>{return task.id}));
60 |
61 | let task = {
62 | id: maxId + 1 ,
63 | title: title
64 | }
65 |
66 | this.setState({
67 | tasks: [task, ...this.state.tasks]
68 | })
69 | },
70 |
71 | onDeleteTask: (taskId) => {
72 | console.log("onDeleteTask...");
73 | let tasks = this.state.tasks.filter((task) => {
74 | return task.id !== taskId
75 | })
76 |
77 | this.setState({
78 | tasks
79 | }, ()=> {
80 | console.log("after update: ",this.state.tasks);
81 | });
82 | }
83 | }
84 |
85 | render () {
86 | console.log("About to call parent render..");
87 | return super.render();
88 | }
89 | }
90 | ```
91 | All state/data should go into the state property of the Provider class. And all actions should go into the action object.
92 |
93 | In the render() of the provider ensure to call the render of the base class
94 | by calling super.render();
95 |
96 | Now your main App class can be coded as below.
97 |
98 | ```js
99 | class App extends Component {
100 | render() {
101 | return (
102 |
103 |
104 |
Task Management App
105 |
106 |
107 |
108 | );
109 | }
110 | }
111 | ```
112 |
113 | Please note, in whichever component you need state, wrap the component
114 | within the Consumer.
115 |
116 | Let's take a look at the TaskApp component.
117 |
118 | ```js
119 | import React from 'react';
120 | import TaskForm from './TaskForm';
121 | import TaskList from './TaskList';
122 |
123 | const TaskApp = () => (
124 |
125 |
126 |
127 |
128 |
129 |
130 | )
131 | export default TaskApp;
132 |
133 |
134 | ```
135 |
136 | The TaskApp component as such doesn't need any state information.
137 |
138 | Now let's have a look at TaskForm and TaskList component. The TaskForm
139 | component needs access to the context as it has to invoke the actions
140 | on the provider. So, import the {Consumer} from the library and wrap
141 | your component within the component. The context is available
142 | as part of a render props function.
143 |
144 | ```js
145 | import React from 'react';
146 | import {Consumer } from '../ReStated';
147 |
148 | const TaskForm = () => (
149 |
150 | {(context) => (
151 |
152 | {this.taskTitle = title}}
153 | type="text" placeholder="what do you want to do today?" />
154 |
158 |
159 | )}
160 |
161 | )
162 |
163 | export default TaskForm;
164 | ```
165 |
166 | The TaskForm needs the context, as it has to invoke the onAddTask method when the add button is clicked.
167 |
168 | The below is the code for the TaskList component. The TaskList component
169 | also needs the context as it needs both the state info as well as the
170 | actions.
171 |
172 | ```js
173 | import React from 'react';
174 | import {Consumer} from '../ReStated';
175 |
176 | const TaskList = () => {
177 | const renderUI = (context) => {
178 | return context.state.tasks.map((task) => {
179 | return (
180 |
181 | {task.title}
182 |
186 |
187 | )
188 | })
189 | }
190 | return (
191 |
192 | {(context) => (
193 | renderUI(context)
194 | )}
195 |
196 | )
197 | }
198 |
199 | export default TaskList;
200 |
201 | ```
202 | We can also create multiple providers if need be. For e.g. let's create
203 | a TimeProvider which supplies time and also a Time component that needs data
204 | from the TimeProvider.
205 |
206 | The below is the code for TimeProvider. Just extend any class from the
207 | Component and it has all the features required to become a Provider.
208 |
209 | ```js
210 | import React from 'react';
211 | import {Container} from '../ReStated';
212 |
213 | export default class TimeProvider extends Container {
214 | state = {
215 | time: new Date()
216 | }
217 | render () {
218 | return super.render();
219 | }
220 | }
221 | ```
222 | The TimeProvider above exposes only a state object with one attribute time.
223 | But you can use this as per your requirements add add methods, more properties etc.
224 |
225 | Now lets create a component that uses TimeProvider.
226 |
227 | ```js
228 | import React from 'react';
229 | import {Consumer} from '../ReStated';
230 |
231 | const Time = () => {
232 | return (
233 |
234 | {({state}) => (
235 | {state.time.toString()}
236 | )}
237 |
238 | );
239 | }
240 |
241 | export default Time;
242 | ```
243 | To get the context we just have to wrap our component within the
244 | component.
245 |
246 | Now let's see how we can integrate this in our App component.
247 |
248 | ```js
249 | class App extends Component {
250 | render() {
251 | return (
252 |
253 |
262 |
263 |
264 |
265 |
266 | );
267 | }
268 | }
269 |
270 | ```
271 | In the App component we just wrap our Time component within the TimeProvider and voila we are done.
272 |
273 | ## Source Code
274 |
275 | ```js
276 | import React, { Component } from 'react';
277 |
278 | // Create a context
279 | export const StateContext = React.createContext(null);
280 | export const {Provider, Consumer} = StateContext;
281 |
282 | export class Container extends Component {
283 | constructor() {
284 | super();
285 | this.setup = this.setup.bind(this);
286 | }
287 |
288 | setup() {
289 | let map = {};
290 | map.state = this.state;
291 | map.actions = this.actions;
292 | return map;
293 | }
294 | render () {
295 | let map = this.setup();
296 | return (
297 |
298 | {this.props.children}
299 |
300 | )
301 | }
302 | }
303 | ```
304 |
305 | 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.
306 |
307 | ## Sending Feedback
308 |
309 | We are always open to [your feedback](https://github.com/rajeshpillai/react-restated/issues).
310 |
--------------------------------------------------------------------------------