React Native Paper - Redux ToolKit : Redux doesn't track change in fonts between different themes - react-native

I am building an application using React Native and Expo.
I'm using Redux ToolKit to store the state of different themes in React Native Paper. In this example, I switch between two themes (one light and one dark) with a simple switch. I can change the colors without any problem and I can see in the debugger that Redux follows perfectly the state changes between the two themes.
However, if I change the font size for only the light theme with the configureFonts function recommended by Paper, Redux (and so my application) does not follow the font size changes between the two themes.
Here is my redux slice (themeSlice.js):
import { createSlice, current } from '#reduxjs/toolkit'
import { MD3LightTheme, MD3DarkTheme, configureFonts } from 'react-native-paper';
const fontConfigLight = {
"labelLarge": {
"fontSize": 20,
"fontWeight": "500",
"letterSpacing": 0.1,
"lineHeight": 25,
}
};
const lightTheme = {
...MD3LightTheme,
fonts: configureFonts({config: fontConfigLight}),
};
const darkTheme = {
...MD3DarkTheme,
};
export const themeSlice = createSlice({
name: 'counter',
initialState: lightTheme,
reducers: {
switchTheme: (state, action) => {
if (action.payload === 'light'){
// console.log('light theme : labelLarge', current(state.fonts.labelLarge))
return lightTheme
} else {
// console.log('dark theme : labelLarge', current(state.fonts.labelLarge))
return darkTheme
}
},
}
})
export const { switchTheme } = themeSlice.actions
export default themeSlice.reducer
and I use the reducer in the SwitchPaper.js file :
import { useState } from 'react';
import { Switch } from 'react-native-paper';
import { useDispatch } from 'react-redux'
import { switchTheme } from './themeSlice'
const ThemeSwitch = () => {
const [isSwitchOn, setIsSwitchOn] = useState(false);
const dispatch = useDispatch()
const onToggleSwitch = () => {
setIsSwitchOn(!isSwitchOn)
if (isSwitchOn) {
dispatch(switchTheme('light'))
} else {
dispatch(switchTheme('dark'))
}
}
return (
<Switch value={isSwitchOn} onValueChange={onToggleSwitch} />
);
};
export default ThemeSwitch;
I suspect that the configureFonts function causes a conflict with the immutability of the Redux store as I sometimes get the error : TypeError: Cannot assign to read only property 'labelLarge' of object '#'.
I am looking for a solution to change fonts between themes while keeping my themes in the global Redux state.
EDIT:
a non-elegant way to solve the problem would be to change darkTheme to :
const darkTheme = {
...MD3DarkTheme,
myOwnProperty: true,
fonts: {
...MD3DarkTheme.fonts,
"labelLarge": {
fontFamily: "System",
letterSpacing: 0.5,
fontWeight: "500",
lineHeight: 16,
fontSize: 11
}
}
};
wishing that the function configureFonts was not there for a particular (and good) reason ...

Related

Are there any parts where 'react-native-mmkv' and 'redux' should be viewed differently? for react-native

I use both 'react-native-mmkv' and 'redux' in my project, but there is no big difference in the way data is stored and used.
Aside from the performance, what's better for storing and using data securely? Or is it better to use both methods?
I am sorry that I am not good at English.
is my code
import * as React from 'react';
import {
SafeAreaView,
Text,
Pressable,
} from 'react-native';
// UI components
import { Button } from 'atoms';
import { Card, SelectList } from 'molecules';
// Hooks
import { useIsFocused } from '#react-navigation/native';
// Redux
import { useAppSelector, useAppDispatch } from 'hooks';
// utils
import { initializeAccountData } from 'utils';
import { useMMKVString } from "react-native-mmkv";
const App = ({ navigation, route }: any) =\> {
// Navigation
const isFocused = useIsFocused();
// Redux
// const { accounts } = useAppSelector(state =\> state.accounts);
// const dispatch = useAppDispatch();
// MMKV Hooks
const \[accounts, setAccount\] = useMMKVString('accounts')
// Update to didMount, update
React.useEffect(() =\> {
const dataInitialize = async () =\> {
await initializeAccountData();
}
dataInitialize();
}, \[\]);
// Update to page focusing
React.useEffect(() =\> {
}, \[isFocused\]);
// Update to account change
React.useEffect(() =\> {
}, \[accounts\]);
return (
\<SafeAreaView style={{ flex: 1 }}\>
...
\</SafeAreaView\>
)
}
export default App;
Redux is in memory storage, if you kill your app you will lose app state.
MMKV is persistent storage and you need to use it if you want to save data for the long term.
You can connect MMKV with redux if you want to store redux state. An example is here https://github.com/mrousavy/react-native-mmkv/blob/master/docs/WRAPPER_REDUX.md

