How do I change the theme of whole app in React-native? - javascript

I want to change the theme of the app(say background colors) by selecting from a list of colors. I've tried saving the state in a reducer and applying the state to a new page.
LoginView.js
import styles from "./styles";
class LoginView extends Component {
constructor(props) {
super(props);
this.state = {
primaryColor: theme.PRIMARY_COLOR
};
}
navigate = () => {
this.props.navigation.navigate("Home");
};
onPressGreen = () => {
theme.PRIMARY_COLOR = "green";
this.setState({ primaryColor: theme.PRIMARY_COLOR });
this.props.onPressGreenButton(theme.PRIMARY_COLOR);
};
onPressRed = () => {
theme.PRIMARY_COLOR = "red";
this.setState({ primaryColor: theme.PRIMARY_COLOR });
};
render() {
return (
<View
style={[styles.container, { backgroundColor: this.state.primaryColor }]}
>
<TouchableOpacity
style={styles.greenButton}
onPress={this.onPressGreen}
>
<Text>Green</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.redButton} onPress={this.onPressRed}>
<Text>Red</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.navigate}>
<Text>Home</Text>
</TouchableOpacity>
</View>
);
}
}
function mapDispatchToProps(dispatch) {
return {
onPressGreenButton: color => dispatch(loginActions.saveColor(color))
};
}
export default connect(
null,
mapDispatchToProps
)(LoginView);
HomeView.js
class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={[styles.container, { backgroundColor: this.props.color }]}>
<Text>Home</Text>
</View>
);
}
}
function mapStateToProps(state) {
return {
color: state.loginReducer.color //color saved in reducer
};
}
export default connect(
mapStateToProps,
null
)(HomeView);
In HomeView.js file instead of applying backgroundcolor in the way I did, how to include it inside styles.container. Is there any way to access function mapStateToProps inside styles.js I've imported. I've used redux to save the state permanently.

you can use redux outside of react component like styles.js
by store.getState() API in redux
but I think the best way to handle this is to create a wrapper component and apply them to it then use it everywhere you want
class MasterView extends Component {
render() {
return (
<View style={[styles.container, { backgroundColor: this.props.color }]}>
{this.props.children}
</View>
);
}
}
function mapStateToProps(state) {
return {
color: state.loginReducer.color //color saved in reducer
};
}
export default connect(
mapStateToProps,
null
)(MasterScreen);
then on each screen, you want you can use it for example
class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<MasterView>
<Text>Home</Text>
</MasterView>
);
}
}
another way is in styles.js
import store from '../store'
const containerColor=store.getState().loginReducer.color
const styles = StyleSheet.create({
container: {
backgroundColor:containerColor
},
}
export default styles

Related

Call function with setState from other component

I have the following setup:
import {getNewImage} from '...'
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
imageURL: 'www.test.com/new.jpg',
}
}
update = () => {
this.setState({
imageURL: 'www.test.com/updated.jpg',
})
}
render() {
return (
<View>
<Image
source={{ uri: this.state.imageURL }}
/>
</View>
);
}
}
import Class1 from '...'
export default class SecondClass extends Component {
render() {
return (
<TouchableOpacity onPress={() => new FirstClass().update()}>
<Class1></Class1>
</TouchableOpacity>
);
}
}
The problem is: it doesn't update the imageURL. I tried multiple things. Logging this inside of update gave back the right object. But trying to log the this.setState() gives an undefinded back.
I suppose by Class1 you mean FirstClass.
you should use the reference of the component using ref, not creating new instance of FirstClass class
checkout this code
export default class SecondClass extends Component {
private firstClass = null;
render() {
return (
<TouchableOpacity onPress={() => this.firstClass.update()}>
<Class1 ref={ref => this.firstClass = ref} />
</TouchableOpacity>
);
}
}
You can try to update state in Class1 similar to what do you want by the react reference.
But on my opinion this is non a good case and predictable behavior to change child component state from parent.
As another options you can add additional state to the SecondClass and pass it via props to child component. And inside Class1 in getDerivedStateFromProps based on that prop change state.
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
isNeedToUdpate: false,
imageURL: "www.test.com/new.jpg"
};
}
static getDerivedStateFromProps(props, state) {
if (props.isNeedToUdpate !== state.isNeedToUdpate) {
return {
isNeedToUdpate: props.isNeedToUdpate,
imageURL: "www.test.com/updated.jpg"
};
}
return null;
}
render() {
return (
<View>
<Image source={{ uri: this.state.imageURL }} />
</View>
);
}
}
export default class SecondClass extends Component {
constructor() {
super();
this.state = {
isNeedToUpdateClass1: false
};
}
render() {
return (
<TouchableOpacity
onPress={() => {
this.setState({ isNeedToUpdateClass1: true });
}}
>
<Class1 isNeedToUpdate={this.state.isNeedToUpdateClass1}></Class1>
</TouchableOpacity>
);
}
}

