Flip the arguments of Blocks and make the query optional (#18374)
* Flip the arguments of Blocks and make the query optional * Rename Query to Load
This commit is contained in:
parent
fc7835c657
commit
a317bd033f
|
@ -152,7 +152,7 @@ export function reportGlobalError(response: Response, error: Error): void {
|
|||
}
|
||||
|
||||
function readMaybeChunk<T>(maybeChunk: Chunk<T> | T): T {
|
||||
if ((maybeChunk: any).$$typeof !== CHUNK_TYPE) {
|
||||
if (maybeChunk == null || (maybeChunk: any).$$typeof !== CHUNK_TYPE) {
|
||||
// $FlowFixMe
|
||||
return maybeChunk;
|
||||
}
|
||||
|
|
|
@ -29,12 +29,15 @@ describe('ReactFlight', () => {
|
|||
act = ReactNoop.act;
|
||||
});
|
||||
|
||||
function block(query, render) {
|
||||
function block(render, load) {
|
||||
return function(...args) {
|
||||
let curriedQuery = () => {
|
||||
return query(...args);
|
||||
if (load === undefined) {
|
||||
return [Symbol.for('react.server.block'), render];
|
||||
}
|
||||
let curriedLoad = () => {
|
||||
return load(...args);
|
||||
};
|
||||
return [Symbol.for('react.server.block'), render, curriedQuery];
|
||||
return [Symbol.for('react.server.block'), render, curriedLoad];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -70,8 +73,32 @@ describe('ReactFlight', () => {
|
|||
});
|
||||
|
||||
if (ReactFeatureFlags.enableBlocksAPI) {
|
||||
it('can transfer a Block to the client and render there', () => {
|
||||
function Query(firstName, lastName) {
|
||||
it('can transfer a Block to the client and render there, without data', () => {
|
||||
function User(props, data) {
|
||||
return (
|
||||
<span>
|
||||
{props.greeting} {typeof data}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
let loadUser = block(User);
|
||||
let model = {
|
||||
User: loadUser('Seb', 'Smith'),
|
||||
};
|
||||
|
||||
let transport = ReactNoopFlightServer.render(model);
|
||||
let root = ReactNoopFlightClient.read(transport);
|
||||
|
||||
act(() => {
|
||||
let UserClient = root.model.User;
|
||||
ReactNoop.render(<UserClient greeting="Hello" />);
|
||||
});
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Hello undefined</span>);
|
||||
});
|
||||
|
||||
it('can transfer a Block to the client and render there, with data', () => {
|
||||
function load(firstName, lastName) {
|
||||
return {name: firstName + ' ' + lastName};
|
||||
}
|
||||
function User(props, data) {
|
||||
|
@ -81,7 +108,7 @@ describe('ReactFlight', () => {
|
|||
</span>
|
||||
);
|
||||
}
|
||||
let loadUser = block(Query, User);
|
||||
let loadUser = block(User, load);
|
||||
let model = {
|
||||
User: loadUser('Seb', 'Smith'),
|
||||
};
|
||||
|
|
|
@ -44,12 +44,15 @@ describe('ReactFlightDOMRelay', () => {
|
|||
return model;
|
||||
}
|
||||
|
||||
function block(query, render) {
|
||||
function block(render, load) {
|
||||
return function(...args) {
|
||||
let curriedQuery = () => {
|
||||
return query(...args);
|
||||
if (load === undefined) {
|
||||
return [Symbol.for('react.server.block'), render];
|
||||
}
|
||||
let curriedLoad = () => {
|
||||
return load(...args);
|
||||
};
|
||||
return [Symbol.for('react.server.block'), render, curriedQuery];
|
||||
return [Symbol.for('react.server.block'), render, curriedLoad];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -89,7 +92,7 @@ describe('ReactFlightDOMRelay', () => {
|
|||
});
|
||||
|
||||
it.experimental('can transfer a Block to the client and render there', () => {
|
||||
function Query(firstName, lastName) {
|
||||
function load(firstName, lastName) {
|
||||
return {name: firstName + ' ' + lastName};
|
||||
}
|
||||
function User(props, data) {
|
||||
|
@ -99,7 +102,7 @@ describe('ReactFlightDOMRelay', () => {
|
|||
</span>
|
||||
);
|
||||
}
|
||||
let loadUser = block(Query, User);
|
||||
let loadUser = block(User, load);
|
||||
let model = {
|
||||
User: loadUser('Seb', 'Smith'),
|
||||
};
|
||||
|
|
|
@ -62,7 +62,7 @@ describe('ReactFlightDOM', () => {
|
|||
};
|
||||
}
|
||||
|
||||
function block(query, render) {
|
||||
function block(render, load) {
|
||||
let idx = webpackModuleIdx++;
|
||||
webpackModules[idx] = {
|
||||
d: render,
|
||||
|
@ -73,10 +73,13 @@ describe('ReactFlightDOM', () => {
|
|||
name: 'd',
|
||||
};
|
||||
return function(...args) {
|
||||
let curriedQuery = () => {
|
||||
return query(...args);
|
||||
if (load === undefined) {
|
||||
return [Symbol.for('react.server.block'), render];
|
||||
}
|
||||
let curriedLoad = () => {
|
||||
return load(...args);
|
||||
};
|
||||
return [Symbol.for('react.server.block'), 'path/' + idx, curriedQuery];
|
||||
return [Symbol.for('react.server.block'), 'path/' + idx, curriedLoad];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -288,7 +291,7 @@ describe('ReactFlightDOM', () => {
|
|||
reject(e);
|
||||
};
|
||||
});
|
||||
function Query() {
|
||||
function load() {
|
||||
if (promise) {
|
||||
throw promise;
|
||||
}
|
||||
|
@ -300,8 +303,8 @@ describe('ReactFlightDOM', () => {
|
|||
function DelayedText({children}, data) {
|
||||
return <Text>{children}</Text>;
|
||||
}
|
||||
let _block = block(Query, DelayedText);
|
||||
return [_block(), _resolve, _reject];
|
||||
let loadBlock = block(DelayedText, load);
|
||||
return [loadBlock(), _resolve, _reject];
|
||||
}
|
||||
|
||||
const [FriendsModel, resolveFriendsModel] = makeDelayedText();
|
||||
|
|
|
@ -49,8 +49,30 @@ describe('ReactBlocks', () => {
|
|||
};
|
||||
});
|
||||
|
||||
it.experimental('renders a simple component', () => {
|
||||
function User(props, data) {
|
||||
return <div>{typeof data}</div>;
|
||||
}
|
||||
|
||||
function App({Component}) {
|
||||
return (
|
||||
<Suspense fallback={'Loading...'}>
|
||||
<Component name="Name" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
let loadUser = block(User);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App Component={loadUser()} />);
|
||||
});
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<div>undefined</div>);
|
||||
});
|
||||
|
||||
it.experimental('prints the name of the render function in warnings', () => {
|
||||
function Query(firstName) {
|
||||
function load(firstName) {
|
||||
return {
|
||||
name: firstName,
|
||||
};
|
||||
|
@ -69,7 +91,7 @@ describe('ReactBlocks', () => {
|
|||
);
|
||||
}
|
||||
|
||||
let loadUser = block(Query, User);
|
||||
let loadUser = block(User, load);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.act(() => {
|
||||
|
@ -86,8 +108,8 @@ describe('ReactBlocks', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it.experimental('renders a component with a suspending query', async () => {
|
||||
function Query(id) {
|
||||
it.experimental('renders a component with a suspending load', async () => {
|
||||
function load(id) {
|
||||
return {
|
||||
id: id,
|
||||
name: readString('Sebastian'),
|
||||
|
@ -102,7 +124,7 @@ describe('ReactBlocks', () => {
|
|||
);
|
||||
}
|
||||
|
||||
let loadUser = block(Query, Render);
|
||||
let loadUser = block(Render, load);
|
||||
|
||||
function App({User}) {
|
||||
return (
|
||||
|
@ -128,7 +150,7 @@ describe('ReactBlocks', () => {
|
|||
it.experimental(
|
||||
'does not support a lazy wrapper around a chunk',
|
||||
async () => {
|
||||
function Query(id) {
|
||||
function load(id) {
|
||||
return {
|
||||
id: id,
|
||||
name: readString('Sebastian'),
|
||||
|
@ -143,7 +165,7 @@ describe('ReactBlocks', () => {
|
|||
);
|
||||
}
|
||||
|
||||
let loadUser = block(Query, Render);
|
||||
let loadUser = block(Render, load);
|
||||
|
||||
function App({User}) {
|
||||
return (
|
||||
|
@ -187,7 +209,7 @@ describe('ReactBlocks', () => {
|
|||
it.experimental(
|
||||
'can receive updated data for the same component',
|
||||
async () => {
|
||||
function Query(firstName) {
|
||||
function load(firstName) {
|
||||
return {
|
||||
name: firstName,
|
||||
};
|
||||
|
@ -203,7 +225,7 @@ describe('ReactBlocks', () => {
|
|||
);
|
||||
}
|
||||
|
||||
let loadUser = block(Query, Render);
|
||||
let loadUser = block(Render, load);
|
||||
|
||||
function App({User}) {
|
||||
return (
|
||||
|
|
|
@ -191,11 +191,11 @@ export function resolveModelToJSON(
|
|||
}
|
||||
}
|
||||
case '2': {
|
||||
// Query
|
||||
let query: () => ReactModel = (value: any);
|
||||
// Load function
|
||||
let load: () => ReactModel = (value: any);
|
||||
try {
|
||||
// Attempt to resolve the query.
|
||||
return query();
|
||||
// Attempt to resolve the data.
|
||||
return load();
|
||||
} catch (x) {
|
||||
if (
|
||||
typeof x === 'object' &&
|
||||
|
@ -204,12 +204,12 @@ export function resolveModelToJSON(
|
|||
) {
|
||||
// Something suspended, we'll need to create a new segment and resolve it later.
|
||||
request.pendingChunks++;
|
||||
let newSegment = createSegment(request, query);
|
||||
let newSegment = createSegment(request, load);
|
||||
let ping = newSegment.ping;
|
||||
x.then(ping, ping);
|
||||
return serializeIDRef(newSegment.id);
|
||||
} else {
|
||||
// This query failed, encode the error as a separate row and reference that.
|
||||
// This load failed, encode the error as a separate row and reference that.
|
||||
request.pendingChunks++;
|
||||
let errorId = request.nextChunkId++;
|
||||
emitErrorChunk(request, errorId, x);
|
||||
|
|
|
@ -16,14 +16,14 @@ import {
|
|||
REACT_FORWARD_REF_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
type BlockQueryFunction<Args: Iterable<any>, Data> = (...args: Args) => Data;
|
||||
type BlockLoadFunction<Args: Iterable<any>, Data> = (...args: Args) => Data;
|
||||
export type BlockRenderFunction<Props, Data> = (
|
||||
props: Props,
|
||||
data: Data,
|
||||
) => React$Node;
|
||||
|
||||
type Payload<Props, Args: Iterable<any>, Data> = {
|
||||
query: BlockQueryFunction<Args, Data>,
|
||||
load: BlockLoadFunction<Args, Data>,
|
||||
args: Args,
|
||||
render: BlockRenderFunction<Props, Data>,
|
||||
};
|
||||
|
@ -44,20 +44,20 @@ function lazyInitializer<Props, Args: Iterable<any>, Data>(
|
|||
): BlockComponent<Props, Data> {
|
||||
return {
|
||||
$$typeof: REACT_BLOCK_TYPE,
|
||||
_data: payload.query.apply(null, payload.args),
|
||||
_data: payload.load.apply(null, payload.args),
|
||||
_render: payload.render,
|
||||
};
|
||||
}
|
||||
|
||||
export function block<Args: Iterable<any>, Props, Data>(
|
||||
query: BlockQueryFunction<Args, Data>,
|
||||
render: BlockRenderFunction<Props, Data>,
|
||||
load?: BlockLoadFunction<Args, Data>,
|
||||
): (...args: Args) => Block<Props> {
|
||||
if (__DEV__) {
|
||||
if (typeof query !== 'function') {
|
||||
if (load !== undefined && typeof load !== 'function') {
|
||||
console.error(
|
||||
'Blocks require a query function but was given %s.',
|
||||
query === null ? 'null' : typeof query,
|
||||
'Blocks require a load function, if provided, but was given %s.',
|
||||
load === null ? 'null' : typeof load,
|
||||
);
|
||||
}
|
||||
if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
|
||||
|
@ -97,11 +97,28 @@ export function block<Args: Iterable<any>, Props, Data>(
|
|||
}
|
||||
}
|
||||
|
||||
if (load === undefined) {
|
||||
return function(): Block<Props> {
|
||||
let blockComponent: BlockComponent<Props, void> = {
|
||||
$$typeof: REACT_BLOCK_TYPE,
|
||||
_data: undefined,
|
||||
// $FlowFixMe: Data must be void in this scenario.
|
||||
_render: render,
|
||||
};
|
||||
|
||||
// $FlowFixMe
|
||||
return blockComponent;
|
||||
};
|
||||
}
|
||||
|
||||
// Trick to let Flow refine this.
|
||||
let loadFn = load;
|
||||
|
||||
return function(): Block<Props> {
|
||||
let args: Args = arguments;
|
||||
|
||||
let payload: Payload<Props, Args, Data> = {
|
||||
query: query,
|
||||
load: loadFn,
|
||||
args: args,
|
||||
render: render,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue