How to change styles in React Native - reactjs

I have a file (non-React component, it is just a JS file) with constants with colors in the project and in the same file a variable (nightTheme) and a function (nightThemeSwitch).
export let nightTheme = false;
export const nightThemeSwitch = () => {
nightTheme = !nightTheme;
};
export const WHITE = nightTheme ? '# 315e7e' : '#ffffff';
export const NAVY_BLUE = nightTheme ? '# 4d80a5' : '# 000099';
export const ABOUT_TEXT = nightTheme ? '# A4B7CB' : '# 27587B';
etc.......
In the main index.js file React Native, I import the function (nightThemeSwitch). I call it through the React Context and change the state, but the styles don't change, only the transparency changes (transparency I think so).
The localStorage does not help me, because the first render React does not see it. ReloadBundle doesn't help me. ForceUpdate doesn't help me either.
Is there a problem with the React.memo? Perhaps you have an idea how to solve this problem? Please tell me. Thanks in advance!
Code:
Main React Component index.js
this.state.nightThemeFunction = () => {
nightThemeSwitch();
this.setState(() => ({
nightThemeContext: nightTheme,
}));
// localStorage.setItem('nightTheme', JSON.stringify(!nightTheme));
// this.forceUpdate();
// reloadBundle();
};
React Component Settings.js
<Switch
onValueChange={() => {
this.context.nightThemeFunction();
}}
value={nightTheme}
trackColor={{ true: ORANGE }}
thumbColor={COMMON_WHITE}
/>
I have attached a photo. You can see that at the first render, the localStorage is not visible, so it does not work.

I think you need to learn more about React, your code seems weird.
You can do it that way
index.js
componenDidMount(){
AsyncStorage.getItem('theme').then((theme) => this.setState({theme}))
}
switchTheme(){
const theme = this.state.theme === 'light' ? 'dark' : 'light'
this.setState({theme})
AsyncStorage.setItem('theme', theme)
}
render(){
return(
<Settings switchTheme={() => this.switchTheme()} theme={this.state.theme} />
)
}
Settings.js
<Switch
onValueChange={() => this.props.switchTheme()}
value={this.props.theme}
trackColor={{ this.props.theme === dark ? 'black' : 'white'}}
/>

Related

How to handle deeplinks in react

I am trying to implement deeplinks in my app using firestore's dynamic links. When the app is opened using a deeplink, I want to show a modal to the user. I am new to react and I am not sure how to implement this. When the app is opened using the dynamic link, I can get the link in App.js
useEffect(() => {
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
return () => unsubscribe();
}, []);
const handleDynamicLink = link => {
//Show Modal
};
return (
<RootStoreProvider value={rootStore}>
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<IconRegistry icons={EvaIconsPack} />
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<ApplicationProvider {...eva} theme={eva[theme]}>
{!rootStore.authStore.isLoggedIn && !startedPressed ? <WelcomeSliderScreen pressed={getStartedPressed}></WelcomeSliderScreen> :
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
}
</ApplicationProvider>
</ThemeContext.Provider>
</SafeAreaProvider>
</RootStoreProvider>
)
}
What is the best way to implement this logic? I started to implement a deepLinkStore but now a start the think that this is not the best solution
const handleDynamicLink = link => {
rootStore && rootStore.linkStore.setDeepLink(link);
};
The problem is that rootStore is sometimes null there, I don't understand why.
Shall I just provide a parameter in RootNavigator? Something like this
const [deepLink, setDeepLink] = React.useState(null)
const handleDynamicLink = (link) => {
setDeepLink(link);
}
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
deepLink={deepLink}
/>
Is this the way to go? How can I access the deepLink parameter in a functional component?

Inject Props to React Component