React Native error, Redux: The slice reducer for key "nav" returned undefined during initialization

Making an uber clone with React Native, while setting up the Redux while making the app in the first place, the Metro bundler returned these errors:
Error: The slice reducer for key "nav" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.
at node_modules\react-native\Libraries\LogBox\LogBox.js:149:8 in registerError
......
Invariant Violation: "main" has not been registered. This can happen if:
* Metro (the local dev server) is run from the wrong folder. Check if Metro is running, stop it and restart it in the current project.
* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.
.....
This is the App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Provider } from 'react-redux';
import { store } from './store';
// 1. Set up redux - done
//
export default function App() {
return (
<Provider store={store}>
<View style={styles.container}>
<Text>Uber</Text>
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
This is the navSlice.js
import { createSlice } from "#reduxjs/toolkit";
const innitialState = {
origin: null,
destination: null,
travelTimeInformation: null,
}
export const navSlice = createSlice({
name:"nav",
innitialState,
reducer: {
setOrigin: (state, action) =>{
state.origin = action.payload;
},
setDestination: (state, action) =>{
state.destination = action.payload;
},
setTravelTimeInformation: (state, action) =>{
state.travelTimeInformation = action.payload;
},
}
});
export const { setOrigin, setDestination, setTravelTimeInformation } = navSlice.actions;
// Selectors
export const selectOrigin = (state) => state.nav.origin;
export const selectDestination = (state) => state.nav.destination;
export const selectTravelTimeInformation = (state) => state.nav.travelTimeInformation;
export default navReducer = navSlice.reducer;
This is the Store.js
import { configureStore } from "#reduxjs/toolkit";
import navReducer from "./slices/navSlice";
export const store = configureStore({
reducer: {
nav: navReducer,
},
});
What are the reasons I am getting back these errors?
How can I fix this?
Is there an issue with how I am setting up the redux?
Change the spelling in navslice to reducers and restart emulator
Your innitialState spelling is wrong.
Change it to initialState

How to call hook in class component in React Native?

Tech used
React Native Appearance, Typescript & Redux Rematch.
Problem
I am attempting to pass my customized theme colours into a class component. I also understand that hooks cannot be used/called within a class component and only a functional component. The reason why I am using a class component is because of Redux Rematch. Is there a way for me to get my colours from the hook listed below into my class component?
This is how I am building my theme:
index.tsx
const palette = {
colourTextDark: "#ffffff",
colourTextLight: "#000000",
};
export const colors = {
colourText: palette.colourTextLight,
};
export const themedColors = {
default: {
...colors,
},
light: {
...colors,
},
dark: {
...colors,
colourText: palette.colourTextDark,
},
};
hooks.tsx
import { useColorScheme } from "react-native-appearance";
import { themedColors } from "./";
export const useTheme = () => {
const theme = useColorScheme();
const colors = theme ? themedColors[theme] : themedColors.default;
return {
colors,
theme,
};
};
Ideally I would want to use it like so:
import { useTheme } from "../../theme/hooks";
...
class Example extends React.Component<Props> {
render() {
// This doesn't work
const { colors } = useTheme();
return (
<Text style={{ color: colors.colourText }}>Please help :)</Text>
)
}
}
How would I be able to do this? Thanks in advance :)
You could create a high order component like this:
const themeHOC = (Component) => {
return (WrappedComponent = (props) => {
const { colors } = useTheme();
return <Component {...props} colors={colors} />;
});
};
And use it like this:
themeHOC(<Example />)
This worked for me.
componentDidMount() {
(async () => {
const theme = useColorScheme() === "dark" ? styles.dark : styles.light;
this.setState({ theme });
})();
this.sendEmail();
}

