I wanted to ask the community about the changes in StyleSheet.create in React Native.
Before:
I have reviewed the past questions about this topic, such as this question, but they all have been answered pretty a long time ago (apart from this answer, but I wanted to have something definite) and a lot has changed since.
Before StyleSheet was creating a unique id for the styles, mainly for performance optimisations. If you wanted to get the styles out of the created styles object, you should have used the flatten method. The majority of the answers reference this flatten method and you could not access styles property as if it was a normal object.
E.g.
const styles = StyleSheet.create({
modalContainer: {
width: 100,
backgroundColor: 'white',
padding: 5,
},
You could not access the padding styles like styles.modalContainer.padding;
Currently:
However, the behaviour of this has changed. This is the source code of StyleSheet from the React Native team. Just copying the create method:
create<+S: ____Styles_Internal>(obj: S): $ObjMap<S, (Object) => any> {
// TODO: This should return S as the return type. But first,
// we need to codemod all the callsites that are typing this
// return value as a number (even though it was opaque).
if (__DEV__) {
for (const key in obj) {
StyleSheetValidation.validateStyle(key, obj);
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
},
};
Which is just returning the object passed to create without doing anything to it. So you can actually access the styles as styles.modalContainer.padding;
Maybe I don't understand clearly the TODO, but this method has been coded this way at least since RN 0.57 and I don't know whether they are going to change it back.
My question:
Is there any sense in using StyleSheet.create anymore?
Thanks in advance for sharing your opinions!
Stylesheet is generally used to create a global style in react native and add it to the respective views which requires to style the objects.
Some widgets like TextInput, Text, Button cannot apply almost all the css styles in the react native.
So, in those cases what you can do is you can wrap those widgets with and then can create global StyleSheets using StyleSheet.create() method to globally use and reduce your headache.
So the conclusion for your question can be summarized as the Stylesheet.create() can be helpful to improve the performance while styling your multiple views using the same style will create a new object every time for each one.
While Stylesheet.create() will act as a single global object for all the views which are using it to style themselves resulting performance/memory optimisation.
I've never heard of this flatten() being necessary like you described. In fact, in the React Native repo in the very first commit, there was an example provided:
Examples/Movies/MovieCell.js:
https://github.com/facebook/react-native/commit/a15603d8f1ecdd673d80be318293cee53eb4475d#diff-4712aeb2165b3c0ce812bef903be3464
In this example, you can see var styles = StyleSheet.create({..}); being used in its present flavor and at that moment in 2016 you can see styles being referenced in the components as styles.styleName.
Additionally in the StyleSheet class here is create from the initial commit:
class StyleSheet {
static create(obj) {
var result = {};
for (var key in obj) {
StyleSheet.validateStyle(key, obj);
result[key] = StyleSheetRegistry.registerStyle(obj[key]);
}
return result;
}
// ...
}
As you see, no call to flatten on the initial commit, neither inside the create method, nor from the user using create.
In summary it seems this never changed and you could always access the styles using the dot operator.
As for whether to use it I don't think you have a choice. It clearly has some sort of validation code inside of it, it's also using type checking and the react team recommends using it. I don't see any other methods that do what it does. How could you use the class without create, just using some sort of init or constructor method? I don't see one on the class. There is no StyleSheet({...}); To get obj returned you need to call create.
Your editor could not give you IntelliSense if you strip away the validation behavior and make it a plain object. You won't know when you're making typos or referencing styles that don't exist, you won't have autocompletion. You'd need to create your own interfaces and use TypeScript. Thus you should use create because otherwise at a minimum you're breaking your IDE.
Related
I use React Native and Expo to develop an application on mobile and tablet.
The objective is to be able to answer a questionnaire where several questions are divided into various categories. A question may have a category or not, so they don't really follow each other in the code.
I use a loop to display my categories, with its questions inside. Questions are components, categories too.
When I answer a question, sometimes I want to reload another question specifically (or several). After answering my question, I have a list of questions I want to reload.
I would like either to launch a function in the component, or to reload it entirely.
How do I reload the question component, which may be quite far from the one I acted on?
Thanks for your help !
I wanted to listen with UseEffect for the modification of my global variable, but it doesn't work. Also, I can't fully reload my page either. I would like to specifically reload the question component or run the function.
What I have already tried:
Reloading the page entirely from my screen with a function in global, didn't work (
I have a lot of elements so I would like to avoid reloading everything)
Listen with useEffect the change of my list of questions to modify to check if my id was in it, but the listening is not done
I can't send functions or other elements in the props of my question because they can be in different components (category or other) so too far away.
Question Component (simplified) :
export default function RealisationQuestionComponent(props){
//When action in Question A
function changeCheckA(value) {
[...]
if (props.question.questions_impactees !== undefined) {
global.questions_impactees = props.question.questions_impactees;
}
}
//Effect in Question B
useEffect(() => {
[my function or reload entire component]
}, [global.questions_impactees]);
return (
<>...</>
)
EDIT :
Following the answer, I tried to use useContext to retrieve my information, but it tells me Can't find variable : RealContext
What i would like to do :
https://www.w3schools.com/react/showreact.asp?filename=demo2_react_context2
What I have done :
My screen AuditRealisationScreen
const RealContext = createContext();
const [listIdVictimes, setListIdVictimes] = useState('please test');
[...]
return (
<RealContext.Provider value={listIdVictimes}>
<RealisationChapitreComponent/>
</RealContext.Provider>
)
RealisationChapitreComponent > [...] > RealisationQuestionComponent
In my component RealisationQuestionComponent
const listTest = useContext(RealContext);
[...]
return (
<Text> {listTest} </Text>
)
Can it work this way?
Use [contexts][1]. Using [reducers][2] with context would be more flexible and organised.
If you have a lot of states to manage with mess of siblings and parent-child and child-parent relations then using [Redux][3] would give you more control to manage those components and its states
What I used do for
How to reload a component or call a function after action in another
component with React Native
const context = ... // declare
and then
// import context
const App=()=>{
...
return(
<Context.Provider value={your functions, data, states, props whatever you want}>
{{ ...child components }}
<Context.Provider/>
)
}
You can play with these contexts usage to achieve your goal
[1]: https://reactjs.org/docs/hooks-reference.html#usecontext
[2]: https://reactjs.org/docs/hooks-reference.html#usereducer
[3]: https://redux.js.org/introduction/why-rtk-is-redux-today#what-is-redux
I've started using RN recently and have been doing some research on the implementation of themes and/or a dark-light mode. Here's roughly how I understand the two main options so far:
Context: Easy setup and can be accessed via hook inside the component that needs it. I count things like the React Navigation themes since that works, in essence, the same way(?)
Styled Components: Essentially just replacing the components with custom ones that access the current theme and that can then be set up to change their props on toggle if needed.
What I don't like about context, is that (the way I understand it) it wouldn't let me access the theme when using a StyleSheet, since that's outside the component. With the styled components on the other hands I'm not sure if I can cover all options inside my custom components, wouldn't I still need some sort of hook in each component anyway?
I was also thinking about just saving the current theme into my Store (in my case Zustand), together with an action that lets me toggle to a dark-mode. But so far, I haven't really seen anyone else do that, is there a downside to doing it that way?
It's not hard to pass context to your stylesheet, it just requires a bit of extra boilerplate. Something like the below:
import ThemeContext from '<path>';
export default () => {
const theme = useContext(ThemeContext);
const stylesWithTheme = styles(theme);
return <Text style={stylesWithTheme.text>Hi</Text>;
}
const styles = theme => StyleSheet.create({
text: {
color: themeStyles.color[theme];
}
});
const themeStyles = {
color: {
dark: '#FFF',
light: '#000',
},
};
I don't think using styled components would be a big departure from the above scheme.
If you did store your theme in state, and define your styles within the body of the component, that could be a savings in boilerplate. It's up to you whether having your styles in the component body is acceptable.
Side note, if you're already using a state manager, I would recommend not mixing it with the Context API without knowing more. It's simpler to have one solution there.
I have gone through the composition api docs for vee-validate, and I can definitely get my validation working on my forms if I follow the pattern described in their docs.
I don't feel comfortable however doing it as documented there, as I just feel I'm writing too much code for each form, and I do not want to repeat myself.
So I've been experimeting a bit with how I can optimise this, and this is what we currently came up with, but I'm a bit stuck now.
You can see the code example on https://codesandbox.io/s/restless-star-1qwgb, but I'll walk you through.
Consider we want to create a form for creating a new Invoice.
An invoice is typically composed of the Invoice model with a number of InvoiceElements attached to it. (the invoice lines)
In our vueJs codebase, we have javascript classes representing each model that we need to work with, so you'll find a class Invoice and a class InvoiceElement, both extending BaseModel which already providers some basic functionality.
On each mode, we have defined a static method returning a yup validation schema, e.g.:
import * as yup from "yup";
import BaseModel from "./BaseModel";
import InvoiceElement from "./InvoiceElement";
export default class Invoice extends BaseModel {
static get validationSchema() {
return yup.object().shape({
due_date: yup.date().min(new Date()).default("2021-09-30"),
reference: yup.string(),
elements: yup
.array()
.of(InvoiceElement.validationSchema)
.default([InvoiceElement.validationSchema.getDefault()])
});
}
}
As you can see, we also define default values for each of the schema fields.
This allows us to do the following in our form component:
setup() {
let { handleSubmit, errors, values } = useForm({
validationSchema: Invoice.validationSchema,
initialValues: Invoice.validationSchema.getDefault(),
validateOnMount: false,
});
let addLine = () => {
values.elements.push(InvoiceElement.validationSchema.getDefault());
};
let submitForm = handleSubmit((values) => {
alert("form was valid and we submit data here");
});
return {
values,
errors,
addLine,
submitForm,
};
}
Okay - I'm very happy with this as we now have:
values which is reactive and I can just bind to my inputs using ```
errors which has my errors
When I submit the form, it correctly triggers vee-validate's handleSubmit() method and my errors get correctly updated.
My main issue with this approach now is how to trigger the validation of the fields when they get updated. The main goal is to avoid having to write too much code using the useField() composable.
I know I'm not following the proposed pattern, but it just kept feeling as if we were writing too much code, and we seem quite close to a good pattern, but I just don't get the last bits...
Maybe someone on here does though :-)
Generally, the docs are only meant to show you "how it works", but I do agree that useField is very verbose if used incorrectly. Using it for models isn't the intended use-case.
For example:
// Very verbose and not the intended use-case
const { value: email } = useField(...);
const { value: password } = useField(...);
The main purpose of useField is to help create input components that can be validated, that if you are willing to couple your form components with vee-validate which I find reasonable in 80% of situations.
To try to answer your issue, since you want to avoid using useField you could actually make use of the other useXXX functions called composition helpers. Like useValidateField which creates a function for you that can validate any field given it was created in a child of a useForm component.
const validate = useValidateField('fieldName');
validate(); // triggers validation on the field
I'm using office-ui-fabric-react library. Current version that I'm on is 7.17.0.
My list is super simple:
<DetailsList
columns={this.state.columns}
items={this.state.items}
compact={true}
selectionMode={SelectionMode.none}
enableUpdateAnimations={true}
/>
When I change the items in a separate method (marked async) and do this.setState({items: someNewItems}), the list doesn't get re-rendered. I'm curious is this a known issue, or is the DetailsList needs to be handled in a special way?
To clarify, after executing this.setState nothing changes, but if I resize the page, the new elements appear suddenly.
This is due to shallow comparison, in fact the solution is listed in the documentation here: https://developer.microsoft.com/en-us/fabric#/controls/web/detailslist
From the docs:
My List is not re-rendering when I mutate its items! What should I do?
To determine if the List within DetailsList should re-render its
contents, the component performs a referential equality check within
its shouldComponentUpdate method. This is done to minimize the
performance overhead associating with re-rendering the virtualized
List pages, as recommended by the React documentation.
As a result of this implementation, the inner List will not determine
it should re-render if the array values are mutated. To avoid this
problem, we recommend re-creating the items array backing the
DetailsList by using a method such as Array.prototype.concat or
ES6 spread syntax shown below:
public appendItems(): void {
const { items } = this.state;
this.setState({
items: [...items, ...['Foo', 'Bar']]
})
}
public render(): JSX.Element {
const { items } = this.state;
return <DetailsList items={items} />;
}
By re-creating the items array without mutating the values, the inner
List will correctly determine its contents have changed and that it
should re-render the new values.
There is no issue with DetailsList. The table should update properly, as you indicated that when you resize the page the items show up but before that it doesn't. It's definitely because the render function isn't being called again.
Which means that when you do this.setState you are not updating the states of your present component. As Mirage, in the comments suggested try checking if you have the correct this and you are updating the correct component.
What exactly is the benefit of using StyleSheet.create() vs a plain object?
const styles = StyleSheet.create({
container: {
flex: 1
}
}
Vs.
const styles = {
container: {
flex: 1
}
}
There is no benefit. Period.
Myth 1: StyleSheet is more performant
There is absolutely no performance difference between StyleSheet and an object declared outside of render (it would be different if you're creating a new object inside render every time). The performance difference is a myth.
The origin of the myth is likely because React Native team tried to do this, but they weren't successful. Nowhere in the official docs you will find anything about performance: https://facebook.github.io/react-native/docs/stylesheet.html, while source code states "not implemented yet": https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/StyleSheet.js#L207
Myth 2: StyleSheet validates style object at compile time
This is not true. Plain JavaScript can't validate objects at compile time.
Two things:
It does validate at runtime, but so does when you pass the style object to a component. No difference.
It does validate at compile time if you're using Flow or TypeScript, but so does once you pass the object as a style prop to a component, or if you properly typehint object like below. No difference either.
const containerStyle: ViewStyle = {
...
}
Quoting directly from comment section of StyleSheet.js of React native
Code quality:
By moving styles away from the render function, you're making the code easier to understand.
Naming the styles is a good way to add meaning to the low level components in the render function.
Performance:
Making a stylesheet from a style object makes it possible to refer to it by ID instead of creating a new style object every time.
It also allows to send the style only once through the bridge. All subsequent uses are going to refer an id (not implemented yet).
Also StyleSheet validates your stylesheet content as well. So any error of incorrect style property is shown at time of compiling rather than at runtime when StyleSheet is actually implemented.
The accepted answer is not an answer to the OP question.
The question is not the difference between inline styles and a const outside the class, but why we should use StyleSheet.create instead of a plain object.
After a bit of researching what I found is the following (please update if you have any info).
The advatanges of StyleSheet.create should be the following:
It validates the styles
Better perfomances because it creates a mapping of the styles to an ID, and then it refers inside with this ID, instead of creating every time a new object. So even the process of updating devices is faster because you don't send everytime all the new objects.
It used to be considered that using a StyleSheet was more performant, and was recommended for this reason by the RN team up until version 0.57, but it is now no longer recommended as correctly pointed out in another answer to this question.
The RN documentation now recommends StyleSheet for the following reasons, though I think these reasons would apply equally to plain objects that are created outside of the render function:
By moving styles away from the render function, you're making the
code easier to understand.
Naming the styles is a good way to add meaning to the low level
components in the render function.
So what do I think are the possible benefits of using StyleSheet over plain objects?
1) Despite claims to the contrary my testing on RN v0.59.10 indicates that you do get some validation when calling StyleSheet.create() and typescript (and probably flow) will also report errors at compile time. Even without compile time checking I think it's still beneficial to do run time validation of styles before they are used for rendering, particularly where components that use those styles could be conditionally rendered. This will allow such errors to be picked up without having to test all rendering scenarios.
2) Given that StyleSheet is recommended by the RN team they may still have hopes of using StyleSheet to improve performance in future, and they may have other possible improvements in mind as well, for example:
3) The current StyleSheet.create() run-time validation is useful, but a bit limited. It seems to be restricted to the type checking that you would get with flow or typescript, so will pick up say flex: "1" or borderStyle: "rubbish", but not width: "rubbish" as that could be a percentage string. It's possible that the RN team may improve such validation in future by checking things like percentage strings, or range limits, or you could wrap StyleSheet.create() in your own function to do that more extensive validation.
4) By using StyleSheet you are perhaps making it easier to transition to third party alternatives/extensions like react-native-extended-stylesheet that offer more.
So, today, September of 2021, after reading all the answers and doing some researches, I created a summary about using Stylesheet instead of a plain object.
Based on React Documentation, you should use the stylesheet when the complexity starts to grow.
The style prop can be a plain old JavaScript object. That's what we usually use for example code.
As a component grows in complexity, it is often cleaner to use StyleSheet.create to define several styles in one place.
In the simulator, when using stylesheet will display an ERROR, and when using the plain object will display only a WARNING.
Based on item 2, it looks like it has some validation while compiling. (A lot of people say that’s a myth)
If you need to migrate for a third-party library in the future, for some of them like react-native-extended-stylesheet, if you are using stylesheet, it will be easier.
You have some methods and properties that boost the development. For example, the property StyleSheet.absoluteFill will do position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, or the method compose() will allow you to combine two styles, overriding it.
P.S.: The performance answer looks to be a myth.
My opinion?
Based on item 2 and 5, go to stylesheet instead of plain objects.
I did not find any differences between StyleSheet and plain object, except of typing validation in TypeScript.
For example, this (note the typing differences):
import { View, Text, Image, StyleSheet } from 'react-native';
import logo from './logo.svg';
export default class App extends Component {
render() {
return (
<View style={styles.someViewStyle}>
<Text style={styles.someTextStyle}>Text Here</Text>
<Image style={styles.someImageStyle} source={logo} />
</View>
);
}
}
const styles: StyleSheet.create({
someViewStyle: {
backgroundColor: '#FFF',
padding: 10,
},
someTextStyle: {
fontSize: 24,
fontWeight: '600',
},
someImageStyle: {
height: 50,
width: 100,
},
});
equals to this:
import { View, Text, Image, ViewStyle, TextStyle, ImageStyle } from 'react-native';
import logo from './logo.svg';
export default class App extends Component {
render() {
return (
<View style={styles.someViewStyle}>
<Text style={styles.someTextStyle}>Text Here</Text>
<Image style={styles.someImageStyle} source={logo} />
</View>
);
}
}
const styles: {
someViewStyle: ViewStyle;
someTextStyle: TextStyle;
someImageStyle: ImageStyle;
} = {
someViewStyle: {
backgroundColor: '#FFF',
padding: 10,
},
someTextStyle: {
fontSize: 24,
fontWeight: '600',
},
someImageStyle: {
height: 50,
width: 100,
},
};
Creating your styles via StyleSheet.create will pass though validation only when global variable __DEV__ is set to true (or while running inside Android or IOS emulators see React Native DEV and PROD variables)
The function source code is pretty simple:
create < +S: ____Styles_Internal > (obj: S): $ReadOnly < S > {
// TODO: This should return S as the return type. But first,
// we need to codemod all the callsites that are typing this
// return value as a number (even though it was opaque).
if (__DEV__) {
for (const key in obj) {
StyleSheetValidation.validateStyle(key, obj);
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
}
I would recommend using it because it performs run-time validation during development, also it freezes the object.
i know that this is a really late answer, but I've read that it shows you errors and provides auto completion in editors when you use StyleSheet.