Force change app language without restart - react-native

Currently my react native app allow language switching by user (have a language selection screen in the app for user to switch) with react-native-localization. What i did is use redux-persist to store the selected language. But that requires an app restart to take effect.
However, we have a business requirement to switch the language on the fly. Anyone have successfully implement it before?
import LocalizedStrings from 'react-native-localization';
export default new LocalizedStrings({
en: {
},
de: {
},
});
In my main navigator:
componentDidMount() {
const language = store.getState().language;
if (language.code) {
LocalizedStrings.setLanguage(language.code);
} else {
LocalizedStrings.setLanguage('en');
}
}
Anyone have successfully implemented in such a way that an app restart is not required?

When language got update, you just need to update key of the root component to force all child component re-render.
In my case, I am using react-navigation, whenever language changed. I update screenProps
export default () => {
const language = useSelector((state: AppState) => state.preferences.language);
return (
<AppContainer
screenProps={{ language }}
...
/>
);
};

Related

React Native Navigation: Navigate to a screen from outside a React component

https://wix.github.io/react-native-navigation/docs/basic-navigation#navigating-in-a-stack
indicates that pushing a new screen to the stack requires the current screen's componentId. My use case is to allow navigating based on certain events emitted by a native module. As such, componentId won't be available to me, since the event listener would reside outside any React component screen. Is there any way to navigate to a screen from outside in RNN? Or even get the current componentId.
I ended up adding a command event listener to store the current componentId in closure.
let currentComponentId;
Navigation.events().registerCommandListener((name, params) => {
if (name === 'push') {
currentComponentId = params.componentId;
}
});
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot(rootRouteConfig);
EventEmitter.addListener('navigate', (name) => {
Navigation.push(currentComponentId, {
component: {
name,
},
});
});
});

React Native Arabic (RTL) without forceRTL

In RN my bilingual app (English - Arabic), I have used I18nManager (views) and I18n (for translations)
When I am changing app language to Arabic, the whole app gets reloaded again from the splash-screen using this code:
I18nManager.forceRTL(true)
Ideally, it should not restart the app from start and it should continue with the current screen with Arabic data.
Currently, it is not happening, only translation elements are getting converted using I18n.t('keyword') but for views Arabic alignment, it's not proper.
Still looking for a better solution, let me know if anyone achieved it.
Thanks
Sopo !!
you should put this code in the top component in your project
import RNRestart from "react-native-restart";
I18nManager.forceRTL(true);
if (!I18nManager.isRTL) RNRestart.Restart();
If you guys wants to store stack state after reloading(because there is no other option without reloading) and want stack state back you can follow this link also you can check my code.
Link: React navigation state persist
Any Component
AsyncStorage.setItem('navigation_state', JSON.stringify(navigation.dangerouslyGetState()));
My App.js
const App = () => {
const [initialState, setInitialState] = useState();
const [isReady, setIsReady] = useState(false);
useEffect(() => {
restoreState();
}, []);
const restoreState = async () => {
try {
const savedStateString = await AsyncStorage.getItem('navigation_state');
const state = savedStateString ? JSON.parse(savedStateString) : undefined;
if (state !== undefined) {
AsyncStorage.removeItem('navigation_state');
setInitialState(state);
}
} finally {
setIsReady(true);
}
};
if (!isReady) {
return null;
}
return (
<Provider store={store}>
<NavigationContainer
initialState={initialState}
ref={rootNavigationRef}>
<Root>
<AppNavigator />
</Root>
</NavigationContainer>
</Provider>
);
};
I working on a project which has two languages, Arabic and English.i use redux for handling app language. I put all styles on redux and handle app style with redux. and when user change language all styles on my app change to that language . also all text handled with redux too. with this way, my app does not reload and app language changed immediately.
If your app is an android hybrid app, you can try this:
import com.facebook.react.modules.i18nmanager.I18nUtil;
I18nUtil i18nUtil = I18nUtil.getInstance();
i18nUtil.forceRTL(context, forceRtl);
i18nUtil.allowRTL(context, true);
value 'forceRtl' is a boolean.
for iOS,I think you can find the same method.
In Expo use
import {Updates} from "expo"
Updates.reload()

Executing this.function on React Native/Router Flux with forwardRef