How to map state to props on the initial file (App.js or Index.js)?

This is probably something very basic. There is a spinner on my App where the routes and providers are declared. This must be reading the redux store, in particular spinner.visible and map to state so I can hide/show the <Spinner> element.
But as I said...this is the entry file of the app. I know how to map it to props using connect, but looks like I can't use connect/mapStateToProps on my entry file.
This works very good, but I don't think that using a subscribe is the best way. I'd like to make the spinner be capable to read the store directly in an elegant way. Any suggestions ?
import React from 'react'
import {Provider} from 'react-redux'
import {View} from 'react-native'
import {createStore, applyMiddleware} from 'redux'
import Spinner from 'react-native-loading-spinner-overlay'
import ReduxThunk from 'redux-thunk'
import reducers from './reducers'
import Routes from './config/routes'
import {getReady} from './services/registration'
import {setAppInitialLoad, setAppSpinner} from './actions/AppActions'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
initialized: false,
spinner: {
visible: false,
text: ''
}
}
store.subscribe(() => {
//ToDo: I really hope to find an elegant solition for this.
//Since it' the top level JS file of the app, I can't use
//connect/mapStateToProps to map the props :(
const spinner = store.getState().AppReducer.spinner
if(spinner.visible != this.state.spinner.visible) {
this.setState({
spinner: {
visible: spinner.visible,
text: spinner.text
}
});
}
}
)
}
componentDidMount() {
store.dispatch(setAppSpinner({ visible: true, text: 'Loading...'}))
getReady().then(response => {
store.dispatch(setAppInitialLoad(response.data.data))
store.dispatch(setAppSpinner({ visible: false, text: ''}))
this.setState({initialized: true})
})
}
render() {
if (this.state.initialized) {
return (
<View>
<Provider store={store}>
<Routes/>
</Provider>
<Spinner visible={this.state.spinner.visible} textContent={this.state.spinner.text}
textStyle={{color: '#000'}}/>
</View>
)
} else {
return (
<View style={{backgroundColor: 'yellow', flex: 1}}/>
)
}
}
}
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk))
export default App;
Use can use store variable
(In your code, it here: const store = createStore(reducers, {}, ...)
store variable has some method, you can read at here (https://redux.js.org/basics/store)

Redux error in react-native

I'm trying to implement redux to show balance in multiple screens as I update balance in single screen it should reflect in all other screens/components.
I'm pretty new to redux. As you know with complexity around redux, its even making difficult to implement it.
I followed some examples in GitHub and youtube and started implementing it .
Under Actions folder I have. following two files
counteractions.js
import * as types from './actionTypes.js';
//ActionCreator methods
export function updateBalance(balanceInfo) {
return {
type: types.LEDGER_BALANCE,
payLoad: { balanceInfo }
}
}
Under Reducers folder.I have this file
balance.js
import * as types from '../actions/actionTypes.js';
const initialState = {
balance: 0
}
// reducer functions .. accepts current/initial state , actions and returns new state
const balanceReducer=(state,action)=>
{
switch (action.type) {
case types.LEDGER_BALANCE:
return {
balance: action.payload.balanceInfo
}
break;
default:
break;
}
}
export default balanceReducer;
in ConfigureStore.js
import {createStore} from 'redux';
import rootReducer from './reducers/index.js';
import balanceReducer from './reducers/balance.js';
const initailState = {
balance: 0,
}
export const store=createStore(balanceReducer,balanceReducer);
App.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, { Component } from 'react';
import { Provider } from 'react-redux';
//Provider - makes redux store available to connect() class in component hierarchy below
import { applyMiddleware, createStore, compose, combineReducers } from "redux";
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import rootReducer from './reducers/index.js';
//import store from './configureStore.js';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity,
TextInput
} from 'react-native';
import ReduxDemo from "./reduxDemo.js";
import { store, reducer } from './balanceDemo.js';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
balancelocal: '',
}
}
_updateLedger = () => {
// store.dispatch({ type: 'BALANCE', payLoad: '500' });
store.dispatch({ type: 'BALANCE', payLoad: 'Your balance is 8000 MUR' });
}
render() {
store.subscribe(() => {
this.setState({
balancelocal: store.getState(),
})
//this.balanceInfo = store.getState().balance;
// alert(this.state.balancelocal);
});
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._updateLedger}>
<Text>Update balance</Text>
</TouchableOpacity>
<TextInput style={{height:100,width:400}} value={this.state.balancelocal}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
I'm yet to complete configure store file. and. I'm wondering. where I have to subscribe and dispatch actions ..
I want to update balance with button click from app.js
I have. to update balance in another page automatically..
Please guide me to understand and implement redux .Please suggest better folder structure and better way to implement redux.
There is quite a bit here to understand.
The basic workflow is (you can have the receiving component as a different one)
Component Button > Action > Reducer > Component Props > Render
To achieve this you need both the setup of the store and the invoking of the "event" through redux.
Below is an example (excuse if not perfect, just typed into here), but the way the other component gets the value from the action is becuase it uses the 'connect' HOC. Everytime redux gets a state change it calls all components that are 'connected'. Here we take the updated balance and return it as part of the 'mapStateToProps' function, which is just setting the components props with that object. The balance is then accessed via this.props.balance and displayed.
This becomes more useful if you want to either call an api in the action and store the result in the reducer. All connected components will then get that new data.
Note1: I have only used redux-thunk middleware to dispatch so forgive me for using that.
Note2: This is a simple example. When the app gets better you will need to prevent over-rendering since any reducer changes will invoke all connected components. I use reselect here.
Setup
reducers.js
import { combineReducers } from 'redux';
import { balanceReducer } from 'balanceReducer';
export default combineReducers({
balanceReducer
})
store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import combineReducers from './reducers'
export default function configureStore() {
let store = createStore(combineReducers, applyMiddleware(thunk));
return store;
}
index.js
import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';
import { Provider } from 'react-redux';
import configureStore from './store';
import Component1 from './component1';
const store = configureStore()
const myapp = () => (
<Provider store={store}>
<View>
<Component1 />
<View>
</Provider>
)
AppRegistry.registerComponent('myapp', () => myapp);
Components
component1.js (key part is the connect HOC)
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { updateBalance } from './action';
class Component1 extends Component {
_updateLedger = () => {
this.props.updateBalance(500);
}
render() {
const { balance } = this.props;
return (
<View>
<TouchableOpacity onPress={this._updateLedger}>
<Text>Update balance</Text>
</TouchableOpacity>
<Text>{balance}</Text>
</View>
)
}
}
function mapStateToProps(state) {
return {
balance: state.balanceReducer.balance
}
}
function mapDispatchToProps(dispatch) {
return {
updateBalance = (balanceInfo) => dispatch(updateBalance(balanceInfo))
};
}
export default connect(
mapStatetoProps,
mapDispatchToProps
)(Component1)
action.js
export function updateBalance(balanceInfo) {
return {
type: types.LEDGER_BALANCE,
payLoad: { balanceInfo }
}
}
balanceReducer.js (key part here is to return new state)
const initialState = {
balance: 0,
}
export function balanceReducer(state = initialState, action) {
if(action.type === types.LEDGER_BALANCE) {
return {
balance: action.payLoad.balanceInfo
}
}
}