For security reasons, I have to update ant design in my codebase from version 3 to 4.
Previously, this is how I use the icon:
import { Icon } from 'antd';
const Demo = () => (
<div>
<Icon type="smile" />
</div>
);
Since my codebase is relatively big and every single page use Icon, I made a global function getIcon(type) that returns <Icon type={type}>, and I just have to call it whenever I need an Icon.
But starting from antd 4, we have to import Icon we want to use like this:
import { SmileOutlined } from '#ant-design/icons';
const Demo = () => (
<div>
<SmileOutlined />
</div>
);
And yes! Now my getIcon() is not working, I can't pass the type parameter directly.
I tried to import every icon I need and put them inside an object, and call them when I need them. Here's the code:
import {
QuestionCircleTwoTone,
DeleteOutlined,
EditTwoTone
} from '#ant-design/icons';
let icons = {
'notFound': <QuestionCircleTwoTone/>,
'edit': <EditTwoTone/>,
'delete': <DeleteOutlined/>,
}
export const getIcon = (
someParam: any
) => {
let icon = icons[type] !== undefined ? icons[type] : icons['notFound'];
return (
icon
);
};
My problem is: I want to put someParam to the Icon Component, how can I do that?
Or, is there any proper solution to solve my problem?
Thanks~
You can pass props as follows in the icons Object:
let icons = {
'notFound':(props:any)=> <QuestionCircleTwoTone {...props}/>,
'edit': (props:any)=><EditTwoTone {...props}/>,
'delete':(props:any)=> <DeleteOutlined {...props}/>,
}
And then if you will pass any prop to the Icon component then it will pass the prop to the specific icon component
let Icon = icons[type] !== undefined ? icons[type] : icons['notFound'];
return (<Icon someParam={'c'}/>)

Testing icon based on dynamically imported svg in react-testing-library

It's my first question here and I've been coding for only a year so please be patient. I looked for similar problems on the website but couldn't find anything that worked for me.
I created an Icon component where I dynamically import the requested SVG.
I first used this solution which was working well but when I tried to test this component with react-testing library and snapshot I realised that the snapshot was always the span that is returned when nothing is imported. I first thought it was linked to the use of useRef() because I saw people saying refs didn't work with Jest so I changed my Icon component to be this:
const Icon: FC<IconProps> = ({ type, onClick, tooltip, className }: IconProps) => {
const [IconProps, setIconProps] = useState({
className: className || 'icon'
});
const tooltipDelay: [number, number] = [800, 0];
useEffect(() => {
setIconProps({ ...IconProps, className: className || 'icon' });
}, [className]);
const SVG = require(`../../svg/${type}.svg`).default;
const spanClassName = "svg-icon-wrapper";
if (typeof SVG !== 'undefined') {
if (tooltip) {
return (
(<Tooltip title={tooltip.title} delay={tooltip.delay ? tooltipDelay : 0}>
<i className={spanClassName}>
<SVG {...IconProps} onClick={onClick} data-testid="icon" />
</i>
</Tooltip>)
);
}
return (
<SVG {...IconProps} onClick={onClick} data-testid="icon" />
);
}
return <span className={spanClassName} data-testid="span" />;
};
export default Icon;
Here is my test
it('matches snapshot of each icon', async (done) => {
jest.useFakeTimers();
const type = 'check';
const Component = <Icon type={type} />;
const renderedComp = render(Component);
setTimeout(() => {
const { getByTestId } = renderedComp;
expect(getByTestId('icon')).toMatchSnapshot();
done();
}, 3000);
jest.runAllTimers();
});
I added timeout because I thought it might be link to the time it takes to import the SVG but nothing I tried worked.
So the main problem is:
how can I have the svg being imported for this component to return an icon (with data-testid='icon') and not a span (with data-testid='span')
Help would be much appreciated. My app works perfectly and I'm stuck with this testing for a while now.

nextjs react recoil persist values in local storage: initial page load in wrong state

