[0.71.0-rc.0] Gap, flexWrap & alignContent renders wrong size

This issue has been created since 2022-11-15.

Description

When using gap, flexWrap and alignContent, size is miscalculated, because gap isn't taken into account when computing the sizes, so if any wrapping happens because of gap, it is not computed properly

Please note that I have tried with and without legacyStretchBehavior enabled (by patching react-native).

Version

0.71.0-rc.0

Output of npx react-native info

System:
OS: macOS 12.6
CPU: (8) arm64 Apple M1
Memory: 155.78 MB / 16.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.11.0 - /usr/local/bin/node
Yarn: 1.22.17 - /opt/homebrew/bin/yarn
npm: 8.19.2 - /usr/local/bin/npm
Watchman: 2022.10.10.00 - /opt/homebrew/bin/watchman
Managers:
CocoaPods: 1.11.3 - /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5
Android SDK: Not Found
IDEs:
Android Studio: 2021.3 AI-213.7172.25.2113.9123335
Xcode: 13.4/13F17a - /usr/bin/xcodebuild
Languages:
Java: 11.0.15 - /usr/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: 18.2.0 => 18.2.0
react-native: 0.71.0-rc.0 => 0.71.0-rc.0
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found

Steps to reproduce

In order to reproduce you need to have:

  • 1 container with flexWrap: ‘wrap’ & alignContent: ‘stretch’ & gap: someValue
  • Children that wrap only because of the gap property (they would fit in the container otherwise)


When that happens the size calculated by alignContent: ’stretch’ doesn’t take into account the wrap, so each child will have the full container size instead of half

.

Example:

  • Container of 300 x 300 direction 'row', flexWrap: 'wrap', alignContent: 'stretch'
  • 5 children of width: 60, flexGrow: 1

Before adding gap: 1
Simulator Screen Shot - iPhone 13 - 2022-11-15 at 15 06 01
After adding gap: 1
Simulator Screen Shot - iPhone 13 - 2022-11-15 at 15 08 14

Note that if the child where already wrapping (example width: 61) this would not happen. It only happens if the wrapping is caused by gap
Simulator Screen Shot - iPhone 13 - 2022-11-15 at 15 09 40

Snack, code example, screenshot, or link to a repository

Web equivalent:
https://jsfiddle.net/t8q6xevj/1/
Native Code used to test:

Feel free to alter gap of container / width of children

import React from 'react';
import {
  SafeAreaView,
  View,
} from 'react-native';

function App(): JSX.Element {
  return (
    <SafeAreaView style={{flexGrow: 1, alignItems: 'center', justifyContent: 'center'}}>
        <View
          style={{
            height: 300,
            width: 300,
            backgroundColor: 'blue',
            flexWrap: 'wrap',
            flexDirection: 'row',
            alignContent: 'stretch',
            gap: 1,
          }}>
          <View
            style={redChild}></View>
          <View
            style={greenChild}></View>
          <View
            style={redChild}></View>
          <View
            style={greenChild}></View>
          <View
            style={redChild}></View>
        </View>
    </SafeAreaView>
  );
}

const child = {
  flexGrow: 1,
  width: 60,
}

const redChild = {
  ...child,
  backgroundColor: 'red',
};
const greenChild = {
  ...child,
  backgroundColor: 'green',
}

export default App;
kelset wrote this answer on 2022-11-15
NickGerleman wrote this answer on 2022-11-15

Good report (and good we can fix before much usage). Gap should not cause the height to increase when it is the reason for wrapping. Will take a look and add this as a test. FYI @jacobp100

YGCalculateCollectFlexItemsRowValues possibly prepares something unexpected, or a later step is doing something wrong to set cross-axis dimensions. Should be able to debug this.

jacobp100 wrote this answer on 2022-11-15

Since gap sets both columnGap and rowGap, I wonder if it happens when you only set columnGap. That's the property that causes it to overflow - but it could be rowGap that is actually the issue and it was just coincidental it manifested during the wrapping

NickGerleman wrote this answer on 2022-11-15

Still happens with just columnGap. I have a fixture working which causes a failing unit test.

image

Planning to dig in a bit, since I know running these UTs is currently a giant pain in OSS (I thought I would have added the CMake baed UTs a while back but have been awful about maintaining focus recently). But should be manually reproable as well.

jacobp100 wrote this answer on 2022-11-15

If you post the branch name I can dig a bit too

jacobp100 wrote this answer on 2022-11-15

Maybe it's worth adding a disclaimer about gap in the release notes too. Warn people it may have bugs, and that fixing those bugs later on may break their layouts

jacobp100 wrote this answer on 2022-11-15
kelset wrote this answer on 2022-11-15

