How to get root component to re-render in react-native with redux (Open source project) - react-native

How would you get the root component in React-Native (Expo.io) to re-render on state change when using redux?
I'm trying to get <FormattedWrapper locale='en' messages={messages}> to update "locale" when state is changed.
I have tried to have a local state in the constructor, use store.getState().language.language, have a local variable which got update in ComponentWillUpdate because of a subscribe function from redux, but nothing works.
I have clean it all up and made a PR to the repo I want to contribute to: https://github.com/ipeedy/react-native-boilerplate/pull/3
The App.js code is here:
import React, { Component } from 'react';
import { StatusBar, Platform } from 'react-native';
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import styled from 'styled-components/native';
import { FormattedWrapper } from 'react-native-globalize';
import messages from './Messages';
import store from './store';
import Navigator from './Navigator';
import { colors } from './utils/constants';
const Root = styled.View`
flex: 1;
background-color: ${props => props.theme.PINK_50};
`;
const StatusBarAndroid = styled.View`
height: 24;
background-color: ${props => props.theme.PINK_200};
`;
class App extends Component {
render() {
return (
<Provider store={store}>
<ThemeProvider theme={colors}>
<FormattedWrapper locale='en' messages={messages}>
<Root>
<StatusBar barStyle='light-content' backgroundColor='transparent' translucent />
{ Platform.OS === 'android' && Platform.Version >= 20 ? <StatusBarAndroid /> : null }
<Navigator />
</Root>
</FormattedWrapper>
</ThemeProvider>
</Provider>
);
}
}
export default App;
Thanks in advance for any help! :)

Since you're aleady using react-redux, therefore you can use the connect function of the same library.
Since Connect requires a new instance of the component that you are using, therefore it can be done as shown below.
First you need to make a separate component of what's inside your provider and connect it with the store
For example from what I understand in your code
import { Provider, connect } from 'react-redux';
class RootContainer extends Component {
render() {
const {locale} = this.props
return (
<ThemeProvider theme={colors}>
<FormattedWrapper locale={locale} messages={messages}>
<Root>
<StatusBar barStyle='light-content' backgroundColor='transparent' translucent />
{ Platform.OS === 'android' && Platform.Version >= 20 ? <StatusBarAndroid /> : null }
<Navigator />
</Root>
</FormattedWrapper>
</ThemeProvider>
);
}
}
const mapState = state = ({
locale: state.language.language
})
ConnectedRootContainer = connect(mapState, null)(RootContainer);
class App extends Component {
render() {
return (
<Provider store={store}>
<ConnectedRootContainer/>
</Provider>
);
}
}
export default App;
Or another simple way would be just to make the connected component for the <FormattedWrapper/>

Related

Nextjs with Styled components not passing theme

I'm trying to pass my theme object to styled-components on my Next.js project, but it keep's failing to receive it as a props.
That's my _document.tsx
import { getInitialProps } from "#expo/next-adapter/document";
import Document, { DocumentContext, DocumentInitialProps } from "next/document";
import { ServerStyleSheet } from "styled-components";
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}
After that I wrapped my _app.tsx with the ThemeProvider
import Layout from '../components/Layout'
import 'setimmediate';
import { ThemeProvider } from 'styled-components';
import { wrapperStore } from "../store";
import { ApolloProvider } from "#apollo/client";
import client from './api/apollo-client';
import React from 'react';
import lightTheme from '../helpers/light-mode';
import { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
return (
<React.Fragment>
<ApolloProvider client={client}>
<ThemeProvider theme={lightTheme}>
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</ApolloProvider>
</React.Fragment>
)
}
export default wrapperStore.withRedux(MyApp);
From here I receive a warning when I try to wrap my component, exporting it withTheme
[withTheme] You are not using a ThemeProvider nor passing a theme prop or a theme in defaultProps in component class "Connect(Front)"
when I try to console log theme from my props, it returns undefined, and when I create a styled component, trying to use theme as props it causes an error
export const RecruitmentTextLocation = styled.Text`
fontSize: ${scaleFont(15)};
textAlign: left;
marginTop: ${scale(5)}px;
marginBottom: ${scale(1)}px;
fontFamily: roboto;
fontWeight: 500;
color: ${({theme}) => theme.colors.cardTitleColor};
`;
TypeError: Cannot read property 'colors' of undefined
I'm really stuck on how I can use theming with styled-components on Nextjs.
I suppose you problem is in light-mode.tsx. How can i see you use typescript in that case need create a declarations file and DefaultTheme interface. example
Create a declarations file
TypeScript definitions for styled-components can be extended by using declaration merging since version v4.1.4 of the definitions. documentation
light-mode.tsx
import { DefaultTheme } from 'styled-components';
export const lightTheme: DefaultTheme = {
colors: {
cardTitleColor: red;
}
};
I was looking for the same and found a solution for typing the theme object you receive as prop on your styled components.
First, export the types of your themes, e.g.:
export type LightTheme = typeof lightTheme;
Then add a src/declaration.d.ts file with the below, so to enhance the DefaultTheme exported by styled-components to be typed with your custom theme object:
import { Theme } from "globalStyles";
declare module "styled-components" {
export interface DefaultTheme extends Theme {}
}
From there you won't need any withTheme to use your theme object, as you will simply be able to access it through:
color: ${({theme}) => theme.colors.cardTitleColor};