I have the following code,
const Layout: React.FC<LayoutProps> = ({ children }) => {
const darkMode = useRecoilValue(darkModeAtom)
console.log('darkMode: ', darkMode)
return (
<div className={`max-w-6xl mx-auto my-2 ${darkMode ? 'dark' : ''}`}>
<Nav />
{children}
<style jsx global>{`
body {
background-color: ${darkMode ? '#12232e' : '#eefbfb'};
}
`}</style>
</div>
)
}
I am using recoil with recoil-persist.
So, when the darkMode value is true, the className should include a dark class, right? but it doesn't. I don't know what's wrong here. But it just doesn't work when I refresh for the first time, after that it works fine. I also tried with darkMode === true condition and it still doesn't work. You see the styled jsx, that works fine. That changes with the darkMode value and when I refresh it persists the data. But when I inspect I don't see the dark class in the first div. Also, when I console.log the darkMode value, I see true, but the dark class is not included.
Here's the sandbox link
Maybe it's a silly mistake, But I wasted a lot of time on this. So what am I doing wrong here?
The problem is that during SSR (server side rendering) there is no localStorage/Storage object available. So the resulted html coming from the server always has darkMode set to false. That's why you can see in cosole mismatched markup errors on hydration step.
I'd assume using some state that will always be false on the initial render (during hydration step) to match SSR'ed html but later will use actual darkMode value. Something like:
// themeStates.ts
import * as React from "react";
import { atom, useRecoilState } from "recoil";
import { recoilPersist } from "recoil-persist";
const { persistAtom } = recoilPersist();
export const darkModeAtom = atom<boolean>({
key: "darkMode",
default: false,
effects_UNSTABLE: [persistAtom]
});
export function useDarkMode() {
const [isInitial, setIsInitial] = React.useState(true);
const [darkModeStored, setDarkModeStored] = useRecoilState(darkModeAtom);
React.useEffect(() => {
setIsInitial(false);
}, []);
return [
isInitial === true ? false : darkModeStored,
setDarkModeStored
] as const;
}
And inside components use it like that:
// Layout.tsx
const [darkMode] = useDarkMode();
// Nav.tsx
const [darkMode, setDarkMode] = useDarkMode();
codesandbox link
Extending on #aleksxor solution, you can perform the useEffect once as follows.
First create an atom to handle the SSR completed state and a convenience function to set it.
import { atom, useSetRecoilState } from "recoil"
const ssrCompletedState = atom({
key: "SsrCompleted",
default: false,
})
export const useSsrComplectedState = () => {
const setSsrCompleted = useSetRecoilState(ssrCompletedState)
return () => setSsrCompleted(true)
}
Then in your code add the hook. Make sure it's an inner component to the Recoil provider.
const setSsrCompleted = useSsrComplectedState()
useEffect(setSsrCompleted, [setSsrCompleted])
Now create an atom effect to replace the recoil-persist persistAtom.
import { AtomEffect } from "recoil"
import { recoilPersist } from "recoil-persist"
const { persistAtom } = recoilPersist()
export const persistAtomEffect = <T>(param: Parameters<AtomEffect<T>>[0]) => {
param.getPromise(ssrCompletedState).then(() => persistAtom(param))
}
Now use this new function in your atom.
export const darkModeAtom = atom({
key: "darkMode",
default: false,
effects_UNSTABLE: [persistAtomEffect]
})

React Hook does not work properly on the first render in gatsby production mode