Maybe it's worth adding a disclaimer about gap in the release notes too. Warn people it may have bugs, and that fixing those bugs later on may break their layouts

This is actually something that I was talking about with @Titozzz: is there a way to maybe have the flex-gap stuff hidden behind some experimental flag so that folks know that fixes/issues/bugs and breaking changes might come at a later stage as things pop up?

NickGerleman wrote this answer on 2022-11-15

Debugging this, comparing wrap with gap vs wrap due to size overflow.

Step 8: "MULTI-LINE CONTENT ALIGNMENT" is normally what sets things up. Children should most recently have been called with cross axis YGMeasureModeAtMost which measures cross-axis size is at zero, but YGAlignStretch will set the cross-dimension based on available size divided by line count.

In the gap case, measured cross-axis dimensions are 300px at this step. In the non-gap case it is transiently this (I think it is the result of a different YGMeasureMode?). But it means at some previous step we are not measuring cross-axis correctly specifically in the gap case.

NickGerleman wrote this answer on 2022-11-15

Maybe it's worth adding a disclaimer about gap in the release notes too. Warn people it may have bugs, and that fixing those bugs later on may break their layouts

This is actually something that I was talking about with @Titozzz: is there a way to maybe have the flex-gap stuff hidden behind some experimental flag so that folks know that fixes/issues/bugs and breaking changes might come at a later stage as things pop up?

I don't think we should gate it. There are more conformance tests we could maybe add to have caught this (e.g. a plan was to generate more Yoga fixtures from WPT), but real usage is also what led to bug discovery here, and putting the feature behind a flag would prevent that.

I do want to gate future capabilities behind StrictLayout, where we want to explicitly allows instability to make conformance fixes while staging. But flex gap is already here, and we have told people they will get it.

intergalacticspacehighway wrote this answer on 2022-11-15

I think the issue could be on this statement where it doesn't account gap and childCrossSize gets set to availableInnerCrossDim i.e. 300px. Maybe we should account gap here as well.

intergalacticspacehighway wrote this answer on 2022-11-15
 currentRelativeChild->getGapForAxis(crossAxis, availableInnerCrossDim).isUndefined()

Adding this fixes the issue!

Screenshot 2022-11-15 at 10 59 52 PM

NickGerleman wrote this answer on 2022-11-15

Okay, so I think I'm understanding this?

In YGDistributeFreeSpaceSecondPass, if we do not have overflow (determined by flexBasisOverflows), we have stretch cross-alignment, and we reason that nothing can add to main axis dimensions, we know we're a single line and want to take full cross dimensions. and can set YGMeasureModeExactly which uses parent dimensions. Guessing an optimization?

If we do have overflow, then we set YGMeasureModeAtMost to find minimum possible cross-axis dimensions instead. I confirmed this is normally taken.

Probably worth checking the other usages of flexBasisOverflows in case there are other options taken in the non-overflow case that only accounts for basis. Or to see whether we should put our math there.

NickGerleman wrote this answer on 2022-11-15

Yeah, it looks like totalOuterFlexBasis, used for overflow logic, is more than just flex basis, it also includes margins already. It is also used for setting main axis measure mode to parent width instead of minimum size if we have overflow.

I think the right solution here is to rename this, and incorporate gap spacing into its checks.

NickGerleman wrote this answer on 2022-11-15

PR here: facebook/yoga#1173
New UT and previous ones are now passing.

Titozzz wrote this answer on 2022-11-15

I also think we should not gate it, but warn users that if it doesn't match the spec there will be breaking changes might be a good idea

NickGerleman wrote this answer on 2022-11-16

A general strategy for compatibility fixes has been:

  1. Enable the fix everywhere if we think breaks are unlikely. I.e. does the change cause issues if we turn it on for an experiment group in a very large codebase?
  2. Put the continuing set of the rest of the breaking fixes under StrictLayout.

We are starting off at zero usage of gap within the RN ecosystem, so we are probably safe to make fixes while the usage is building, but after, we could fold likely impacting changes into StrictLayout.

NickGerleman wrote this answer on 2022-11-16

It's also one of the reasons I want to gate new major additions behind StrictLayout, since it makes that guarantee explicit (unless you opt into a quirk).

Titozzz wrote this answer on 2022-11-17

Fixed by 1aa157b

More Details About Repo
Owner Name facebook
Repo Name react-native
Full Name facebook/react-native
Language JavaScript
Created Date 2015-01-09
Updated Date 2022-12-10
Star Count 106395
Watcher Count 3660
Fork Count 22685
Issue Count 2288

YOU MAY BE INTERESTED

