Skip to content

Commit

Permalink
feat: update issr to 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexSergey committed Aug 16, 2022
1 parent 5be87b8 commit 4bc0483
Show file tree
Hide file tree
Showing 168 changed files with 3,936 additions and 1,396 deletions.
46 changes: 35 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Schematically, the SSR application looks like this:
- NodeJS app runs React app.
- **iSSR** handles all asynchronous operations.
- After receiving data from asynchronous operations, the React application is rendered.
- NodeJS application serves HTML to the user.
- NodeJS' application serves HTML to the user.

## Problems

Expand All @@ -60,7 +60,7 @@ React currently has many solutions for building SSR applications. The most popul
- You can use any state management solution like Redux, Apollo, Mobx or native setState.
- You can use any other SSR libraries (for example @loadable, react-helmet, etc).

## Using
## Usage

The simplest example of an SSR application using an asynchronous function via setState

Expand All @@ -69,7 +69,7 @@ The simplest example of an SSR application using an asynchronous function via se
Here is a simple Todo List Application without SSR. It uses *jsonplaceholder* for mocking the data:

```jsx
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { render } from 'react-dom';

const getTodos = () => {
Expand Down Expand Up @@ -195,7 +195,7 @@ The main goal is to create 2 applications **client** and **server** with common
```jsx
import React from 'react';
import fetch from 'node-fetch';
import { useSsrState, useSsrEffect } from '@issr/core';
import { useSsrState, useSsrEffect, useRegisterEffect } from '@issr/core';

