@@ -1789,4 +1789,83 @@ describe('ReactAsyncActions', () => {
17891789 } ) ;
17901790 assertLog ( [ 'reportError: Oops' ] ) ;
17911791 } ) ;
1792+
1793+ // @gate enableOptimisticKey
1794+ it ( 'reconciles against new items when optimisticKey is used' , async ( ) => {
1795+ const startTransition = React . startTransition ;
1796+
1797+ function Item ( { text} ) {
1798+ const [ initialText ] = React . useState ( text ) ;
1799+ return < span > { initialText + '-' + text } </ span > ;
1800+ }
1801+
1802+ let addOptimisticItem ;
1803+ function App ( { items} ) {
1804+ const [ optimisticItems , _addOptimisticItem ] = useOptimistic (
1805+ items ,
1806+ ( canonicalItems , optimisticText ) =>
1807+ canonicalItems . concat ( {
1808+ id : React . optimisticKey ,
1809+ text : optimisticText ,
1810+ } ) ,
1811+ ) ;
1812+ addOptimisticItem = _addOptimisticItem ;
1813+ return (
1814+ < div >
1815+ { optimisticItems . map ( item => (
1816+ < Item key = { item . id } text = { item . text } />
1817+ ) ) }
1818+ </ div >
1819+ ) ;
1820+ }
1821+
1822+ const A = {
1823+ id : 'a' ,
1824+ text : 'A' ,
1825+ } ;
1826+
1827+ const B = {
1828+ id : 'b' ,
1829+ text : 'B' ,
1830+ } ;
1831+
1832+ const root = ReactNoop . createRoot ( ) ;
1833+ await act ( ( ) => {
1834+ root . render ( < App items = { [ A ] } /> ) ;
1835+ } ) ;
1836+ expect ( root ) . toMatchRenderedOutput (
1837+ < div >
1838+ < span > A-A</ span >
1839+ </ div > ,
1840+ ) ;
1841+
1842+ // Start an async action using the non-hook form of startTransition. The
1843+ // action includes an optimistic update.
1844+ await act ( ( ) => {
1845+ startTransition ( async ( ) => {
1846+ addOptimisticItem ( 'b' ) ;
1847+ await getText ( 'Yield before updating' ) ;
1848+ startTransition ( ( ) => root . render ( < App items = { [ A , B ] } /> ) ) ;
1849+ } ) ;
1850+ } ) ;
1851+ // Because the action hasn't finished yet, the optimistic UI is shown.
1852+ expect ( root ) . toMatchRenderedOutput (
1853+ < div >
1854+ < span > A-A</ span >
1855+ < span > b-b</ span >
1856+ </ div > ,
1857+ ) ;
1858+
1859+ // Finish the async action. The optimistic state is reverted and replaced by
1860+ // the canonical state. The state is transferred to the new row.
1861+ await act ( ( ) => {
1862+ resolveText ( 'Yield before updating' ) ;
1863+ } ) ;
1864+ expect ( root ) . toMatchRenderedOutput (
1865+ < div >
1866+ < span > A-A</ span >
1867+ < span > b-B</ span >
1868+ </ div > ,
1869+ ) ;
1870+ } ) ;
17921871} ) ;
0 commit comments