necolas / react-native-web

Cross-platform React UI packages
https://necolas.github.io/react-native-web
MIT License
21.67k stars 1.79k forks source link

Notice: intent to deprecate and remove className prop from View and Text #1146

Closed necolas closed 5 years ago

necolas commented 6 years ago

The className prop is going to be deprecated from View and Text (and anything else it might currently be available on). This prop was only supported as an escape-hatch for Twitter Lite's migration from a View implemented atop of CSS Modules to the React Native View.

Rough plan at the moment is:

If you're relying on this undocumented escape-hatch please let me know the details.

seahorsepip commented 6 years ago

I've used className in the past to get a reference to the dom node when a browser only work around was necessary. I tried to get the dom node trough the ref but couldn't find it.

Also I've used it in the past to apply css classes that have browser specific psuedo selectors to hide scrollbars for example. Since I can't find anything about using css psuedo classes in react native which doesn't make sense but is needed in the example above.

Any recommendations?

steida commented 5 years ago

className is useful escape-hatch for browser only styles. I don't know how to float an image in another way. Sure RNW can be strict, but the evolution is incremental.

Maybe some prefix for web-only inline styles can fix it.

seahorsepip commented 5 years ago

Just an update, I've currently replaced all views that used the className prop as escape hatch with divs which just seems to work like a react component instead of a react native web component. So a .web.js file could contain mixed components like: <View>, <Text>, <div>, <h2> etc.

e.g.

render() {
   return <div className={'not-an-escape-hatch-but-mixed-components'}><Text>Hello</Text></div>;
}
necolas commented 5 years ago

Mixing with classNames is not safe

I don't know how to float an image

Use the float style

kristerkari commented 5 years ago

I know that I'm very late to the discussion as I haven't for some reason noticed this before.

I have some projects that are using React Native Web and CSS modules. If the className gets removed, then is there any easy way to bring back the support, for example enable with an option or a possibility to create a small library that adds support for className?

I personally really like the idea that RNW would be compatible with the Web and Web React with allowing the usage of className like it has done so far.

https://reactjs.org/docs/faq-styling.html#how-do-i-add-css-classes-to-components

Note that this functionality is not a part of React, but provided by third-party libraries. React does not have an opinion about how styles are defined; if in doubt, a good starting point is to define your styles in a separate *.css file as usual and refer to them using className.

necolas commented 5 years ago

Going forward, you should never mix CSS modules with React Native components. In practice that means doing this…

import classes from './styles.css';
const A = () => <View className={classes.root} />

…will have no impact on the presentation. But you can continue to use CSS modules with React DOM components, and those React DOM components can be used in the same web app as React Native components.

kristerkari commented 5 years ago

Going forward, you should never mix CSS modules with React Native components.

My case is a bit different because I'm using CSS Modules + Sass or PostCSS in my React Native projects. I have also been planning on starting to migrate Web projects to use RNW, but if the support for CSS modules is removed, then I can not share any components between my RN projects and Web.

I know that this is not a problem for most of the users, but it breaks the RN<->Web compatibility for me and a few other people who are using CSS pre-processors in React Native and might possibly want to render the code in a browser.

Here's the link to my React Native CSS modules project if you want to read more: https://github.com/kristerkari/react-native-css-modules

necolas commented 5 years ago

If your project is compatible with RN shouldn't it be compatible with RNW without relying on the className prop being passed to the DOM?

kristerkari commented 5 years ago

If your project is compatible with RN shouldn't it be compatible with RNW without relying on the className prop being passed to the DOM?

I have created a Babel plugin that is used only on the React Native side to transform className prop to style. That way you can use className for both RN and Web.

That way I can use CSS modules on both platforms together with RNW.

kristerkari commented 5 years ago

To give you a better idea, I have created simple example apps to show how you can render regular CSS or Sass on Android, iOS, and Web: https://github.com/kristerkari/react-native-css-modules#example-apps

necolas commented 5 years ago

CSS modules doesn't have the same safety or resolving characteristics as React Native styles.

kristerkari commented 5 years ago

CSS modules doesn't have the same safety or resolving characteristics as React Native styles.

I am fully aware of that, but it also does not mean that you can not create well functioning Web apps using CSS modules.

I'm ok with the className support not being enabled by default, but it would be nice if there was a way to opt-in with a setting to enable it, or something similar.

steida commented 5 years ago

@kristerkari You can make a higher order component with setNativeProps in useEffect. You can also leverage nativeID.

kristerkari commented 5 years ago