const getTodos = () => {
return fetch('https://jsonplaceholder.typicode.com/todos')
Expand All @@ -204,11 +204,13 @@ const getTodos = () => {

export const App = () => {
const [todos, setTodos] = useSsrState([]);
const registerEffect = useRegisterEffect();

useSsrEffect(async () => {
const todos = await getTodos()
setTodos(todos);
});
useSsrEffect(() => {
registerEffect(getTodos).then(todos => {
setTodos(todos);
});
}, []);

return (
<div>
Expand All @@ -225,16 +227,35 @@ export const App = () => {

In this code, *getTodos* is an asynchronous operation that makes call to the *jsonplaceholder* server and gets the todo list data.

- *useRegisterEffect* is a hook to wrap your async logic. Async method from this function will fetch the data from the server.
**if you need to pass additional parameters to the function you can pass these params to registerEffect**:
```js
const getTodos = (page) => {
return fetch(`https://somedatasite.com/articles/${page}`)
.then(data => data.json())
};
// ....
const [page, setPage] = useSsrState(1);
const [todos, setTodos] = useSsrState([]);
const registerEffect = useRegisterEffect();

useSsrEffect(() => {
registerEffect(getTodos, page).then(todos => {
setTodos(todos);
});
}, [page]);
```

- *useSsrState* is analogue of useState only with SSR support

- *useSsrEffect* is analogue useEffect (() => {}, []); for SSR. It works with any async logic.
- *useSsrEffect* is analogue React's useEffect; for SSR.

**Step 4**. **client.jsx** should contain part of the application for Frontend

```jsx
import React from 'react';
import { hydrate } from 'react-dom';
import createSsr from '@issr/core';
import { createSsr } from '@issr/core';
import { App } from './App';

const SSR = createSsr(window.SSR_DATA);
Expand Down Expand Up @@ -269,7 +290,7 @@ const app = express();
app.use(express.static('public'));

app.get('/*', async (req, res) => {
const { html, state } = await serverRender(() => <App />);
const { html, state } = await serverRender.string(() => <App />);

res.send(`
<!DOCTYPE html>
Expand All @@ -293,6 +314,9 @@ app.listen(4000, () => {
console.log('Example app listening on port 4000!');
});
```
- *sererRender* - contains 2 methods:
- **string** - will render your application to the string
- **stream** - this method will work React 18 only. It's *PipeableStream*. Please, see the example: [React18](https://github.com/AlexSergey/issr/blob/master/examples/19-react-18)

There are 2 important points in this code:

Expand Down
4 changes: 2 additions & 2 deletions examples/1-simple-without-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"build": "cross-env NODE_ENV=production node build"
},
"dependencies": {
"@issr/core": "1.2.0",
"@issr/core": "2.0.0",
"lodash": "4.17.15",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"@types/lodash": "^4.14.149",
"@rockpack/compiler": "2.0.1"
"@rockpack/compiler": "3.0.0-next.2"
}
}
14 changes: 7 additions & 7 deletions examples/1-simple-without-backend/src/index.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/* eslint-disable */
import React from 'react';
import { render } from 'react-dom';
import createSsr, { useSsrState, useSsrEffect } from '@issr/core';
import { createSsr, useRegisterEffect, useSsrState, useSsrEffect } from '@issr/core';

const asyncFn = () => new Promise((resolve) => setTimeout(() => resolve('Hello world'), 1000));

const App = ({ children }) => {
const [state, setState] = useSsrState('text here');
const registerEffect = useRegisterEffect();

useSsrEffect(async () => {
const data = await asyncFn();
setState(data);
});
useSsrEffect(() => {
registerEffect(asyncFn).then(data => {
setState(data);
});
}, []);

return (
<div>
Expand Down
6 changes: 3 additions & 3 deletions examples/10-redux-sagas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"build": "cross-env NODE_ENV=production node build"
},
"dependencies": {
"@issr/core": "1.2.0",
"@issr/core": "2.0.0",
"@reduxjs/toolkit": "1.1.0",
"axios": "0.19.0",
"immutable": "4.0.0-rc.12",
Expand All @@ -22,7 +22,7 @@
"serialize-javascript": "3.0.0"
},
"devDependencies": {
"@issr/babel-plugin": "1.2.0",
"@rockpack/compiler": "2.0.1"
"@issr/babel-plugin": "2.0.0",
"@rockpack/compiler": "3.0.0-next.2"
}
}
1 change: 0 additions & 1 deletion examples/10-redux-sagas/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import Image from './containers/Image';

export const App = () => <Image />;
3 changes: 1 addition & 2 deletions examples/10-redux-sagas/src/client.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import { App } from './App';
import createSsr from '@issr/core';
import { createSsr } from '@issr/core';
import createStore from './store';
import rest from './utils/rest';

Expand Down
8 changes: 4 additions & 4 deletions examples/10-redux-sagas/src/containers/Image/index.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchImage } from './action';
import { useSsrEffect } from '@issr/core';
import { useSsrEffect, useRegisterEffect } from '@issr/core';

const Image = () => {
const dispatch = useDispatch();
const image = useSelector(state => state.imageReducer);
const registerEffect = useRegisterEffect();

useSsrEffect(() => {
dispatch(fetchImage());
});
registerEffect(dispatch, fetchImage());
}, []);

return (
<div>
Expand Down
12 changes: 10 additions & 2 deletions examples/10-redux-sagas/src/containers/Image/saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@ function* watchFetchImage(rest) {
yield takeEvery(fetchImage, fetchImageAsync, rest);
}

const callApi = () => (
new Promise(resolve => {
setTimeout(() => {
resolve({ url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Large_breaking_wave.jpg/800px-Large_breaking_wave.jpg' });
}, 500);
})
)

function* fetchImageAsync(rest) {
try {
yield put(requestImage());
const { data } = yield call(() => rest.get('https://api.github.com/users/defunkt'));
yield put(requestImageSuccess({ url: data.avatar_url }));
const { url } = yield call(() => callApi());
yield put(requestImageSuccess({ url }));
} catch (error) {
yield put(requestImageError());
}
Expand Down
11 changes: 6 additions & 5 deletions examples/10-redux-sagas/src/server.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from 'path';
import React from 'react';
import Koa from 'koa';
import serve from 'koa-static';
import Router from 'koa-router';
Expand All @@ -22,13 +21,15 @@ router.get('/*', async (ctx) => {
rest
});

const { html } = await serverRender(() => (
const { html } = await serverRender.string(() => (
<Provider store={store}>
<App />
</Provider>
), async () => {
store.dispatch(END);
await rootSaga.toPromise();
), {
outsideEffects: async () => {
store.dispatch(END);
await rootSaga.toPromise();
}
});

ctx.body = `
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"build": "cross-env NODE_ENV=production node build"
},
"dependencies": {
"@issr/core": "1.2.0",
"@issr/core": "2.0.0",
"@reduxjs/toolkit": "1.1.0",
"axios": "0.19.0",
"immutable": "4.0.0-rc.12",
Expand All @@ -22,7 +22,7 @@
"serialize-javascript": "3.0.0"
},
"devDependencies": {
"@issr/babel-plugin": "1.2.0",
"@rockpack/compiler": "2.0.1"
"@issr/babel-plugin": "2.0.0",
"@rockpack/compiler": "3.0.0-next.2"
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import Image from './containers/Image';

export const App = () => <Image />;
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import { App } from './App';
import createSsr from '@issr/core';
import { createSsr } from '@issr/core';
import createStore from './store';

const SSR = createSsr();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {requestImage, requestImageError, requestImageSuccess } from './action';
import { useSsrEffect } from '@issr/core';
import { useSsrEffect, useRegisterEffect } from '@issr/core';
import rest from '../../utils/rest';

const callApi = () => (
new Promise(resolve => {
setTimeout(() => {
resolve({ url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Large_breaking_wave.jpg/800px-Large_breaking_wave.jpg' });
}, 500);
})
)

function getImage(dispatch) {
dispatch(requestImage());
return rest.get('https://api.github.com/users/defunkt')
.then(({ data }) => {
dispatch(requestImageSuccess({ url: data.avatar_url }));
return callApi()
.then(({ url }) => {
dispatch(requestImageSuccess({ url }));
})
.catch(() => dispatch(requestImageError()))
}

const Image = () => {
const dispatch = useDispatch();
const image = useSelector(state => state.imageReducer);
const registerEffect = useRegisterEffect();

useSsrEffect(() => dispatch(getImage));
useSsrEffect(() => {
registerEffect(dispatch(getImage));
}, []);

return (
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from 'path';
import React from 'react';
import Koa from 'koa';
import serve from 'koa-static';
import Router from 'koa-router';
Expand All @@ -19,12 +18,14 @@ router.get('/*', async (ctx) => {
initState: { }
});

const { html } = await serverRender(() => (
const { html } = await serverRender.string(() => (
<Provider store={store}>
<App />
</Provider>
));

setTimeout(() => {
console.log('after', store.getState())
});
ctx.body = `
<!DOCTYPE html>
<html lang="en">
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "@issr/example-11",
"name": "@issr/example-12",
"version": "1.0.0",
"scripts": {
"start": "cross-env NODE_ENV=development node build",
"build": "cross-env NODE_ENV=production node build"
},
"dependencies": {
"@apollo/react-hooks": "3.1.5",
"@issr/core": "1.2.0",
"@issr/core": "2.0.0",
"apollo-cache-inmemory": "1.6.5",
"apollo-client": "2.6.8",
"apollo-link-http": "1.5.17",
Expand All @@ -22,7 +22,7 @@
"serialize-javascript": "3.0.0"
},
"devDependencies": {
"@issr/babel-plugin": "1.2.0",
"@rockpack/compiler": "2.0.1"
"@issr/babel-plugin": "2.0.0",
"@rockpack/compiler": "3.0.0-next.2"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from 'react-apollo';
import { useSsrEffect } from '@issr/core';
import { useRegisterEffect } from '@issr/core';

const books = [
{
Expand Down Expand Up @@ -50,7 +49,7 @@ const GET_BOOKS = gql`
`;

export const App = () => {
useSsrEffect();
useRegisterEffect();
let { data } = useQuery(GET_BOOKS);

const loaded = data && data.books && Array.isArray(data.books);
Expand Down
Loading

0 comments on commit 4bc0483

Please sign in to comment.