Unable to load provider from react-redux module in react native

I am creating a slide bar, In that, I have used the react-redux library. When I call the class which contains the redux-code, it works fine. I want to show this slide bar after login. Therefore, with conditions (I set a state variable if user login successfully then only this page should get rendered), I tried to call the same file which shows a blank page. I printed the console log. I am able to print all the logs. But with conditions, I am not able to load the data.
I don't know much about react-redux.Can you assist me to resolve this?
My code is,
main.js,
import React, {Component} from 'react';
import {
StyleSheet,
Dimensions,
Platform,
View,
StatusBar,
DrawerLayoutAndroid,
} from 'react-native';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from '../Redux/reducers';
import { setNavigator, setActiveRoute } from "../Redux/actions";
import DrawerContent from '../Navigation/DrawerContent';
import Toolbar from '../Navigation/Toolbar';
import AppNavigation from '../Navigation/AppNavigation';
import { bgStatusBar, bgDrawer } from '../global.styles';
let store = createStore(reducer);
/* getDrawerWidth Default drawer width is screen width - header width
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const getDrawerWidth = () => Dimensions.get('window').width - (Platform.OS === 'android' ? 56 : 64);
export default class Main extends Component {
constructor() {
super();
this.drawer = React.createRef();
this.navigator = React.createRef();
}
componentDidMount() {
store.dispatch(setNavigator(this.navigator.current));
}
openDrawer = () => {
this.drawer.current.openDrawer();
};
closeDrawer = () => {
this.drawer.current.closeDrawer();
};
getActiveRouteName = navigationState => {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getActiveRouteName(route);
}
return route.routeName;
};
render() {
return (
<Provider store={store}>
<DrawerLayoutAndroid
drawerWidth={getDrawerWidth()}
drawerPosition={DrawerLayoutAndroid.positions.Left}
renderNavigationView={
() => <DrawerContent closeDrawer={this.closeDrawer} />
}
ref={this.drawer}
>
<View style={styles.container}>
<StatusBar
translucent
animated
/>
<Toolbar showMenu={this.openDrawer} />
<AppNavigation
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = this.getActiveRouteName(currentState);
store.dispatch(setActiveRoute(currentScreen));
}}
ref={this.navigator}
/>
</View>
</DrawerLayoutAndroid>
</Provider>
);
}
}
Login.js
import Main from './main';
render() {
return (
<View>
{this.state.isLoggedIn ?
<Main/>
:
<ChangePassword isUpdatePassword={this.state.isUpdatePassword} callLogin={this.callLogin}/>
);
}
}
If I just call Main class inside render method it works. But It does not work with conditions.

can react-native-root-siblings work with react-redux

in a handleClick function, update the rootSiblings like this,
handleClick() { this.progressBar.update( <ProgressBar /> ); }
and in ProgressBar component,
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
const getFinishedWidth = progress => ({ width: progress * totalWidth });
const getUnfinishedWidth = progress => ({ width: (1 - progress) * totalWidth });
function CustomerReassignProgressBar(props) {
const { progress } = props;
return (
<View style={styles.bar}>
<View style={getFinishedWidth(progress)} />
<View style={getUnfinishedWidth(progress)} />
</View> );
}
CustomerReassignProgressBar.propTypes = { progress: PropTypes.number, };
const mapStateToProps = state => ({ progress: state.batchReassignProgress, });
export default connect(mapStateToProps)(ProgressBar);
then, when calling handleClick(), the app crushed, the error is, 'Could not find "store" in either the context or props of "Connect(ProgressBar)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ProgressBar)".'
if I don't use connect in component, it works well. So, I guess, maybe rootSiblings can not work with react-redux. But does anyone knows this problem?
Upgrade to react-native-root-siblings#4.x
Then
import { setSiblingWrapper } from 'react-native-root-siblings';
import { Provider } from 'react-redux';
const store = xxx;// get your redux store here
// call this before using any root-siblings related code
setSiblingWrapper(sibling => (
<Provider store={store}>{sibling}</Provider>
));

Provider store can't be found in the context or props of Connect()

I keep getting a "could not find 'store' in either the context or props of "Connect(SalesOrderList)" error. I provide the provider store as you can see in my App.js file below. Not sure what the error is coming from. This is my first attempt at integrating redux with react-native and I'm having trouble hooking everything together. Anyone with experience please help. Thanks =)
My App.js
const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(Thunk));
console.log(store.getState());
export default class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
state = { loggedIn: null};
renderView() {
switch (this.state.loggedIn) {
case true:
return <AppNavigator />
case false:
return <Login />;
default:
return <AppNavigator />;
}
}
render() {
return (
<Provider store={store}>
<View style={styles.container}>
{this.renderView()}
</View>
</Provider>
);
}
}
Index.ios.js
import {
AppRegistry,
} from 'react-native';
import App from './src/components/App';
import { StackNavigator } from 'react-navigation';
import SalesOrderList from './src/components/SalesOrderList';
import SalesOrderItem from './src/components/SalesOrderItem';
const AppNavigator = StackNavigator({
SalesOrderList : { screen: SalesOrderList },
SalesOrderItem : { screen: SalesOrderItem }
});
AppRegistry.registerComponent('issicrm', () => AppNavigator);
export default AppNavigator;
So you are registering the AppNavigator component in your index.ios.js:
AppRegistry.registerComponent('issicrm', () => AppNavigator);.
If you look closely, the AppNavigator component you are linking is just a StackNavigator component, it is not connected to your Redux store at all, this why you are seeing the error within the SalesOrderList component's connect call.
I think you probably wanted to register your App component in your index.ios.js, because you imported it but have never used it.

Navigation in React Router v4 Native Not Changing Scenes

I'm using React Router for a React Native app. Not sure what I'm missing here but I install react-router-native and require the history package, and setup a couple methods that just push a new route onto the stack but nothing happens. I console.log('clicked'); to check that it's firing and it is so not sure what's wrong.
import React, { Component } from 'react';
import { View } from 'react-native';
import Splash from './Splash';
import createHistory from 'history/createMemoryHistory';
const history = createHistory();
class SplashContainer extends Component {
goToLogin = () => {
history.push('/Login');
}
goToRegister = () => {
history.push('/SignUp');
}
render () {
console.log(history)
return (
<Splash
goToLogin={this.goToLogin}
goToRegister={this.goToRegister}
/>
);
}
}
export default SplashContainer;
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { Button } from 'native-base';
import { Link } from 'react-router-native';
import PropTypes from 'prop-types';
const Splash = (props) => {
console.log(props)
return (
<View style={styles.container}>
<Button light block onPress={props.goToLogin}>
<Text>Login</Text>
</Button>
<Button dark block bordered style={{marginTop: 10}} onPress={props.goToRegister}>
<Text>Register</Text>
</Button>
</View>
);
}
Splash.propTypes = {
goToLogin: PropTypes.func.isRequired,
goToRegister: PropTypes.func.isRequired
}
export default Splash;
I don't know your Router config, but your methods should be:
goToLogin = () => {
const { history } = this.props
history.push('/Login');
}
history will passed down via props of component inside Router's stack.