Wizard and WizardPage
I am trying to build a generic Wizard and WizardPages to be generic a reusable across my app. I think the Wizard component as the one in charge of managing the state, and the WizardPage that is going to work as a wrapper, and will render Back, Cancel and Next buttons. It's suppose that the Next button (maybe the Back too) should dispatch an redux action. This action, could be different depending on the component being wrapped. This is what i have (not complete version):
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import withWizardLayout from '../../hoc/WithWizardLayout';
class Wizard extends Component {
constructor(props) {
super(props);
this.state = {
page: 0,
};
}
nextPage = () => {
this.setState({ page: this.state.page + 1 });
};
previousPage = () => {
this.setState({ page: this.state.page - 1 });
};
render() {
const { wizardPages } = this.props;
const { page } = this.state;
const wizardItem = wizardPages[page];
const nextPage = this.nextPage;
const previousPage = this.previousPage;
const ComponentWrapped = withWizardLayout(wizardItem.ComponentWrapped, nextPage, previousPage);
return (
<ComponentWrapped />
);
}
}
export default Wizard;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { compose } from 'react-apollo';
import { connect } from 'react-redux';
import { Flex, Box } from 'reflexbox';
import { BlueRoundButton, GreyRoundButton } from '../components/Button';
export default function withWizardLayout(ComponentDependent, nextPageCallBack, previousPageCallback) {
class WizardPage extends Component {
previousPage = () => {
};
nextPage = () => {
this.props.test();
};
render() {
const { ComponentWrapped, isFirstPage, isLastPage } = ComponentDependent;
return (
<Flex column>
<ComponentWrapped
isLastPage={isLastPage}
isFirstPage={isFirstPage}
>
<Flex justify='flex-end'>
<Flex mr='auto' w={0.1}>
<GreyRoundButton style={{ fontWeight: '500', position: 'relative', top: '10%' }}>Back</GreyRoundButton>
</Flex>
<Flex w={0.08} mr={2} align='center' justify='center' style={{ textAlign: 'center' }}>
<span>Cancel</span>
</Flex>
<Flex w={0.15} justify='center'>
<BlueRoundButton onClick={this.nextPage} style={{ position: 'relative', top: '10%', fontWeight: '500' }}>Continue</BlueRoundButton>
</Flex>
</Flex>
</ComponentWrapped>
</Flex>
);
}
}
CustomersContainer
import { compose } from 'react-apollo';
import { connect } from 'react-redux';
import CustomerImport from '../components/Settings/CustomerImport';
import { withWizardLayout } from '../hoc/WithWizardLayout';
const mapStateToProps = null;
const mapDispatchToProps = dispatch => (
{
test: () => (dispatch(console.log("hi"))),
}
);
export default compose(connect(null, mapDispatchToProps))(CustomerImport);
Connected Component
import React, { Component } from 'react';
import styled from 'styled-components';
import {
SettingBlock,
NotifyBlock,
SuccessMessage,
ErrorMessage,
Instructions,
} from './styles';
import ExcelUploader from '../ExcelUploader';
const ProgressBar = styled.div`
width: 0;
height: 30px;
background-color: Green;
`;
const ExcelWrapper = styled.div`
margin-bottom: 4rem;
`;
export default class CustomerImport extends Component {
constructor(props) {
super(props);
this.state = {
progress: 0,
notify: {
success: {
message: '',
active: false,
},
error: {
message: '',
active: false,
},
},
};
}
render() {
const { success, error } = this.state.notify;
const ButtonsWrapper = this.props.children;
return (
<div>
<NotifyBlock>
<SuccessMessage className={success.active ? 'active' : null}>{success.message}</SuccessMessage>
<ErrorMessage className={error.active ? 'active' : null}>{error.message}</ErrorMessage>
</NotifyBlock>
<SettingBlock>
<h3>
Import your customers
</h3>
<ProgressBar style={{ width: `${this.state.progress}%` }} />
<Instructions>
Select an Excel file containing your customer information.
Need a template? Grab one here.
</Instructions>
<ExcelWrapper>
<ExcelUploader />
</ExcelWrapper>
{ButtonsWrapper}
</SettingBlock>
</div>
);
}
}
This is how i am supposed to render the Generic Wizard, we can have X wizardPages:
const wizardPages = [
{
ComponentWrapped: CustomersContainer,
isFirstPage: false,
isLastPage: false,
},
];
<Wizard wizardPages={wizardPages} />
The problem with this approach, is that i want on the onClick of the buttons in withWizardLayout:
1) Execute the callback on the Father (that's possible, i am passing as prop the handler)
2) Call the dispatch action received of the container. (i am not able not access the dispatched action, in this case, this.props.test)
How can i refactor this, to think a generic way to handle this? I think another ways to refactor this (having different withWizardLayout functions, but i am having render problems on the console).
Maybe i didn't architect this in the best way.
Help!
Related
Currently, in my react app, I have a parent component which is housing two child components (TrendsComonent and BaselineComponent) and they load successfully. The parent component also has a dropdown component loaded.
What i want to do is set a default Component to load in the parent initially, but I want to map each child component to the dropdown options.
For instance, when the parent component is visited I would like to have TrendsComponent load initially as the default but have it tied to the trends dropdown option, as well as have the BaselineComponent mapped to the baseline option of the dropdown.
Basically, I just want to load components based on the dropdown option as opposed to all at once
TrendComponent.js
import React, { Component } from "react";
import Chart from "react-apexcharts";
import { StyleSheet, css } from 'aphrodite/no-important';
const styles = StyleSheet.create({
TrendsComponent: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
textAlign: 'center'
},
TrendsTitle: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems:'center'
}
});
class TrendsComponent extends Component {
render() {
return (
<div>
<div className={css(styles.TrendsTitle)}>
Net Calories
</div>
<div className={css(styles.TrendsComponent)}>
<div className={css(styles.TrendsComponent)}>
<div className="mixed-chart">
<Chart
/>
</div>
</div>
</div>
</div>
);
}
}
export default TrendsComponent;
BaselineComponent.js
import React, { Component } from "react";
import Chart from "react-apexcharts";
import { StyleSheet, css } from 'aphrodite/no-important';
const styles = StyleSheet.create({
TrendsComponent: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
textAlign: 'center'
},
TrendsTitle: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems:'center'
}
});
class BaselineComponent extends Component {
render() {
return (
<div>
<div className={css(styles.TrendsTitle)}>
Net Calories
</div>
<div >
<div >
<div className="mixed-chart">
<Chart
/>
</div>
</div>
</div>
</div>
);
}
}
export default BaselineComponent;
Then I have the parent component that currently holds that component as well as the dropdown
trendparent.js
import React, { Component } from "react";
import Chart from "react-apexcharts";
import { StyleSheet, css } from 'aphrodite/no-important';
import TrendsComponent from './Trendscomponent';
import BaselineComponent from './BaselineComponent';
import TrendDropdownComponent from './TrendDropdownComponent';
class trendparent extends Component {
render() {
return (
<div>
<div className={css(styles.TrendsTitle)}>
Net Calories
</div>
<div>
<div>
<div>
<TrendsComponent />
<BaselineComponent />
</div>
</div>
</div>
<div style={{height:50}}>
</div>
<div>
<TrendDropdownComponent />
</div>
</div>
);
}
}
export default trendparent;
dropdown.js
import React, { Component } from "react";
import { makeStyles } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormHelperText from '#material-ui/core/FormHelperText';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
const useStyles = makeStyles(theme => ({
formControl: {
margin: theme.spacing(1),
minWidth: 220,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
export default function SimpleSelect() {
const classes = useStyles();
const [age, setAge] = React.useState('');
const inputLabel = React.useRef(null);
const handleChange = event => {
setAge(event.target.value);
};
return (
<div>
<FormControl className={classes.formControl}>
<InputLabel id="demo-simple-select-label">Calories</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={age}
onChange={handleChange}
>
<MenuItem value={10}>Trend</MenuItem>
<MenuItem value={20}>Baseline</MenuItem>
</Select>
</FormControl>
</div>
);
}
The question here is what you really mean by "load".
1) If what you mean by "load" means actually to only render a component that was statically imported (already declared in the beginning of the file), then all you have to do is set some default value on the state i.e. :
state = {
renderComponentX: false
}
And then on the dropdown change method change the state to true:
setState({"renderComponentX":true})
And inside render have a condition as :
{this.state.renderComponentX && <ComponentX />}
2) If on the other hand what you want is really to dynamically load components then it's a bit more complicated :
You need to create a component that Asynchronously loads other components.
I normally create an array of randomKeys on the state of the component, at the constructor:
constructor(props) {
super(props);
// Dynamic Key Generation for dynamic view loading
let randomKeys = [];
while(randomKeys.length < 10){
let y = Math.random()*10000;
if(randomKeys.indexOf(y) === -1) randomKeys.push(y);
}
this.state = {
randomKeys
};
}
So that each of the new imported components will have a different Key.
In this case it's hardcoded to 0 but if you want to make this inside an iterator, you would have to create a variable to act as a counter to keep updating the index such as randomKeys[i] where i needs to grow from 0 to the length of components you want to import. Also you need to make sure to generate enough keys in the constructor; this one is only generating 10, because this was for manual import, not within an iterator.
<AsyncComponent key={this.state.randomKeys[0]} getComponent={() => import('../Login/Login.js')} />
and my AsyncComponent looks like this :
import React from 'react';
import PropTypes from 'prop-types';
export default class AsyncComponent extends React.Component {
state = {
AsyncModule: null,
};
componentDidMount() {
let that = this;
this.unmounted = false;
this.props.getComponent()
.then(module => {
console.log("AsyncComponent loaded module:",module);
return module.default;
})
.then(AsyncModule => {
if(that.unmounted!==true) {
that.setState({AsyncModule})
}
});
}
componentDidUpdate() {
}
componentWillUnmount() {
this.unmounted = true;
}
render() {
const {loader, ...childProps} = this.props;
const {AsyncModule} = this.state;
if(AsyncModule) {
return (<AsyncModule {...childProps} />)
}
if(loader) {
console.log('loader = ',loader);
return <div>Loading...</div>;
}
return null;
}
}
AsyncComponent.propTypes = {
getComponent: PropTypes.func,
loader: PropTypes.element
};
I'm trying to replicate this example. It won't work somehow. From components higher in the hierarchy to lower ones, I have three files:
index.js:
import React, { Component } from "react"
import { SelectableGroup } from "react-selectable-fast"
import List from "../components/List"
const items = [
{
player: "Dirk Nowitzki",
year: 1999,
},
{
player: "Magic Johnson",
year: 1980,
},
{
player: "Michael Jordan",
year: 1990,
},
]
const IndexPage = () => (
<SelectableGroup
className="main"
clickClassName="tick"
// enableDeselect
// tolerance={this.state.tolerance}
// globalMouse={this.state.isGlobal}
// allowClickWithoutSelected={false}
// duringSelection={this.handleSelecting}
duringSelection={() => {
console.log("DURING")
}}
// onSelectionClear={this.handleSelectionClear}
onSelectionFinish={() => {
console.log("FINISH")
}}
>
<List items={items} />
</SelectableGroup>
)
export default IndexPage
List.js
import React, { Component } from "react"
import SelectableComponent from "./Film"
export default class List extends Component {
render() {
console.log(this.props.items)
return (
<>
{this.props.items.map((item, i) => (
<SelectableComponent key={i} player={item.player} year={item.year} />
))}
</>
)
}
}
Player.js
import React from "react"
import { createSelectable } from "react-selectable-fast"
const Player = ({ selectableRef, selected, selecting }) => (
<div
ref={selectableRef}
style={{
border: "1px solid blue",
width: "300px",
height: "300px",
float: "left",
}}
className="tick"
>
{console.log(selected)}
{console.log(selecting)}
</div>
)
export default createSelectable(Player)
It doesn't throw any errors, but the console.log in Player.js are always undefined. OnSelectionFinish and DuringSelection work fine, at least the console.log are coming through.
The variable names need to be changed they are isSelected and isSelecting in your Player.js
A good rule of thumb is to print your whole props object when debugging.
Further code can be found on the library's github page.
import React from 'react'
import { TSelectableItemProps, createSelectable } from 'react-selectable-fast'
class SomeComponent extends Component<TSelectableItemProps> {
render() {
const { selectableRef, isSelected, isSelecting } = this.props
return <div ref={selectableRef}>...</div>
}
}
export default createSelectable(SomeComponent)
I have 2 root components in react360.I have the main panel with information and my products and i have my 3d model.I want to communicate one another.Μore specifically I just want to click on the product then the color from my 3d model changes.At this time, my 3d model has value from the store I have originally defined, but while the price is changing, it is not renewed in the 3d model.for example when the application starts the original color of my model is blue but when I click on the first item it does not change to red. Wrere is the problem?????before
after
client.js
import { ReactInstance } from "react-360-web";
import { Location } from "react-360-web";
function init(bundle, parent, options = {}) {
const r360 = new ReactInstance(bundle, parent, {
// Add custom options here
fullScreen: true,
...options
});
// Render your app content to the default cylinder surface
r360.renderToSurface(
r360.createRoot("Center", {
/* initial props */
}),
r360.getDefaultSurface()
);
r360.renderToLocation(
r360.createRoot("React3DView"),
r360.getDefaultLocation()
);
// Load the initial environment
r360.compositor.setBackground(r360.getAssetURL("360_world.jpg"));
}
window.React360 = { init };
index.js
import { AppRegistry } from "react-360";
import Center from "./components/center";
import React3DView from "./components/obj";
AppRegistry.registerComponent("Center", () => Center);
AppRegistry.registerComponent("React3DView", () => React3DView);
reducer
initialState = {
data: [
{ id: 1, value: "MILEPTY.png" },
{ id: 2, value: "cleveland.png" },
{ id: 3, value: "phila.png" },
{ id: 4, value: "raptors.png" },
{ id: 5, value: "rockets.png" }
],
currentinfo: "Hello.Press click on T-shirt to show information",
currentcolor: "blue"
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "INFO":
if (action.key === 1) {
return {
...state,
currentinfo: "Milwaukee bucks",
currentcolor: "red"
};
}
if (action.key === 2) {
return {
...state,
currentinfo: "Cleveland Cavaliers",
currentcolor: "green"
};
}
if (action.key === 3) {
return { ...state, currentinfo: "Philadelphia 76xers" };
}
if (action.key === 4) {
return { ...state, currentinfo: "Toronto Raptors" };
}
if (action.key === 5) {
return { ...state, currentinfo: "Huston Rockets" };
}
default:
return state;
}
};
export default reducer;
centerPanel
import React from "react";
import { AppRegistry, StyleSheet, Text, View, Image, asset } from "react-360";
import Products from "./products";
import { connect } from "react-redux";
class CenterPanel extends React.Component {
render() {
return (
<View style={styles.panel}>
<View style={{ flex: 1, flexDirection: "row" }}>
<View
style={{
width: 250,
height: 600
}}
>
<Text style={{ marginTop: "100" }}>{this.props.currentinfo}</Text>
</View>
<View
style={{
width: 1000,
height: 600,
backgroundColor: "green"
}}
>
<View style={{ flex: 1, flexDirection: "row" }}>
{this.props.data.map(element => (
<Products
key={element.id}
value={element.value}
id={element.id}
/>
))}
</View>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
panel: {
// Fill the entire surface
width: 1000,
height: 600,
backgroundColor: "rgba(255, 255, 255, 0.4)"
}
});
const mapStateToProps = state => {
return {
data: state.data,
currentinfo: state.currentinfo
};
};
export default connect(mapStateToProps)(CenterPanel);
products
import React from "react";
import { AppRegistry, StyleSheet, Text, View, Image, asset } from "react-360";
import { connect } from "react-redux";
class Products extends React.Component {
state = {
img: this.props.value
};
render() {
return (
<View
style={styles.panelimages}
onInput={() => this.props.onText(this.props.id)}
>
<Image style={styles.images} source={asset(this.state.img)} />
</View>
);
}
}
const styles = StyleSheet.create({
panelimages: {
width: 150,
height: 150,
marginTop: 200,
backgroundColor: "white"
},
images: {
width: 150,
height: 150
}
});
const mapDispatchToProps = dispatch => {
return {
onText: id => dispatch({ type: "INFO", key: id })
};
};
export default connect(
null,
mapDispatchToProps
)(Products);
center
import React from "react";
import { AppRegistry, StyleSheet, Text, View, Image, asset } from "react-360";
import { createStore } from "redux";
import { Provider } from "react-redux";
import reducer from "../store/reducer";
import CenterPanel from "./centerPanel";
// const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
const store = createStore(reducer);
export default class Center extends React.Component {
render() {
return (
<Provider store={store}>
<CenterPanel />
</Provider>
);
}
}
objpanel
import React from "react";
import {
asset,
AppRegistry,
StyleSheet,
Text,
View,
VrButton,
Image
} from "react-360";
import Entity from "Entity";
import { connect } from "react-redux";
class Object3d extends React.Component {
render() {
return (
<View>
<Entity
source={{ obj: asset("t-shirt.obj") }}
style={{
transform: [{ translate: [-3.5, -3.5, -2.8] }],
color: this.props.currentcolor -------->here is problem
}}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
currentcolor: state.currentcolor
};
};
export default connect(mapStateToProps)(Object3d);
obj
import React from "react";
import { AppRegistry, StyleSheet, Text, View, Image, asset } from "react-360";
import { createStore } from "redux";
import { Provider } from "react-redux";
import reducer from "../store/reducer";
import Object3d from "./objpanel";
// const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
const store = createStore(reducer);
export default class React3DView extends React.Component {
render() {
return (
<Provider store={store}>
<Object3d />
</Provider>
);
}
}
I've tried to do this with redux but in the end I had more problems implementing it than it was worth. In order to implement something like that you would need to follow the code that is described here:
React 360 multi panel example
Additionally, I've implemented what you are trying to do without redux. You can look at the code in this repository and also view the production link here. Its modeled after the react 360 code.
CryptoDashboardVR repo
CryptodashboardVR Multipanel synchronization
Finally, If you still need help, check out my course on React 360. I explain the concepts used. Udemy react 360.
Hope that helps. For now, I would abandon the redux approach until maybe 2.0 comes out.
I have to update some info in a plugin. What I do is:
app.js:
import React, { Component } from 'react';
import CesiumGlobe from "./cesium/CesiumGlobe";
class App extends Component {
constructor(props){
super(props);
this.state = {
pin : [],
};
}
componentDidMount() {
let fetchData = () => {
let url= "localhost:3001/pin"
fetch(url)
.then(resp=>resp.json())
.then(data =>{
this.setState((state)=>{
return { pin:data }
})
});
}
fetchData()
this.update = setInterval(fetchData, 2000)
}
render() {
const containerStyle = {
width: '100%',
height: '100%',
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'fixed',
};
let icons = [];
this.state.pin.forEach(function(element) {
icons.push(element);
});
return (
<div style={containerStyle}>
<CesiumGlobe icons={icons} />
<div style={{position : "fixed", top : 0}}>
<div style={{color : "white", fontSize: 40, }}>
Text Over the Globe
</div>
<button style={{fontSize : 40}} onClick={this.handleFlyToClicked}>
Jump Camera Location
</button>
</div>
</div>
);
}
}
export default App;
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import "cesium/Source/Widgets/widgets.css";
import buildModuleUrl from "cesium/Source/Core/buildModuleUrl";
buildModuleUrl.setBaseUrl('./cesium/');
function tick() {
ReactDOM.render(
<App />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
My intention was that this should load the contents every second of "pin.json" and pass it to the state updating it every second.
The first time I launch the application it works fine, but if I make changes to the json, the changes are completely ignored...
I cannot find out what i'm doing wrong, it seems i'm loosing the elephant in the room, but all my researches gives to me nothing.
I don't understand how I'm getting this error (pic below). In my LoginForm.js file, the onEmailChange(text) is giving me an unresolved function or method call to onEmailChange() error when I hover over it in my WebStorm IDE. In my index.js file, no error is being thrown anywhere.
I've looked around SO for this issue but it doesn't fully pertain to my problem.
I've tried File > Invalidate Caches/Restart but that didn't work.
Here's App.js:
import React, { Component } from 'react';
import {StyleSheet} from 'react-native';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import firebase from 'firebase';
import reducers from './reducers';
import LoginForm from './components/common/LoginForm';
class App extends Component {
render() {
return(
<Provider style={styles.c} store={createStore(reducers)}>
<LoginForm/>
</Provider>
);
}
}
const styles = StyleSheet.create({
c: {
flex: 1
}
});
export default App;
Here's LoginForm.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {emailChanged} from 'TorusTeensApp/src/actions';
import {Text, StyleSheet, KeyboardAvoidingView, TextInput, TouchableOpacity} from 'react-native';
class LoginForm extends Component {
render() {
onEmailChange(text)
{
this.props.emailChanged(text);
}
return(
<KeyboardAvoidingView style={styles.container}>
<TextInput
style={styles.userInput}
onsubmitediting={() => this.passwordInput.focus()}
returnKeyType={"next"}
placeholder={"Email"}
label={"Email"}
keyboardType={"email-address"}
autoCorrect={false}
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
<TextInput
style={styles.userInput}
ref={(userInput) => this.passwordInput = userInput}
returnKeyType={"go"}
placeholder={"Password"}
label={"Password"}
secureTextEntry
/>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Create Account</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 20 // creates a gap from the bottom
},
userInput: {
marginBottom: 20,
backgroundColor: '#9b42f4',
height: 40
},
buttonContainer: {
backgroundColor: '#41bbf4',
paddingVertical: 10,
marginBottom: 20
},
buttonText: {
textAlign: 'center',
color: '#FFFFFF'
}
});
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
export default connect(mapStateToProps, null, {emailChanged}) (LoginForm);
Here's index.js:
import {EMAIL_CHANGED} from './types';
export const emailChanged = (text) => {
return {
type: 'EMAIL_CHANGED',
payload: text
};
};
export default emailChanged();
Your connect is miswired
connect(mapStateToProps, null, {emailChanged}) (LoginForm);
It should be something like:
connect(mapStateToProps,
(dispatch) => ({emailChanged: (text) => dispatch(emailChanged(text))})
)(LoginForm);
so that your action actually gets dispatched
and as spotted by emed in comment:
export default emailChanged;
without parentheses.
You defined your callback inside your render() method and not inside the class body. Do it like this:
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
render() {
return(...);
}
}
Also you shouldn't bind methods inside your render() method. Do it in the constructor of your Component:
class LoginForm extends Component {
constructor(props) {
super(props);
this.onEmailChange.bind(this);
}
onEmailChange(text) {
// do something
}
// other methods
}
Or if you use babel and ES6, you can define your callback with an arrow function, then it will be automatically bound:
class LoginForm extends Component {
onEmailChange = text => {
// do something
};
// other methods
}
See also the react docs about autobinding.
Also your call to connect seems incorrect. If you want to dispatch the action emailChanged it has to look like this:
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
const mapDispatchToProps = dispatch => {
// this put a function emailChanged into your props that will dispatch the correct action
emailChanged: text => dispatch(emailChanged(text))
};
const LoginFormContainer = connect(mapStateToProps, mapDispatchToProps)(LoginForm);
export default LoginFormContainer;
The third argument to connect needs to be a function that knows how to merge the output of mapStateToProps, mapDispatchToProps, and ownProps all into one object that is then used as props for your connected component. I think you're trying to pass that action to the mapDispatchToProps argument, which is the second argument not the third. So, based on what I think you're doing, you probably wanna change your connect line to look like this.
export default connect(mapStateToProps, {emailChanged}) (LoginForm);
Then, export the function from your actions file not the output of calling that function.
export default emailChanged;
Notice I removed the parentheses so it's not being called.
Then make the callback function a method on your class and bind it in the constructor.
constuctor(props) {
super(props);
this.onEmailChange = this.onEmailChange.bind(this);
}
onEmailChange(text) {
this.props.emailChanged(text);
}
Then update onChangeText on that element.
onChangeText={this.onEmailChange}