There are a few additional things that I would like to be considered related to RNW's CSS modules support.

  1. Writing regular CSS with React Native's constraints means that the CSS stays a lot simpler and easier to maintain. Styling with CSS files works the same way as using an Javascript object with styles and the style property in React Native, but it's just presented differently to the user. I have created a stylelint config that can be used to validate any CSS to be React Native compatible even if you only use it on the Web. I know that it's not the same as using StyleSheet, but better than using CSS modules without any restrictions.
  2. Using CSS modules allows CSS pre/post-processors to be used in React Native, and also allows the default RN styling to be extended. I have for example added support for CSS Media Queries and CSS viewport units to React Native. You can use Javascript libraries to get those features to React Native, but then you also need to use the same libraries when you want to render the components for Web.
  3. Removing className from RNW obviously means that no CSS pre/post-processor can be used with it, making it less flexible as you can not do things like scripting in Sass or use libraries like postcss-preset-env, etc.
kristerkari commented 5 years ago

Looks like this change also affects the use of styled components with RNW.

An example from my project:

const Container = styled(View)`
  flex: 1;
  justify-content: center;
  align-items: center;
  background-color: #fff;
`;

Warning: Using the "className" prop on <View> is deprecated.
kristerkari commented 5 years ago

so @necolas any chance of re-opening this issue and adding a setting to RNW that would remove the warnings and enable className?

I would be just fine with the className being disabled by default and not the recommended way to do styling with RNW, but that you could opt-in to use it when needed.

It seems to me that removing className support completely will just break a lot of libraries that could otherwise be compatible with RNW.

kations commented 5 years ago

@necolas would be great to have support for styled-components or a way to disable the warnings

necolas commented 5 years ago

Use styled-components/native. But you're better off using RN directly

kristerkari commented 5 years ago

Use styled-components/native. But you're better off using RN directly

Ah, for some reason I did not notice the /native in the import because it was rendering fine in React Native without the /native in the import.

btw. @necolas the styled-component implementation got me thinking about one idea. Do you think that it would be possible to create a plugin that would turn CSS from a file to style objects that run through the RNW Stylesheet.create and that way is rendered the same way as other styles? Kind of the same idea as with DSS and how it resolves the classes inside className etc.

That way the user would still have the option to use CSS files and there would be no need to expose className prop.

te-online commented 5 years ago

When I try to use styled-components/native it throws an error about react-native not being what it's expecting. I guess that this has to do with aliasing react-native with react-native-web?

./node_modules/styled-components/native/dist/styled-components.native.esm.js
Attempted import error: 'react-native' does not contain a default export (imported as 'reactNative').

Did anyone get this working properly?

karlsander commented 5 years ago

While I appreciate the safety and predictability that RN styles provide, and media queries are not really in the spirit of RNW, I have not found a way to reasonably and correctly server render my app without shipping some media query based styles outside of RNW. I was hoping to rely on @kristerkari's react-native-css-modules to do that.

I would be very interested in ideas for better ways we could work on to server render responsive apps with RNW, but I don't have anything.

kristerkari commented 5 years ago

While I appreciate the safety and predictability that RN styles provide, and media queries are not really in the spirit of RNW, I have not found a way to reasonably and correctly server render my app without shipping some media query based styles outside of RNW.

true, I don't think that CSS media queries can be supported if the className prop gets removed. The only option would be to do the media queries with Javascript.

As I said before, this change really removes a lot flexibility from RNW by restricting the styles to the StyleSheet API.

karlsander commented 5 years ago

Well there are hackier options like:

kristerkari commented 5 years ago

Well there are hackier options like:

Those are valid workarounds/hacks for the problem, but they will also mean that the user needs to add code for the workaround to his/her project.

My goal with the React Native CSS modules library is to give the user a simple way to style React Native apps with plain CSS or CSS processors. It really sucks if the user has to do some hacks to get that working on the Web.

In my opinion the only suitable ways to tackle the problem would be to either add opt-in setting for the className support OR create some kind of plugin that would transform CSS->StyleSheet + transform CSS media queries to something that RNW could support.

necolas commented 5 years ago

As I said before, this change really removes a lot flexibility from RNW by restricting the styles to the StyleSheet API.

No, it doesn't. You can apply styles using data-* attribute selectors. But as I mentioned, your project won't produce consistent styles between web and RN because you're just generating CSS modules code.

kristerkari commented 5 years ago