React native: passing image path as params through 2 components

i'm trying to pass an image path down to one component via react-navigation params and from that component to a third one as a prop.
This is a chat application and the structure of the components is:
ChatsMain.js --> (click on SingleChat, passing parameters when navigating) --> SingleConversation.js --> Message
I can pass 'name' as a parameter, but not the uri of an image, not even by assigning it to variables.
The name that i see on the SingleChat is passed without problems to the correspondent conversation, but when i pass the image i get the error:
'Failed prop type: Invalid prop source supplied to Image.'
I hope it's clear enough, i don't think it's the correct way to do this, but i am new to react native!
ChatsMain.js
const imageNames = {
juf: require('../assets/juf.png'),
meester: require('../assets/meester.png'),
groep: require('../assets/groep.png'),
onderwerpen: require('../assets/onderwerpen.png')
}
export default class ChatsMain extends React.Component {
render() {
return (
<SingleChat
image={imageNames.juf}
backgroundColor={yellow}
borderColor={blue}
name='Juf Elsa'
onPress={() => this.props.navigation.navigate('SingleConversation', {name: 'Juf Elsa', image: imageNames.juf})}
/>
)
SingleConversation.js
export default class SingleConversation extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('name', 'Chat'),
headerStyle: [titleBar, { backgroundColor: yellow }],
headerTitleStyle: [title, { color: blue }]
}
}
render() {
return (
<View style={chatMainContainer}>
<ScrollView style={chatsContent}>
<Message image={({navigation}) => navigation.getParam('image', 'image')} />
</ScrollView>
</View>
);
}
}
Message.js
export default class ReceivedFirst extends React.Component {
render(
){
return(
<View style={textWithPicture}>
<View style={[chatPhotoContainer, { borderColor: yellow }]}>
<Image source={this.props.image} style={{ width: 70, height: 70}} />
.....
.....
)
You can use this.props.navigation.state.params instead this.props.navigation.getParam
export default class SingleConversation extends React.Component {
constructor(props) {
super(props);
this.state = {
image: this.props.navigation.state.params.image
};
}
...
Message image={this.state.image} />
OR
You need to get a Navigation from the props.
render() {
const { navigation } = this.props
return (
<View style={chatMainContainer}>
<ScrollView style={chatsContent}>
<Message image={() => navigation.getParam('image', 'image')} />
</ScrollView>
</View>
);
}

React Native - Can't display the Data in other screen

What I want to achieve is display the datas in other screen or another component with React Redux. I'm using the latest version of React Redux and Redux.
My problem is every time I want to display the data, or when I press the button (TouchableOpacity) in EventCreator.js it doesn't display. I tried to check all of my codes but I don't see any typo or any wrong codes.
Feel free to ask for more of my codes. Thank you!
UPDATE: I tried all codes to my another new project, but with RNNV1. So it might be some new codes for RNNv2 that is related to React Redux to change. I prefer to all viewers to focus check my codes about RNNv2 connected to React Redux. Thank you!
These are my codes:
App.js, all my Registered Screens.
import {Navigation} from 'react-native-navigation';
import React from 'react';
//Screens
import AuthScreen from './src/screens/Auth/Auth';
import EventMap from './src/screens/Map/Map';
import EventCreator from './src/screens/EventCreator/EventCreator';
import EventHome from './src/screens/Home/Home';
import EventPushScreen from './src/screens/EventPushScreen/EventPushSc';
//Redux
import { Provider } from 'react-redux';
import configureStore from './src/store/configureStore';
const store = configureStore();
//Register Screens
Navigation.registerComponentWithRedux("Event.AuthScreen", () => AuthScreen);
Navigation.registerComponentWithRedux("Event.Map", () => EventMap);
Navigation.registerComponent("EventCreator", () => (props) => (
<Provider store={store}>
<EventCreator {...props}/>
</Provider>
), () => EventCreator);
Navigation.registerComponent("EventHome", () => (props) => (
<Provider store={store}>
<EventHome {...props}/>
</Provider>
), () => EventHome);
//Start A App
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: "Event.AuthScreen",
}
}],
options: {
topBar: {
title: {
text: 'Welcome',
alignment: 'center'
}
}
}
}
}
});
});
This is my 1st Screen where the TextInput and Button (TouchableOpacity) is.
EventCreator.js
import EventInput from '../../components/EventInput';
class EventCreator extends Component {
eventAddedHandler = (eventName) => {
this.props.onAddEvent(eventName);
};
render() {
return (
<View style={styles.container}>
<EventInput onEventAdded={this.eventAddedHandler}/>
</View>
);
}
};
const mapDispatchToProps = dispatch => {
return {
onAddEvent: (eventName) => dispatch(addEvent(eventName))
};
};
export default connect(null, mapDispatchToProps)(EventCreator);
EventInput.js, This is my Component that is connected to EventCreate.js
class EventInput extends Component {
state = {
eventName: ""
};
eventNameChangedHandler = (val) => {
this.setState({
eventName: val
});
};
eventSubmitHandler = () => {
if (this.state.eventName.trim() === "") {
return;
}
this.props.onEventAdded(this.state.eventName);
};
render () {
return (
<View style={styles.inputAndButtonContainer}>
<TextInput
placeholder="Create your event"
value={this.state.eventName}
onChangeText={this.eventNameChangedHandler}
style={styles.textInputContainer}
/>
<TouchableOpacity onPress={this.eventSubmitHandler}>
<View style={styles.buttonContainer}>
<Text style={{color: 'black'}}>Add</Text>
</View>
</TouchableOpacity>
</View>
);
}
};
Home.js, this is where I want to display may Data or the FlatList.
class Home extends Component {
eventSelectedHandler = key => {
const selEvent = this.props.events.find(event => {
return event.key === key;
});
Navigation.push("EventHomeStack", {
component: {
name: "EventPushScreen",
passProps: {
selectedEvent: selEvent
},
options: {
topBar: {
title: {
text: selEvent.name
}
}
}
}
});
};
render() {
return (
<View>
<EventList
events={this.props.events}
onItemSelected={this.eventSelectedHandler}
/>
</View>
);
}
};
const mapStateToProps = state => {
return {
events: state.events.events
};
};
export default connect(mapStateToProps)(Home);
EventList.js, another Component connected to Home.js
import ListItem from './ListItem';
const eventList = (props) => {
return (
<FlatList
style={styles.listContainer}
data={props.events}
renderItem={(info) => (
<ListItem
eventName={info.item.name}
eventImage={info.item.image}
onItemPressed={() => props.onItemSelected(info.item.key)}
/>
)}
/>
);
};
export default eventList;
listItem.js, another Component connected to EventList.js
const listItem = (props) => (
<TouchableOpacity onPress={props.onItemPressed}>
<View style={styles.listItem}>
<Image resizeMode="cover" source={props.eventImage} style={styles.eventImage}/>
<Text>
{props.eventName}
</Text>
</View>
</TouchableOpacity>
);
export default listItem;
events.js (reducer)
import {ADD_EVENT, DELETE_EVENT} from '../actions/actionTypes';
const initialState = {
events: []
};
const reducer = (state = initialState, action) => {
switch(action.type) {
case ADD_EVENT:
return {
...state,
events: state.events.concat({
key: `${Math.random()}`,
name: action.eventName,
image: {
uri: "https://c1.staticflickr.com/5/4096/4744241983_34023bf303_b.jpg"
}
})
};
case DELETE_EVENT:
return {
...state,
events: state.events.filter(event => {
return event.key !== action.eventKey;
})
};
default:
return state;
}
};
export default reducer;