I have the following Problem:
I have a gatsby website that uses emotion for css in js. I use emotion theming to implement a dark mode. The dark mode works as expected when I run gatsby develop, but does not work if I run it with gatsby build && gatsby serve. More specifically the dark mode works only after switching to light and back again.
I have to following top level component which handles the Theme:
const Layout = ({ children }) => {
const [isDark, setIsDark] = useState(() => getInitialIsDark())
useEffect(() => {
if (typeof window !== "undefined") {
console.log("save is dark " + isDark)
window.localStorage.setItem("theming:isDark", isDark.toString())
}
}, [isDark])
return (
<ThemeProvider theme={isDark ? themeDark : themeLight}>
<ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>
</ThemeProvider>
)
}
The getInitalIsDark function checks a localStorage value, the OS color scheme, and defaults to false. If I run the application, and activate the dark mode the localStorage value is set. If i do now reload the Application the getInitialIsDark method returns true, but the UI Renders the light Theme. Switching back and forth between light and dark works as expected, just the initial load does not work.
If I replace the getInitialIsDark with true loading the darkMode works as expected, but the lightMode is broken. The only way I got this to work is to automatically rerender after loading on time using the following code.
const Layout = ({ children }) => {
const [isDark, setIsDark] = useState(false)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
if (typeof window !== "undefined" && isReady) {
console.log("save is dark " + isDark)
window.localStorage.setItem("theming:isDark", isDark.toString())
}
}, [isDark, isReady])
useEffect(() => setIsReady(true), [])
useEffect(() => {
const useDark = getInitialIsDark()
console.log("init is dark " + useDark)
setIsDark(useDark)
}, [])
return (
<ThemeProvider theme={isDark ? themeDark : themeLight}>
{isReady ? (<ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>) : <div/>}
</ThemeProvider>
)
}
But this causes an ugly flicker on page load.
What am I doing wrong with the hook in the first approach, that the initial value is not working as I expect.
Did you try to set your initial state like this?
const [isDark, setIsDark] = useState(getInitialIsDark())
Notice that I am not wrapping getInitialIsDark() in an additional function:
useState(() => getInitialIsDark())
You will probably crash your build because localStorage is not defined at buildtime. You might need to check if that exists inside getInitialIsDark.
Hope this helps!
#PedroFilipe is correct, useState(() => getInitialIsDark()) is not the way to invoke the checking function on start-up. The expression () => getInitialIsDark() is truthy, so depending on how <ThemedLayout isDark={isDark}> uses the prop it might work by accident, but useState will not evaluate the fuction passed in (as far as I know).
When using an initial value const [myValue, setMyValue] = useState(someInitialValue) the value seen in myValue can be laggy. I'm not sure why, but it seems to be a common cause of problems with hooks.
If the component always renders multiple times (e.g something else is async) the problem does not appear because in the second render the variable will have the expected value.
To be sure you check localstorage on startup, you need an additional useEffect() which explicitly calls your function.
useEffect(() => {
setIsDark(getInitialIsDark());
}, [getInitialIsDark]); //dependency only needed to satisfy linter, essentially runs on mount.
Although most useEffect examples use an anonymous function, you might find more understandable to use named functions (following the clean-code principle of using function names for documentation)
useEffect(function checkOnMount() {
setIsDark(getInitialIsDark());
}, [getInitialIsDark]);
useEffect(function persistOnChange() {
if (typeof window !== "undefined" && isReady) {
console.log("save is dark " + isDark)
window.localStorage.setItem("theming:isDark", isDark.toString())
}
}, [isDark])
I had a similar issue where some styles weren't taking effect because they were being applied to through classes which were set on mount (like you only on production build, everything worked fine in develop).
I ended up switching the hydrate function React was using from ReactDOM.hydrate to ReactDOM.render and the issue disappeared.
// gatsby-browser.js
export const replaceHydrateFunction = () => (element, container, callback) => {
ReactDOM.render(element, container, callback);
};
This is what worked for me, try this and let me know if it works out.
First
In src/components/ i've created a component navigation.js
export default class Navigation extends Component {
static contextType = ThemeContext // eslint-disable-line
render() {
const theme = this.context
return (
<nav className={'nav scroll' : 'nav'}>
<div className="nav-container">
<button
className="dark-switcher"
onClick={theme.toggleDark}
title="Toggle Dark Mode"
>
</button>
</div>
</nav>
)
}
}
Second
Created a gatsby-browser.js
import React from 'react'
import { ThemeProvider } from './src/context/ThemeContext'
export const wrapRootElement = ({ element }) => <ThemeProvider>{element}</ThemeProvider>
Third
I've created a ThemeContext.js file in src/context/
import React, { Component } from 'react'
const defaultState = {
dark: false,
notFound: false,
toggleDark: () => {},
}
const ThemeContext = React.createContext(defaultState)
class ThemeProvider extends Component {
state = {
dark: false,
notFound: false,
}
componentDidMount() {
const lsDark = JSON.parse(localStorage.getItem('dark'))
if (lsDark) {
this.setState({ dark: lsDark })
}
}
componentDidUpdate(prevState) {
const { dark } = this.state
if (prevState.dark !== dark) {
localStorage.setItem('dark', JSON.stringify(dark))
}
}
toggleDark = () => {
this.setState(prevState => ({ dark: !prevState.dark }))
}
setNotFound = () => {
this.setState({ notFound: true })
}
setFound = () => {
this.setState({ notFound: false })
}
render() {
const { children } = this.props
const { dark, notFound } = this.state
return (
<ThemeContext.Provider
value={{
dark,
notFound,
setFound: this.setFound,
setNotFound: this.setNotFound,
toggleDark: this.toggleDark,
}}
>
{children}
</ThemeContext.Provider>
)
}
}
export default ThemeContext
export { ThemeProvider }
This should work for you here is the reference I followed from the official Gatsby site

Resources