Issue Title Created Date Comment Count Updated Date
Opt-in to hacktoberfest 1 2022-09-27 2022-11-10
Create Upload component 1 2022-06-01 2022-11-04
inaccurate MANO annotation 3 2022-05-10 2022-12-09
Missing RPC endpoint to fetch contract constants 4 2022-09-30 2022-11-06
Better pointer generation 0 2022-07-27 2022-11-26
Support to sdk lower than 23 4 2022-02-09 2022-12-01
Out of memory when resuming a long-running emitter 0 2022-02-09 2022-08-16
为什么不使用0.8.8版本的ijk而是使用0.7.15呢 5 2021-11-19 2022-11-16
Remove Resource Hooks 0 2021-07-16 2022-09-22
Ability to limit concurrency? 4 2021-03-19 2022-11-16
[NV-185] - Add variable protection docs 4 2021-09-25 2022-10-17
ShadowImage问题 0 2018-01-24 2022-01-12
Not mobile responsive 5 2021-07-23 2022-11-09
Tool support for writing plugins in C# 0 2022-02-14 2022-10-05
'Check for updates' opens Lens unnecessarily 0 2022-07-28 2022-11-04
Metrics collector 0 2013-01-23 2022-01-16
Subnet finder missing by_cidr method 1 2021-10-08 2022-11-22
Should identifier match id? 2 2021-02-17 2022-10-25
USD export PreviewSurface Prim missing class identifier (USDU-234) 1 2021-12-11 2022-11-29
Getting "Uncaught Invariant Violation: Expected drag drop context" error despite the fact everything is wrapped in DndProvider 1 2022-01-01 2022-12-09
Provide APIs to get JobSpec and JobRuns based on Resource URN 2 2022-06-06 2022-11-20
使用kitex命令自动生成代码,.thrift文件能够正常生产, .proto文件就会报错 3 2022-05-17 2022-11-28
[feature] Should be able to set _DEFAULT_LAUNCHER_IMAGE using environment variable in notebook pod 3 2022-01-27 2022-10-06
Local external subtitle files with STRM videos don't work again 4 2021-02-06 2022-09-29
Indentation with a guard on a new line 2 2022-01-17 2022-12-05
SIREN for french Polynesia and New Caledonia 1 2022-06-02 2022-11-03
Invalid regular expression 2 2021-05-11 2022-12-09
general: change keyword representation from clojerl.Keyword to plain Erlang atom 1 2016-01-26 2022-11-02
Update the default explicit histogram buckets 0 2022-09-23 2022-12-09
feat: add support for @TestTarget annotation for junit tests written in scala 0 2021-10-04 2022-04-29
error TS2307: Cannot find module '@bit/rgautam.angular-tutorial.product-list'. 4 2021-08-23 2022-11-28
How to develop speech to text with Python in termux 4 2020-02-12 2022-11-27
`mc alias set` is not working with the `--insecure` flag 1 2022-03-17 2022-12-08
Clarify public properties are serialized/deserialized 1 2021-06-18 2022-11-21
After new nodepool -> Client does not have permission to perform action 'Microsoft.ManagedIdentity/userAssignedIdentities/assign/action 0 2022-05-07 2022-10-20
Support for MultiPath TCP 0 2022-10-17 2022-12-09
Submission for SC consideration: PEP 644 -- Require OpenSSL 1.1.1 or newer 5 2021-03-03 2022-12-08
IPv6 support 0 2021-07-13 2022-01-12
Send a PagerDuty alert when a plugin gets autodisabled 0 2021-08-02 2021-10-16
[NLL] loss of span for type outlives errors in closures/async blocks 3 2022-07-14 2022-09-17
Adding custom headers, but not seeing them in Fiddler 10 2021-12-22 2022-10-24
Bump pre-commit from 2.13.0 to 2.14.1 0 2021-09-01 2022-10-06
Rasterized outputs do not match transform_points_screen for non-square images 5 2021-07-14 2022-11-01
可能只下载某一个视频或某一个人的全部视频吗? 0 2020-05-09 2022-09-14
Unusually high ncRNA detected on same contig & coordinate - Archea 0 2021-11-17 2022-11-29
feat: As a user, I want to add `apisix test` to `apisix help`, so that people could know this command quickly 2 2022-05-27 2022-12-09
what does PropertyUtil.v2 function mean? 6 2021-11-30 2022-07-14
[Improvement/support]: README.md example update 3 2021-10-15 2022-11-21
Upon moving to the new `hey`/`flake` based system, ZGEN_SOURCE broken 0 2020-10-28 2022-12-01
Eraroraporto - trovu duoblajn tradukojn 1 2021-03-18 2022-10-24