React-navigation how to pass data down to navigator objects

Consider the following example:
import { AppRegistry } from "react-native";
import React, { Component } from "react";
import {
createSwitchNavigator,
createStackNavigator,
createBottomTabNavigator
} from "react-navigation";
import Icon from "react-native-vector-icons/Ionicons";
import { withNavigation } from "react-navigation";
import {
View,
Text,
Platform,
TouchableNativeFeedback,
TouchableOpacity,
StyleSheet
} from "react-native";
const Touchable =
Platform.OS === "android" ? TouchableNativeFeedback : TouchableOpacity;
class ListComponent extends Component {
static navigationOptions = {
title: "List"
};
handleGo = () => {
this.props.navigation.navigate("Board");
};
render = () => {
//??? How to get myData ???
return (
<View>
<Text>HELLO LIST!!!!</Text>
<Touchable onPress={this.handleGo}>
<Text>GO TO BOARD</Text>
</Touchable>
<Text>{myData}</Text>
</View>
);
};
}
const List = withNavigation(ListComponent);
class BoardComponent extends Component {
static navigationOptions = {
title: "Board"
};
//??? How to get myData ???
render = () => {
return (
<View>
<Text>HELLO BOARD!!!!</Text>
<Text>{myData}</Text>
</View>
);
};
}
const Board = BoardComponent;
class PanelComponent extends Component {
static navigationOptions = {
title: "Panel"
};
//??? How to get myData ???
render = () => {
return (
<View>
<Text>HELLO PANEL!!!!</Text>
<Text>{myData}</Text>
</View>
);
};
}
const Panel = PanelComponent;
const Test0 = createStackNavigator(
{
List: {
screen: List
},
Board: {
screen: Board
}
},
{
navigationOptions: {
headerStyle: {
backgroundColor: "grey"
},
headerTintColor: "blue",
headerTitleStyle: {
fontWeight: "bold"
}
}
}
);
const Test1 = createStackNavigator(
{
Panel: {
screen: Panel
}
},
{
navigationOptions: {
headerStyle: {
backgroundColor: "grey"
},
headerTintColor: "blue",
headerTitleStyle: {
fontWeight: "bold"
}
}
}
);
const LoggedInNavigator = createBottomTabNavigator(
{
Test0: {
screen: Test0,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Icon
name={"ios-list-box-outline"}
size={24}
color={"#cdcdcd"}
/>
)
}
},
Test1: {
screen: Test1,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Icon
name={"ios-construct-outline"}
size={24}
color={"#cdcdcd"}
/>
)
}
}
},
{
tabBarOptions: {
showLabel: false,
activeTintColor: "white",
activeBackgroundColor: "blue",
style: {
backgroundColor: "grey"
}
},
animationEnabled: true,
swipeEnabled: true,
initialRouteName: "Test1"
}
);
export const createRootNavigator = () => {
let myData = getMyDataFromDB(); // <=== How can I pass myData down to Panel/Board/List
return createSwitchNavigator(
{
LoggedIn: {
screen: LoggedInNavigator
}
},
{
initialRouteName: "LoggedIn"
}
);
};
class App extends Component {
render = () => {
const Layout = createRootNavigator();
return <Layout />;
};
}
export default App;
AppRegistry.registerComponent("app", () => App);
How can I pass down myData through all the routes to the end components?
This is a skeleton of a much bigger application where I query for data on the root navigator (createRootNavigator) that must be served to some components down the navigation tree.
You could make a Higher Order Component or a wrapper component that handles the fetching of your data in componentDidMount and wrap all your routes with it
// HOC component example
import React from 'react';
const withMyData = (WrappedComponent) => {
class myEnchancedComponent extends React.Component {
state ={ myData: null }
componentDidMount() {
let myData = getMyDataFromDB();
// set the state after you get the data
this.setState({myData})
}
render() {
const {myData} = this.state;
return (
<WrappedComponent {...this.props} myData={myData}
/>
);
}
}
return myEnchancedComponent;
};
export default withMyData;
// example of how to use it
const List = withMyData(withNavigation(ListComponent));
The are other ways you can solve this too. You could use redux combined with redux-persist to store your data and make them available even offline.
He you can use redux for that, or another way is using local storage (react-native-local-storage), with this lib you can storage any data and in any moment or page you can access it.

