Nextjs with Styled components not passing theme - react-native

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};

Related

Can not use global styles in styled-components

I am trying to setup global styles in react-native.
I have imported
import {injectGlobal} from 'styled-components';
and have
class XoxoContainer extends Component {
render() {
return <Xoxo {...this.props} />
}
}
injectGlobal`
font-family: '20'
`;
But I keep getting styledComponents.injectGlobals is not a function. in the console.
That function is not part of the library on react-native according to this Github issue. That's why it keeps saying that it's not a function, because it can't find it.
create a styles.js file like this:
import React, {Component} from 'react';
import {
Platform,
StyleSheet
} from 'react-native';
export const styles = StyleSheet.create({
view_flex_one_white: {
flex: 1,
backgroundColor: white
}});
and use this anywhere in your app with import
import {styles} from "...link to file/styles";
render(){
return(
<View style={styles.view_flex_one_white}>
</View>
)
}
Create a js file with this pattern:
'use strict';
var React = require('react-native');
var myStyle = React.StyleSheet.create({
style1: { },
style2: { }
)}
module.exports = myStyle;
your component js use require to use that style sheet
var customStyle = require('../the/path/to/commonStyleSheet');
Use now like this:
<View style = {customStyle .style1} />

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)

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

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/>

Using Redux with React Native

I am working on a react-native app created using: create-react-native-app
As far as I can tell the most top level component is inside the App.js file and I have imported the Provider there and wrapped it around the Top Level Component but I am still getting the following errors for some reason:
ExceptionsManager.js:65 Invariant Violation: Could not find "store" in
either the context or props of "Connect(App)". Either wrap the root
component in a , or explicitly pass "store" as a prop to
"Connect(App)".
What am I doing wrong?
Here is my code:
import React from 'react';
import { StyleSheet, Text, View, FlatList, TextInput, StatusBar, Button,TouchableOpacity } from 'react-native';
import { TabNavigator, StackNavigator } from 'react-navigation';
import { Constants } from 'expo'
import { purple, white } from './utils/colors'
import { saveDeckTitle, getDecks, getDeck, addCardToDeck } from './utils/api'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import reducer from './reducers'
import { connect } from 'react-redux'
import Decks from './components/Decks'
import NewQuestionView from './components/NewQuestionView'
import NewDeck from './components/NewDeck'
const R = require('ramda')
const store = createStore(reducer)
const DecksETC = StackNavigator({
Decks: {
screen: Decks
},
NewDeck: {
screen: NewDeck
},
NewQuestionView: {
screen: NewQuestionView
}
})
const NewDeckETC = StackNavigator({
NewDeck: {
screen: NewDeck
},
Decks: {
screen: Decks
},
NewQuestionView: {
screen: NewQuestionView
}
})
const Tabs = TabNavigator({
DecksETC: {
screen: DecksETC
},
NewDeckETC: {
screen: NewDeckETC
}
});
class App extends React.Component {
render() {
console.log('R', R)
return (
<Provider store={store}>
<Tabs />
</Provider>
// <Tabs />
);
}
}
const styles = StyleSheet.create({
container: {
paddingTop: 23,
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
input: {
margin: 15,
height: 40,
borderColor: '#7a42f4',
borderWidth: 1
},
});
function mapStateToProps(state) {
console.log('mapStateToProps')
debugger
return {
'sample': 'sample'
}
}
export default connect(mapStateToProps)(App)
The problem is that App component does not know anything about the store because the Provider component is what brings the Redux store into its children components. The App component itself does not receive a reference to the store, so when you try to connect, the store is not found.
Well I see you have use connect function for the root component directly, which is a pattern I never see before. Let's try the normal way, which is that you will create a Root component and just use it inside Provider. Then you will pass child components into that Root component.
You will then separate each child component into a new file. In each file, you will use connect() to pass redux store into that component. That's the common pattern I see in many many projects. And this pattern will help you avoid confusing situation like above!
Well the function connect requires two functions mapStateToProps and mapDispatchToProps as arguments
i.e. export default connect(mapStateToProps,mapDispatchToProps)(App)
if you do not have a mapDispatchToProps then simply pass null.
i.e. export default connect(mapStateToProps,null)(App)

Why isn't my component's state computed property updating after Redux store updates its value?

Why isn't my component's state computed property updating after Redux store updates its value?
I am using some helper methods to grab the sub-store via AppStore.getState().ApiStore for my isAuthenticated state property. It seems like when this store value updates, the component state value does not update. Does React Native not watch for store updates in computed component state properties?
My component looks like the below:
// Vendor
import React, { Component } from 'react'
import { AppRegistry, Text, View, StyleSheet, TextInput, Button} from 'react-native'
import AppStore from './Stores/AppStore'
import StoreHelpers from './Stores/StoreHelpers'
// Custom
import Login from './Components/Login/Login'
import Api from './Services/Api.js'
// Styles
const styles = StyleSheet.create({
mainView: {
flex: 1,
padding: 20,
marginTop: 30,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#3b5998',
},
});
// Main App Component
export default class Main extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
isAuthenticated: !StoreHelpers.getApiStore().userBalanceResponse.error // Computed property from store
}
// Enable this for debugging
console.log(this.state)
AppStore.subscribe(() => {
console.log(AppStore.getState())
})
}
render() {
return (
<View style={styles.mainView}>
<Login />
</View>
)
}
}
// skip this line if using Create React Native App
// AppRegistry.registerComponent('AwesomeProject', () => Main);
You can't see it because your component is not subscribed to the store. Anything to do with store is the job of redux and NOT React Native. So if you wrap your component inside react-redux connect and pass in mapStateToProps to it you should get the right computed value.
// ... rest of imports
import { connect } from 'react-redux';
// Main App Component
class Main extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
isAuthenticated: this.props.isAuthenticated,
}
// ... rest of code
}
// ... rest of code
}
const mapStateToProps = (store) => ({
isAuthenticated: !userBalanceResponse: store.userBalanceResponse.error,
});
export default connect(mapStateToProps, null)(Main);
To make it work, make sure that you set up redux store properly. Wrap your root component within a Provider component and pass in store into it. Suppose your root component is called App, then it would look something like the following:
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import Main from 'path-to-main/Main';
// we will pass this store to the Provider
const store = createStore(
reducer,
// ... middlewares etc this is optional
);
export default class App extends Component {
render() {
return(
<Provider store={store}>
<Main />
</Provider>
)
}
}