I am actually updating an app that's contains this code:
routes.js
<Scene key='events' component={Events} title='Brief'/>
Events/index.js
static onEnter() {
Actions.refs.events.getWrappedInstance().refreshScene()
}
refreshScene = () => {
this.setState({any: true})
}
export default connect(mapStateToProps,
{
...
}, null, {withRef: true})(Events)
But now, the newest version of router flux don't accept anymore withRef. You must replace by forwardRef.
So,
export default connect(mapStateToProps,
{
...
}, null, {forwardRef: true})(Events)
will not allow Actions.refs.events.getWrappedInstance().refreshScene() anymore. And I can't make my refreshScene() works again.
Any clue about how to solve the situation ?
react-native-router-flux seems to have removed Actions.refs functionality since version 4.0.0
Comment on the issue from one of the contributors suggests:
Either use a HOC
Either use Redux to pass the same method in your components
Either use your method as a prop and doing the trick in this way
Here is the changelog

React navigation state management without redux

I'm using React Navigation library for my React Native project and struggling to understand how to handle state with it.
In normal React Native application I can have state at the top level component and pop events from child components via props, however with React Navigation it seems that I cannot pass any props to components used as Screens.
After reading through related GitHub issue it seems that library devs are very opinionated in forcing everyone to use some kind of global event handler - redux or mobx, I guess.
The handler which needs to modify the following state. I got stuck when I started to try to move the state inside the app as I couldn't figure out how to:
Pass the handler to the TaskForm component.
Pass the state as props to TaskList if its rendered as part of App.js
Please, avoid replying "just use redux". I believe that using redux in this example would be massive overkill.
I use react native and react navigation in my app without redux, and so far it’s working great. The trick is passing screenProps all the way down the line.
For example, I have a More view. I create a basic More view with 2 sub views in a stack:
class More extends Component {
render() {
return <something>
}
}
class SubView1 extends Component {...}
class SubView2 extends Component {...}
Then I create the stack:
const MoreStack = StackNavigator({
More: {
screen: More
},
SubView1: {
screen: SubView1,
},
...
}, options);
And then I create a wrapper class that I export:
export default class MoreExport extends Component {
static navigationOptions = {
title: "More"
}
render() {
return <MoreStack screenProps={this.props.screenProps} />;
}
}
If all of this is in More.js, I can just import More from More.js and use it anywhere else.
If I then pass in screenProps to my root view and then have a wrapper class for each layer, I can pass the screenProps all the way down, and all views can access them using this.props.screenProps.
I use a wrapper like the one above around each StackNavigator and TabNavigator, and the screenProps are passed all the way down.
For example, in my root class’s render method, I could do:
return <More screenProps={{prop1: something, prop2: somethingElse}} />
And then the More class and each SubView in the MoreStack would all have access to these props.
Feel free to let me know if you want more clarification!
Disclaimer: I don’t know if this is the correct or recommended way to do it, but it does work
You can set param to navigation like this:
static navigationOptions = ({ navigation }) => {
return {
tabBarIcon: ({ tintColor, focused }) =>
<View>
<Icon name="bell-ring" size={24} color={focused ? 'green' : 'black'} />
{(navigation.state.params.badgeCount && navigation.state.params.badgeCount > 0) ?
<Text>{navigation.state.params.badgeCount}</Text>
:
<View></View>
}
</View>
}
}
and change badgeCount by:
this.props.navigation.setParams({ badgeCount: 3 })
After being inspired by steffeydev I looked more around react community issues and found a very simple example of wrapper using function.
It's surprisingly simple solution and I don't know why I didn't think about it before.
The function is the following:
const createComponent = (instance, props) =>
navProps => React.createElement(instance, Object.assign({}, props, navProps));
Thanks for inspiration and pointing me to screenProps which lead me to finding this solution.
I was struggling with the same issue and tried some of the other answers before discovering the following part of the documentation for React Navigation: https://reactnavigation.org/docs/en/stack-navigator.html#params.
Essentially, each Screen in the Stack can be passed params which can include handlers and then the various screens can interact with the application state.
My general structure is then to have an App class with state and handlers and the handlers are then passed into each Navigation Screen as needed. I'm not sure if I have this pattern right, but it's the way I understood the general React tutorial.
Example: In my demo app, I have a page flow like this:
Park finder screen -> Park detail screen (with Bookmark action)
Bookmark list screen -> Park detail screen
If you find a park, you can click on a Bookmark button which adds the park to the list of bookmarks shown on the Bookmark screen. You can then click on a park bookmark to see the details.
My App looks generally like this:
import React, { Component } from 'react';
// Screens
import ParkFinderScreen from './Components/ParkFinderScreen';
import ParkBookmarksScreen from './Components/ParkBookmarksScreen';
import ParkDetailsScreen from './Components/ParkDetailsScreen';
// Navigation
import { createStackNavigator, createBottomTabNavigator, createAppContainer } from 'react-navigation';
class App extends Component {
constructor(props) {
super(props);
this.state = {
bookmarks: new Map()
};
}
// Bookmark the park shown in the detail section.
handleBookmark (park) {
let newBookmarks = this.state.bookmarks;
newBookmarks.set(park.parkCode, park);
this.setState({
bookmarks: newBookmarks
});
}
render() {
const FinderStack = createStackNavigator(
{
ParkFinder: {
screen: ParkFinderScreen
},
ParkFinderDetails: {
screen: ParkDetailsScreen,
params: {
handleBookmark: (park) => this.handleBookmark(park),
}
},
}
);
const BookmarksStack = createStackNavigator(
{
ParkBookmarks: {
screen: ParkBookmarksScreen,
params: {
bookmarks: this.state.bookmarks
}
},
ParkBookmarksDetails: {
screen: ParkDetailsScreen,
},
}
);
const AppNavigator = createBottomTabNavigator(
{
Bookmarks: BookmarksStack,
Finder: FinderStack,
}
);
const AppContainer = createAppContainer(AppNavigator);
return (
<AppContainer/>
);
}
}
export default App;
I'm using Apollo Client, but I've removed those parts.
In the Screen components, you can access the props like other ones using this.props.navigation.getParam('bookmarks').
One issue I encountered was that whenever I change the App state, I'm taken to the first screen. The state is updated, but it's a little disorienting. I'm not sure if there is a way to update the App state while staying on the screen. I can sort of understand that given the App state has updated, all the children need to be updated and so the current screen (which is part of a child I think) is reset. I don't know if that is a limitation of the system or a byproduct of how I designed the components.
I hope this helps someone. This seems to keep with the intended behavior of React Native. The library team really seems to want you to not use Redux. https://reactnavigation.org/docs/en/redux-integration.html