No, it doesn't. You can apply styles using data-* attribute selectors. But as I mentioned, your project won't produce consistent styles between web and RN because you're just generating CSS modules code.

As mentioned before, there are workarounds, but those workarounds require the user to write extra code that is Web specific. You can not use the same CSS styles for both React Native and Web unless you use the StyleSheet API.

mglavall commented 5 years ago

This is giving me a lot of trouble because we use StyledComponents and we reuse components on web and app. What would be the correct way to still be able to use these 2? I can not use styledComponents/native because we are using them on the web as well.

steida commented 5 years ago

@mglavall StyledComponents is an unnecessary abstraction from day one. It was created as slow and buggy wrapper over glamour. In JavaScript, we don't need to emulate CSS text-based syntax. Anyway, with React Native Web, it's pointless to use it over StyleSheet. The correct way is to not use StyledComponents. It's leaky abstraction (google it). For styling, there is style or emotions css prop. Misusing components and their props for styling is possible but wrong.

kations commented 5 years ago

@mglavall maybe have a look at https://github.com/callstack/react-theme-provider very easy to build something like styled components for react-native/web

mglavall commented 5 years ago

@mglavall StyledComponents is an unnecessary abstraction from day one. It was created as slow and buggy wrapper over glamour. In JavaScript, we don't need to emulate CSS text-based syntax. Anyway, with React Native Web, it's pointless to use it over StyleSheet. The correct way is to not use StyledComponents. It's leaky abstraction (google it). For styling, there is style or emotions css prop. Misusing components and their props for styling is possible but wrong.

I don't think that response is helping anyone in any way. ´Don't use it´does not help all the people that are already using it. Thanks for the explanation, though

steida commented 5 years ago

"Don't use it" or "You are doing it wrong" is often the best answer ever. Some things are unfixable. For example GraphQL SDL or the State (socialism) or fiat money. To illustrate that pattern, check https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3 Hope you will understand why I wrote that response.

calumjames commented 5 years ago

Styled Components removes the extra guff required if we'd like to build readable and clear markup.

Almost any component tends to be given a meaningful name, even if it's just a component that takes styles and doesn't do anything functionally. It removes the existence of <View style={timelineFooterStyles} /> everywhere and we instead tend to end up with the much clearer and more readable <TimelineFooter/>. This is much more readable when a few View components are nested or adjacent each other.

If we'd like to replicate this with the StyleSheet API, when defining our "styled" components, everyone in the team would have to remember to handle children and other props. Styled Components handles this for us, not only ensuring there's less scope for bugs but making sure our "styled" component definitions are simpler with less guff.

@steida, I'll google the leaky abstraction issue, but my concern is with the Styled Components paradigm being written off as not needed or not useful.

steida commented 5 years ago

I experimented with styled components API a lot in the past. The main problem is clashes among component props and style props which is unsolvable.

'' is not a bug, it's a feature. It's not verbose, it's explicit. Do you know utility / atomic CSS movement in CSS world? Anything slightly complex leads to style duplications. Start with this article https://frontstuff.io/in-defense-of-utility-first-css and read more about the topic. Sooner or later you will realize, that having style primitives and being able to arbitrary mix them via style prop array is far most better approach then plain old CSS untyped string snippet soup. Also, style objects can be typed and injected via context by theme etc. The result is much more robust and easier to use code. DX matters.

much clearer and more readable

You wanted to say "which I am more familiar with". Sure you can make a reusable component but repeating yourself is far less expensive than the wrong abstraction.

Imagine a simple Text component. It's not as simple, because text can and will have different sizes, fonts, centering. Are you going to replicate everything via props? Sure you can..., but again, read something about utility CSS.

I was using even things like <Box width= but it was not as flexible as <View style=[a, b, c

te-online commented 5 years ago

I did something like this, and it seems to work – maybe there are drawbacks, I don't know... 😅

// SharedComponent.ts
import styled from 'styled-components';
import { Theme } from '../styles/types';
import { Text } from 'react-native'; // I still use the `react-native-web` webpack alias for the web project, although it might not even be necessary

const webOrNativePrimitive = (defaultTag?: string, defaultPrimitive?: any) => {
  return (typeof navigator !== 'undefined' && navigator.product !== 'ReactNative') || process.env.NODE ? defaultTag || 'p' : defaultPrimitive || Text;
};

export const Typography = styled(webOrNativePrimitive())<{ type?: string; os?: string; theme: Theme }>`
  font-family: ${({ theme }: { theme: Theme }) => theme.font.defaultFont};
  ...
`;