React Native: Send component state to other component using Tab Navigator

I have a component to add todos AddTodo which works fine and update the state with my added todos and I have a component TodoItems to display the todos in <FlatList/>. I'm using React Native Tab Navigator to switch between components but I'm not sure how to send the state this.state.todos from AddTodo component to TodoItems component.
I have been researching but couldn't find a solution in Tab Navigator but there are plenty of solutions for Stack Navigator.
Component AddTodo
export default class AddTodo extends Component {
constructor(props) {
super(props);
this.state = {
todoText: null,
todos: []
}
}
onAdd = () => {
if (this.state.todoText) {
this.state.todos.push({'todoItem': this.state.todoText});
this.setState({todos: this.state.todos});
}
}
render() {
return(
<View>
<TextInput onChangeText={(text) => {
this.setState({todoText: text});
}} />
<TouchableOpacity onPress={() => {
this.onAdd;
}}>
</View>
);
}
}
Component TodoItems
export default class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {
todosList: []
}
}
render() {
return(
<View>
<FlatList
data={this.state.todosList}
renderItem={(item, index) => {
<Text>{item.todoItem}</Text>
}}
/>
</View>
);
}
}
Component Tabs
import {TabNavigator} from 'react-navigation';
import AddTodo from "./AddTodo";
import TodoItems from "./TodoItems";
var myTabs = TabNavigator(
{
'AddTodo':{screen: AddTodo,},
'TodoItems':{screen: TodoItems, },
},
{
tabBarPosition: 'top',
swipeEnabled: false,
tabBarOptions: {
labelStyle:{
fontSize: 13,
fontWeight: 'bold',
},
indicatorStyle: {
borderBottomColor: '#003E7D',
borderBottomWidth: 2,
},
style:{
backgroundColor: '#F30076',
elevation: 0,
},
},
});
export default myTabs;
Well I think you have two options:
You can use Redux which allows you to globalise your state objects so you can use them all over your app, but it can be rather complicated
https://redux.js.org/
Or you can render TodoItems from within AddTodo:
render() {
return(
<View>
<TextInput onChangeText={(text) => {
this.setState({todoText: text});
}} />
<TouchableOpacity onPress={() => {
this.onAdd;
}}>
</View>
<TodoItems data={this.state.todos} />
);
}
Then you can access that data from within TodoItems:
Hope this helps!

Categories