khevamann / rn-responsive-styles

Responsive styles for react-native and react-native-web
MIT License
39 stars 3 forks source link

Reload cause style to change on desktop with maxSize #17

Closed JinbeiStudio closed 1 year ago

JinbeiStudio commented 1 year ago

Hello,

When reloading my page on the web, the page adopt the style specified in maxSize, instead of the default one.

On refresh it goes from row to column. No issue if I access the page with a link.

const useStyles = CreateResponsiveStyle(
    {
        scrollView: {
            alignItems: 'center',
            flexDirection: 'row',
            minHeight: '100%',
        },
    },
    {
        [maxSize(DEVICE_SIZES.SMALL_DEVICE)]: {
            scrollView: {
                flexDirection: 'column',
            },
        },
    }
);
khevamann commented 1 year ago

I am not seeing an issue when I try to reproduce, are you using the BreakpointProvider or no? And would you be able to provide a larger set of code to reproduce?

JinbeiStudio commented 1 year ago

I am not seeing an issue when I try to reproduce, are you using the BreakpointProvider or no? And would you be able to provide a larger set of code to reproduce?

Hey :)

I am not using the BreakpointProvider. It is only necessary when using custom breakpoints right ?

I have the issue both on my local dev environement, and on my prod environment. A simple refresh in chrome cause to have the lower breakpoints styles.

Techno wise, I have a monorepo with next js for the web and expo for mobile. Based on the solito monorepo : https://github.com/nandorojo/solito/tree/master/example-monorepos/blank

Here is an example of a page :

import { View, ScrollView } from 'react-native';
import { TextInput, Headline, HelperText, Surface, Button, Snackbar } from 'react-native-paper';
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { useUser } from 'app/functions/hooks/useUser';
import { useState, useEffect } from 'react';
import { useRouter } from 'solito/router';
import { CreateResponsiveStyle, DEVICE_SIZES, maxSize } from 'rn-responsive-styles';

export default function LoginForm() {
    const { login, loggedIn } = useUser();
    const [visibleSnackBar, setVisibleSnackBar] = useState(false);
    const { push } = useRouter();
    const styles = useStyles();

    const {
        control,
        handleSubmit,
        formState: { isSubmitting, errors },
        setError,
    } = useForm({
        defaultValues: {
            email: '',
            password: '',
            api: '',
        },
    });

    const onSubmit = ({ email, password }: SubmitTypes) => {
        return login(email, password).catch((error) => {
            setError('api', {
                message: error?.response?.data?.message,
            });
        });
    };

    const onDismissSnackBar = () => setVisibleSnackBar(false);

    useEffect(() => {
        if (loggedIn) {
            setVisibleSnackBar(true);
            setTimeout(() => {
                push('/');
            }, 2000);
        }
    }, [loggedIn, push]);

    return (
        <View style={styles.pageContainer}>
            <View style={styles.container}>
                <ScrollView
                    showsVerticalScrollIndicator={false}
                    showsHorizontalScrollIndicator={false}
                    contentContainerStyle={styles.scrollView}>
                    <Surface style={styles.surface}>
                        <Headline style={styles.title}>Se connecter</Headline>
                        <Controller
                            name="email"
                            control={control}
                            rules={{
                                required: { value: true, message: 'Ce champs est requis.' },
                            }}
                            render={({ field: { onChange, onBlur, value } }) => (
                                <TextInput
                                    left={<TextInput.Icon name="email" />}
                                    textContentType="emailAddress"
                                    mode={'outlined'}
                                    label="Email"
                                    autoCapitalize="none"
                                    onBlur={onBlur}
                                    onChangeText={onChange}
                                    value={value}
                                    style={styles.input}
                                    error={errors.email ? true : false}
                                />
                            )}
                        />
                        {errors.email && (
                            <HelperText type="error">{errors.email.message}</HelperText>
                        )}

                        <Controller
                            name="password"
                            control={control}
                            rules={{
                                required: { value: true, message: 'Ce champs est requis.' },
                            }}
                            render={({ field: { onChange, onBlur, value } }) => (
                                <TextInput
                                    left={<TextInput.Icon name="onepassword" />}
                                    secureTextEntry
                                    textContentType="password"
                                    mode={'outlined'}
                                    label="Mot de passe"
                                    onBlur={onBlur}
                                    onChangeText={onChange}
                                    value={value}
                                    style={styles.input}
                                    error={errors.password ? true : false}
                                />
                            )}
                        />
                        {errors.password && (
                            <HelperText type="error">{errors.password.message}</HelperText>
                        )}

                        <Button
                            style={styles.loginButton}
                            icon="account"
                            mode="contained"
                            loading={isSubmitting ? true : false}
                            onPress={handleSubmit(onSubmit)}>
                            Connexion
                        </Button>
                        {errors.api && (
                            <HelperText type="error">
                                Une erreur s'est produite lors de la connexion.
                            </HelperText>
                        )}
                    </Surface>
                    <Surface style={styles.surface}>
                        <Headline style={styles.title}>Pas encore inscrit ?</Headline>
                        <Button
                            icon="account-plus"
                            mode="contained"
                            onPress={() => push('/register')}>
                            Je m'inscris
                        </Button>
                    </Surface>
                </ScrollView>
            </View>
            <Snackbar
                wrapperStyle={styles.snackbarWrapper}
                visible={visibleSnackBar}
                onDismiss={onDismissSnackBar}>
                Vous êtes désormais inscrit !
            </Snackbar>
        </View>
    );
}

