Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight Fixture] Refetching #20316

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fixtures/flight/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"camelcase": "^5.2.0",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"concurrently": "^5.0.0",
"cors": "^2.8.5",
"css-loader": "2.1.1",
"dotenv": "6.2.0",
"dotenv-expand": "5.1.0",
Expand Down
39 changes: 28 additions & 11 deletions fixtures/flight/server/cli.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ babelRegister({
});

const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());

// Application
app.get('/', function(req, res) {
Expand All @@ -25,18 +28,32 @@ app.get('/', function(req, res) {
require('./handler.server.js')(req, res);
});

let todos = [
{
id: 1,
text: 'Shave yaks',
},
{
id: 2,
text: 'Eat kale!',
},
];

app.get('/todos', function(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json([
{
id: 1,
text: 'Shave yaks',
},
{
id: 2,
text: 'Eat kale',
},
]);
res.json(todos);
});

app.post('/todos', function(req, res) {
todos.push({
id: todos.length + 1,
text: req.body.text,
});
res.json(todos);
});

app.delete('/todos/:id', function(req, res) {
todos = todos.filter(todo => todo.id !== Number(req.params.id));
res.json(todos);
});

app.listen(3001, () => {
Expand Down
22 changes: 18 additions & 4 deletions fixtures/flight/server/handler.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = async function(req, res) {
[resolve('../src/Counter.client.js')]: {
Counter: {
id: './src/Counter.client.js',
chunks: ['2'],
chunks: ['1'],
name: 'Counter',
},
},
Expand All @@ -30,22 +30,36 @@ module.exports = async function(req, res) {
name: 'Counter',
},
},
[resolve('../src/AddTodo.client.js')]: {
default: {
id: './src/AddTodo.client.js',
chunks: ['2'],
name: 'default',
},
},
[resolve('../src/ShowMore.client.js')]: {
default: {
id: './src/ShowMore.client.js',
chunks: ['3'],
chunks: ['4'],
name: 'default',
},
'': {
id: './src/ShowMore.client.js',
chunks: ['3'],
chunks: ['4'],
name: '',
},
'*': {
id: './src/ShowMore.client.js',
chunks: ['3'],
chunks: ['4'],
name: '*',
},
},
[resolve('../src/DeleteTodo.client.js')]: {
default: {
id: './src/DeleteTodo.client.js',
chunks: ['5'],
name: 'default',
},
},
});
};
27 changes: 27 additions & 0 deletions fixtures/flight/src/AddTodo.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';

import Container from './Container.js';
import useMutation from './useMutation.client';

export default function AddTodo() {
const [text, setText] = React.useState('');
const [postTodo, isPosting] = useMutation(async () => {
setText('');
await fetch('http://localhost:3001/todos', {
method: 'POST',
mode: 'cors',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({text}),
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can have this fetch return a new Flight response. It means you'll have to hard code which Flight root entry point this corresponds to. That's kind of a hard problem for a router but it's good to indicate that it is a problem that's worth solving.

});
return (
<Container>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={() => postTodo()}>add</button>
{isPosting && ' ...'}
</Container>
);
}
10 changes: 8 additions & 2 deletions fixtures/flight/src/App.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import Container from './Container.js';

import {Counter} from './Counter.client.js';
import {Counter as Counter2} from './Counter2.client.js';

import AddTodo from './AddTodo.client.js';
import DeleteTodo from './DeleteTodo.client.js';
import ShowMore from './ShowMore.client.js';

export default function App() {
Expand All @@ -17,9 +18,14 @@ export default function App() {
<Counter2 />
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
<li key={todo.id}>
{todo.text} <DeleteTodo id={todo.id} />
</li>
))}
</ul>
<AddTodo />
<br />
<br />
<ShowMore>
<p>Lorem ipsum</p>
</ShowMore>
Expand Down
3 changes: 3 additions & 0 deletions fixtures/flight/src/Context.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as React from 'react';

export const RefreshContext = React.createContext(null);
12 changes: 12 additions & 0 deletions fixtures/flight/src/DeleteTodo.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react';
import useMutation from './useMutation.client';

export default function DeleteTodo({id}) {
const [deleteTodo] = useMutation(async () => {
await fetch('http://localhost:3001/todos/' + id, {
method: 'DELETE',
mode: 'cors',
});
});
return <button onClick={() => deleteTodo()}>x</button>;
}
22 changes: 17 additions & 5 deletions fixtures/flight/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@ import * as React from 'react';
import {Suspense} from 'react';
import ReactDOM from 'react-dom';
import ReactTransportDOMClient from 'react-transport-dom-webpack';
import {RefreshContext} from './Context.client';

let data = ReactTransportDOMClient.createFromFetch(
let initialData = ReactTransportDOMClient.createFromFetch(
fetch('http://localhost:3001')
);

function Content() {
return data.readRoot();
let [data, setData] = React.useState(initialData);

function refresh() {
setData(
ReactTransportDOMClient.createFromFetch(fetch('http://localhost:3001'))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have it, this should use the client cache and it's the cache that gets reset.

However, even in that world this can be optimized by seeding the client cache with a new entry.

The idiomatic way would be to seed it with a response that comes from the same HTTP request as the mutation though.

That last part you can prepare for now even without a client cache. You just need to pass this in to the refresh function as an argument.

Copy link
Collaborator

@sebmarkbage sebmarkbage Dec 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be explicit. The reason for this is not perf but eventual consistency. You can easily get into a state where you never see your mutation in prod because the propagation is always slower than immediately handling the next request. That's a really bad user experience and bad developer experience to only know about it once you hit prod.

);
}

return (
<RefreshContext.Provider value={refresh}>
{data.readRoot()}
</RefreshContext.Provider>
);
}

ReactDOM.render(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg

ReactDOM.unstable_createRoot(document.getElementById('root')).render(
<Suspense fallback={<h1>Loading...</h1>}>
<Content />
</Suspense>,
document.getElementById('root')
</Suspense>
);

// Create entry points for Client Components.
Expand Down
23 changes: 23 additions & 0 deletions fixtures/flight/src/useMutation.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';

import {RefreshContext} from './Context.client';

export default function useMutation(performMutation) {
const [pendingCount, setPendingCount] = React.useState(0);
const [startTransition, isTransitioning] = React.unstable_useTransition();
const refresh = React.useContext(RefreshContext);

async function handleMutation(...args) {
setPendingCount(c => c + 1);
try {
await performMutation(...args);
} finally {
setPendingCount(c => c - 1);
}
startTransition(() => {
refresh();
});
}

return [handleMutation, pendingCount > 0 || isTransitioning];
}
12 changes: 10 additions & 2 deletions fixtures/flight/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2804,6 +2804,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"

cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"

cosmiconfig@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc"
Expand Down Expand Up @@ -6457,7 +6465,7 @@ oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"

object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@4.1.1, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"

Expand Down Expand Up @@ -9309,7 +9317,7 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"

vary@~1.1.2:
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"

Expand Down