React Native startup optimization

I am looking for a way to optimize the startup time of a pure react native mobile app.
As a JavaScript framework, is that possible to bundle the JavaScript files into separated files, say something like common.js and app.js. I was searching via Google with keywords something like "react native webpack" stuff but it seems like all these libraries are deprecated or out of date, such as react-native-webpack-server, react-native-webpack-starter-kit etc.
I am wondering if anybody here is also looking for a way to optimize the JavaScript bundle in react native. Or, maybe these third party bundle approach has been overcame by Facebook standard bundle?
You could dynamically load your component, in this way your bundle.js will contain only the fraction of js needed and as you navigate you will request the other different parts / fractions.
Rather than do the traditional way: import App from './containers/App/App'; you could do something like this:
class ImportedComponent extends Component {
state = {
component: null
}
componentWillMount() {
this.props.load()
.then((mod) => this.setState(() => ({
component: mod.default
})))
}
render() {
return this.props.children(this.state.component)
}
}
const App = (props) => (
<ImportedComponent load={() => import('./containers/App/App')}>
{(Component) => Component === null ? <h6 className="loading-message">Loading...</h6> : <Component {...props}/>}
</ImportedComponent>
)
or you can lazy load your component itself. Let's say for example that I have Moment JS and I don't want to load it until it's needed. so what I could do:
1) Create a state and set it to null.
constructor(props){
super(props);
this.state = {
lazyLoadedComponent: () => null
}
}
2) Use async componentDidMount with await, try and catch and update the state lazyLoadedComponent on componentDidMount
async componentDidMount(){
try {
const Moment = await import('react-moment');
this.setState({ lazyLoadedComponent: (data)=>{
return React.createElement(Moment.default, {format:'MM/DD/YY'}, data)
}
});
}
catch(err) {
this.setState({ lazyLoadedComponent: <div>{`Failed to load component: ${err}`}</div> });
}
}
3) Call the component on the render:
{this.state.lazyLoadedComponent(value.createdOn)}
By following these 2 examples you should, hopefully, be looking at a bundle.js under 250KB.
As a possible solution you can use ram-bundle format, that metro bundler provides.
In this case you will not load the entire js-bundle - you will load only part, that you need at a startup (in a lot of application are a lot of places, which user may not even see, and this feature allow you load such parts, only when they are required). So you can simplify your entry point and load only small piece of your bundle.
You can look at react-native-bundle-splitter. This library well integrated with almost all popular navigation libraries and allows you to postpone a loading of specific routes. For example, if you have a login screen, you can load at start up only this screen, and all others load in background or start the loading of them, only when user can see them.