const useStyles = CreateResponsiveStyle(
    {
        pageContainer: {
            flex: 1,
        },
        surface: {
            padding: 50,
            minHeight: 400,
            minWidth: 400,
            margin: 16,
            justifyContent: 'center',
        },
        container: {
            alignItems: 'center',
            justifyContent: 'center',
            flex: 1,
        },
        title: {
            textAlign: 'center',
            padding: 16,
        },
        input: {
            marginTop: 16,
            minWidth: 300,
        },
        loginButton: {
            marginTop: 24,
        },
        scrollView: {
            alignItems: 'center',
            flexDirection: 'row',
            minHeight: '100%',
        },
        snackbarWrapper: {
            top: 0,
        },
    },
    {
        [maxSize(DEVICE_SIZES.SMALL_DEVICE)]: {
            input: {
                minWidth: 300,
            },
            surface: {
                padding: 20,
                width: 340,
                height: 300,
                minWidth: undefined,
                minHeight: 400,
            },
            scrollView: {
                flexDirection: 'column',
            },
        },
    }
);

type SubmitTypes = {
    email: string;
    password: string;
};
JinbeiStudio commented 1 year ago

I tried adding the breakpoint provider but the issue remain the same.

khevamann commented 1 year ago

@JinbeiStudio Sorry I forgot to reply, I tried reproducing this and still cannot get the behavior you are saying. Is there any way you could provide a full reproducible example? I have an app I am working on off of the Solito example and I tried pasting in that style you said and it behaves correctly, I tried refreshing and changing device sizes and couldn't reproduce

JinbeiStudio commented 1 year ago

@khevamann Here is a simple example to reproduce.

If I access the page with the navigation, surface display in rows, if I refresh the browser surface display in column. As if the size of the device detected was not the good one.

Thank you.

import { View } from 'react-native';
import React from 'react';
import { Surface, Text } from 'react-native-paper';
import { CreateResponsiveStyle, DEVICE_SIZES, maxSize } from 'rn-responsive-styles';

export default function Test() {
    const styles = useStyles();

    return (
        <View style={styles.view}>
            <Surface style={styles.surface}>
                <Text>Test1</Text>
            </Surface>
            <Surface style={styles.surface}>
                <Text>Test2</Text>
            </Surface>
        </View>
    );
}

const useStyles = CreateResponsiveStyle(
    {
        surface: {
            padding: 50,
            minHeight: 400,
            minWidth: 400,
            margin: 16,
            justifyContent: 'center',
        },
        view: {
            alignItems: 'center',
            justifyContent: 'center',
            flexDirection: 'row',
            minHeight: '100%',
        },
    },
    {
        [maxSize(DEVICE_SIZES.SMALL_DEVICE)]: {
            view: {
                flexDirection: 'column',
            },
        },
    }
);
khevamann commented 1 year ago

@JinbeiStudio I also tried with the surfaces and this example does not break. https://snack.expo.dev/@khevamann/rn-responsive-styles---issue-17-repro if you can provide an example that is broken I am happy to look at it, but I am unable to reproduce with the snippet you sent me or in my own solito app.

JinbeiStudio commented 1 year ago

@JinbeiStudio I also tried with the surfaces and this example does not break. https://snack.expo.dev/@khevamann/rn-responsive-styles---issue-17-repro if you can provide an example that is broken I am happy to look at it, but I am unable to reproduce with the snippet you sent me or in my own solito app.

I managed to find where the issue lies. I am using nextJS and there is a mismatch between the style generated server side and client side.

It appears to be because the window dimensions can't be retrieved by nextJS on the server side : Dimensions.get('window'))

Here is a github discussion regarding this subject. nandorojo created the Dripsy library that implements a responsive style approach as well. https://github.com/vercel/next.js/discussions/14469

JinbeiStudio commented 1 year ago

I managed to find a solution by checking if window var is set or not, with a default width if not. This deal with framework having server side rendering like NextJs.

export default function useDeviceSize() {
    const breakpoints = useBreakpoints();
    const [isServer, setIsServer] = useState(true);

    useEffect(() => {
        if (typeof window !== 'undefined') {
            setIsServer(false);
        }
    }, [isServer]);

    const [dims, setDims] = useState(() =>
        !isServer ? Dimensions.get('window') : { width: 1200 }
    );
arturmoroz commented 1 year ago

Hello! I have same problem. When page loads on desktop I see responsive styles [maxSize(DEVICE_SIZES.SM)] but when I resize window to mobile and return back the styles becomes fine. It works in wrong way after page loads on Desktop. I use RNW with Next.js. P.S. Thanks for great job!

arturmoroz commented 1 year ago

I've done preview https://stackblitz.com/edit/next-js-rn-responsive-styles?file=pages%2Findex.js You can press "Open in New Tab" and try it. On load on wide screen we have color: red but we need to have color: blue. But after resize from desktop to mobile our color becomes fine.

khevamann commented 1 year ago

@arturmoroz Thank you for this! I will take a look at it this weekend and get a fix out!

frostwowa commented 1 year ago

@khevamann I have the same problem, help me plz

khevamann commented 1 year ago

I will have a PR ready for this by tonight. I was trying to find a good solution to this for a bit, but it looks like the recommended approach is to delay rendering until it has reached the client. So with Next and SSR enabled, I am adding a new provider that can be used to delay rendering until it is off the server.

khevamann commented 1 year ago

Sorry for taking awhile to get to this, I have added instructions to the README. Let me know if there are any issues or questions