Skip to content

Commit

Permalink
Add React 18+ support (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
kmoskwiak authored Jul 5, 2024
1 parent fc0f1a2 commit 6887768
Show file tree
Hide file tree
Showing 51 changed files with 12,705 additions and 14,085 deletions.
49 changes: 49 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'xo',
'plugin:react/recommended',
],
overrides: [
{
env: {
node: true,
},
files: [
'.eslintrc.{js,cjs}',
],
parserOptions: {
sourceType: 'script',
},
},
{
extends: [
'xo-typescript',
],
files: [
'*.ts',
'*.tsx',
],
},
{
files: [
'*.ts',
'*.tsx',
],
StrictPascalCase: false,
},
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: [
'react',
],
rules: {
},
};
6 changes: 3 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI
name: CI

on:
push:
Expand All @@ -16,12 +16,12 @@ jobs:

strategy:
matrix:
node-version: [10.x, 12.x]
node-version: [16.x, 17.x, 18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/npmpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: 12
node-version-file: ".nvmrc"
- run: npm ci
- run: npm run build
- run: npm test
Expand All @@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: 12
node-version-file: ".nvmrc"
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Playwright Tests
on:
push:

jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: ".nvmrc"
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
node_modules
dist
example/build
example/build
dist-server
/test-results/
/playwright-report/
/playwright/.cache/
/*.scratchpad.*
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v22.3
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 3.x.x

## 3.0.0 Bling bang bang born

## 2.x.x

### 2.0.x
Expand Down
165 changes: 61 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# useSSE
# useSSE 3.x.x-beta

> [!CAUTION]
> 3.x.x is still in beta
> [!NOTE]
> You are viewing a v3.x.x version of hook which is designed to be compatible with React 18. This version of hook is still in beta.
> If you are using React <18 check latest stable [2.x.x version of useSSE](https://github.com/kmoskwiak/useSSE/tree/v2.0.1)
![useSSE](https://repository-images.githubusercontent.com/262809605/78398700-a279-11ea-9ba2-4c15b6a1ec9a)
[![npm version](https://badgen.net/npm/v/use-sse)](https://www.npmjs.com/package/use-sse)
![Node.js CI](https://github.com/kmoskwiak/useSSE/workflows/Node.js%20CI/badge.svg?branch=master)
![Node.js CI](https://github.com/kmoskwiak/useSSE/workflows/CI/badge.svg?branch=master)

`useSSE` is abbreviation for use server-side effect. It is a custom React hook to perform asynchronous effects both on client and serve side.

Expand All @@ -15,58 +22,77 @@ npm i use-sse
Use `useSSE` to fetch data in component:

```jsx
import React from "react";
import { useSSE } from "use-sse";

const MyComponent = () => {
/**
* Create a custom component with effect
**/
const TitleComponent = () => {
const [data, error] = useSSE(() => {
return fetch("https://myapi.example.com").then((res) => res.json());
}, []);

return <div>{data.title}</div>;
return <h1>{data.title}</h1>;
};
```

All effects will be resolved on server side during rendering.
/**
* To take full advantage of a Suspense boundaries wrap each component in UniversalDataProvider
* You can also use ServerDataProvider or BrowserDataProvider
**/
export const Title = () => {
return (
<UniversalDataProvider>
<TitleComponent />
</UniversalDataProvider>
)
}
```

This is a part of server side render phase. Se an example for the whole code.
Load component using Suspense API:

```js
const { ServerDataContext, resolveData } = createServerContext();

// We need to render app twice.
// First - render App to reqister all effects
renderToString(
<ServerDataContext>
<App />
</ServerDataContext>
```jsx
import * as React from 'react';
import Title from './Title';

export const App = () => (
<div>
<React.Suspense fallback={'Loading...'}>
<Title/>
</React.Suspense>
</div>
);
```

// Wait for all effects to finish
const data = await resolveData();
All effects will be resolved on server side during rendering.

// Inject into html initial data
res.write(data.toHtml());
This is a part of server side render phase. See an example for the whole code.

// Render App for the second time
// This time data form effects will be avaliable in components
const htmlStream = renderToNodeStream(
<ServerDataContext>
<App />
</ServerDataContext>
);
```jsx
const stream = renderToPipeableStream(
<App />,
{
onShellReady() {
res.statusCode = didError ? 500 : 200;
res.setHeader('Content-type', 'text/html');
stream.pipe(res);
},
onShellError() {
res.statusCode = 500;
res.send('<h1>An error occurred</h1>');
},
onError(err) {
didError = true;
console.error(err);
},
},
);
```

On client side of application use `BroswerDataContext`:

```js
// This will create context will all data fetched during server side rendering
const BroswerDataContext = createBroswerContext();

```jsx
hydrate(
<BroswerDataContext>
<App />
</BroswerDataContext>,
<App />,
document.getElementById("app")
);
```
Expand All @@ -93,76 +119,7 @@ Returns an array with two elements `[data, error]`.
- `data` - resolved response from effect
- `error` - an error if effect rejected or if timeout happend.

---

### createServerContext()

Creates server side context.

```js
const { ServerDataContext, resolveData } = createServerContext();
```

#### Returns

`ServerDataContext` - React context provider component.

```html
<ServerDataContext>
<App />
</ServerDataContext>
```

`resolveData` - function to resolve all effects.

```js
const data = await resolveData(timeout);
```

| param | type | required | default value | description |
| --------- | -------- | -------- | ------------- | ----------------------------------------------- |
| `timeout` | `number` | false | `undefined` | max number of ms to wait for effects to resolve |

`data` is an object containing value of context.

Calling `data.toHtml(variableName)` will return a html script tak with stringified data:

| param | type | required | default value | description |
| -------------- | -------- | -------- | -------------------- | ----------------------- |
| `variableName` | `string` | false | \_initialDataContext | name of global variable |

```js
data.toHtml();
// "<script>window._initialDataContext = { context data };</script>"
```

Both should be used in server side render function.

---

### createBroswerContext()

Creates client side context.

```js
createBroswerContext(variableName);
```

#### params

| param | type | required | default value | description |
| -------------- | -------- | -------- | -------------------- | ----------------------- |
| `variableName` | `string` | false | \_initialDataContext | name of global variable |

#### returns

`BroswerDataContext` - React context provider for client side application

```html
<BroswerDataContext>
<App />
</BroswerDataContext>
```

## Examples

Expand Down
12 changes: 12 additions & 0 deletions babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": [
"@babel/env",
[
"@babel/preset-react",
{
"runtime": "automatic"
}
],
"@babel/preset-typescript"
]
}
26 changes: 26 additions & 0 deletions e2e/example.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test, expect } from '@playwright/test';

test('renders data from success response', async ({ page }) => {
await page.goto('http://localhost:3000/');

const container = await page.getByTestId('data');

await expect(container).toHaveText(/Jake The Dog Stretching/);
});

test('handles error from effect', async ({ page }) => {
await page.goto('http://localhost:3000/');

const container = await page.getByTestId('error');

await expect(container).toHaveText(/Error/);
});


test('renders data from endpoint with timeout', async ({ page }) => {
await page.goto('http://localhost:3000/');

const container = await page.getByTestId('long');

await expect(container).toHaveText(/Finn The Human Sword Fighting/);
});
3 changes: 0 additions & 3 deletions example/.babelrc

This file was deleted.

Loading

0 comments on commit 6887768

Please sign in to comment.