I'm new to react native and would like to use Context to keep a socket connection alive between screens in the future. For now, I tried to learn the concept of context just to pass simple values around but the value doesn't get sent.
Tried to follow the tutorial here, but by sending simple values instead.
I create my ValueContext in ValueContext.js here.
import React from 'react';
const ValueContext = React.createContext();
export default ValueContext;
Here's my LoginScreen.js where I set context provider.
import React, { Component } from 'react';
import ConnectionScreen from './ConnectionScreen';
import ValueContext from './ValueContext';
const testValue = 5;
export const sendValue = props => (
<ValueContext.Provider value={testValue}>
<ConnectionScreen />
</ValueContext.Provider>
)
class LoginScreen extends Component {
render() {
return()
}
}
Then in my ConnectionScreen.js
import React, { Component } from 'react';
import { View, Alert } from 'react-native';
import LoginScreen from './LoginScreen';
import ValueContext from './ValueContext';
export const receiveValue = props => (
<ValueContext.Consumer>
{testValue => <ConnectionScreen {...props} testValue={testValue} />}
</ValueContext.Consumer>
)
class ConnectionScreen extends Component {
showAlertValue = () => {
Alert.alert(this.props.testValue);
}
render() {
return(
<View>
{this.showAlertValue()}
</View>
)
}
}
So after setting the value in LoginScreen, I would like to access it in ConnectionScreen. All I get in my alert box is an empty box with no values. Am I doing something wrong here?
I hope I can make sense of all of this in question format. I have been trying to work my way through this was the past 7 or so hours without success. My brain has simply run dry at this point and I am at a dead end regardless.
I am working with react 15.6.1, redux 3.7.1, react-redux 5.0.5, redux-immutable 4.0.0, redux-form 7.2.0, react-select 1.2.1.
My application has search functionality with field A and field B using 2 different forms (different forms for different pages). I don't it is pivotal to this question but I am using redux-form and react-select for the forms and search fields. I am storing user-entered search criteria in my Search reducer to synchronise autopopulation etc of select lists across the different forms.
redux-form---->react-select(field A)
---->react-select(field B)
My Search reducer is using Immutable.fromJs() in the initialState(). The reducer is working as expected. The problem I have is where to use the HOC in order to convert the Map object returned from the reducer into the JS array required for the react-select component.
MainSearchForm.js: -
import React, {Component} from 'react'
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import FormFieldA from './FormFieldA'
import FormFieldB from './FormFieldB'
class MainSearchForm extends Component {
render() {
return(
<form>
<FormFieldA options={this.props.fieldAoptions}/>
<FormFieldB options={this.props.fieldBoptions}/>
</form>
)
}
}
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
fieldBoptions: Search.get('fieldBoptions'),
}
}
MainSearchForm = connect(mapStateToProps,{})(MainSearchForm);
export default reduxForm({
form: 'main-search',
})(MainSearchForm)
To simply the example, both FormFieldA and FormFieldB components follow as the same: -
import React, {Component} from 'react'
import Select from 'react-select';
class FormFieldA extends Component {
render() {
const handleOnChange = (value) => {
// call dispatch here
}
return(
<Select
name="field-a-input"
id="field-a"
options={this.props.options}
onChange={handleOnChange}
/>
)
}
}
export default FormFieldA
So the options prop in the react-select component must be a JS array: -
options: [
{ label: 'Red' },
{ label: 'Green' },
{ label: 'Blue' }
]
I can convert this using Immutable.toJS() but the Redux official guide recommends against this for performance reasons, recommending this pattern using an (I assume reusable) HOC component to parse the Immutable Map to the JS array.
My question is, how would I incorporate this? As you can see in the code above, right now my MainSearchForm is connecting to the Redux store to retrieve the options data required as options for the react-select options props. Would the answer be to just not have MainSearchForm and instead have a intermediary component for each field rendered by MainSearchForm, with this intermediary component call the HOC before using connect, as is seen in the guide: -
HOC from the Redux Immutable guide: -
import React from 'react'
import { Iterable } from 'immutable'
export const toJS = WrappedComponent => wrappedComponentProps => {
const KEY = 0
const VALUE = 1
const propsJS = Object.entries(
wrappedComponentProps
).reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
wrappedComponentProp[VALUE]
)
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE]
return newProps
}, {})
return <WrappedComponent {...propsJS} />
}
Example intermediary smart component to parse the Immutable Map through the HOC and connect() with FormFieldA: -
import { connect } from 'react-redux'
import { toJS } from './to-js'
import FormFieldA from './FormFieldA'
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
}
}
export default connect(mapStateToProps)(toJS(FormFieldA))
Would that be best practice?
I would sincerely appreciate any help with this. This is my first time working with HOC & Immutable and it's a lot to take in. But I think I have finally got a grasp of the paradigm.
Don't worry too much about best practises, the best practises of today are the anti patterns of tomorrow. Don't get me wrong is good to know what's the "best" way doing things, but the best is always relative so take that into consideration.
The idea is that your FormFieldA and FormFieldB shouldn't care about redux, immutable, foobar, bajouras, wtvLibraryOrToolThatMightCome.
So your HOC, for the immutable should be in, your MainSearchForm:
import React, {Component} from 'react'
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import { toJS } from './to-js'
import FormFieldA from './FormFieldA'
import FormFieldB from './FormFieldB'
class MainSearchForm extends Component {
render() {
return(
<form>
<FormFieldA options={this.props.fieldAoptions}/>
<FormFieldB options={this.props.fieldBoptions}/>
</form>
)
}
}
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
fieldBoptions: Search.get('fieldBoptions'),
}
}
MainSearchForm = connect(mapStateToProps,{})(toJS(MainSearchForm));
export default reduxForm({
form: 'main-search',
})(MainSearchForm)
Leave your FormFieldA and FormFieldB as they are, very dumb and just waiting for a simple array of options.
This is just a personal taste, but I usually even separate the component from the redux stuff.
For example with your case I would have MainSearchForm and a MainSearchFormContainer
import React, {Component} from 'react'
import FormFieldA from './FormFieldA'
import FormFieldB from './FormFieldB'
class MainSearchForm extends Component {
render() {
return(
<form>
<FormFieldA options={this.props.fieldAoptions}/>
<FormFieldB options={this.props.fieldBoptions}/>
</form>
)
}
}
export default MainSearchForm
And the container:
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import MainSearchForm from './MainSearchForm'
import { toJS } from './to-js'
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
fieldBoptions: Search.get('fieldBoptions'),
}
}
const MainSearchFormContainer = connect(mapStateToProps,{})(toJS(MainSearchForm));
export default reduxForm({
form: 'main-search',
})(MainSearchFormContainer)
Looking at the docs for react-async-poll I'm following the Usage example to integrate asyncPoll into my component, but I'm getting a Uncaught TypeError: dispatch is not a function complaint from within my onPollinterval function
import React, { Component } from 'react';
import { connect } from 'react-redux';
import asyncPoll from 'react-async-poll';
import { fetchCaCities, } from '../actions';
import MyMap from './my-map';
class CaliforniaMap extends Component {
componentDidMount() {
this.props.fetchCaCities();
}
render() {
return (
<div>
<h1>California Map</h1>
<MyMap center={[37.5, -120]} zoom={6} layers={[this.props.caCities]} />
</div>
);
}
}
const onPollInterval = (props, dispatch) => {
console.log(dispatch); // undefined
return dispatch(fetchCaCities());
};
const mapStateToProps = state => ({
caCities: state.map.california.caCities,
});
export default asyncPoll(60 * 1000, onPollInterval)(connect(
mapStateToProps, { fetchCaCities }
)(CaliforniaMap)
Maybe react-async-poll doesn't work for connected components?
According to the docs:
The dispatch parameter is only passed to [onInterval] if it is
available in props, otherwise it will be undefined.
The example they give is confusing because it does not define dispatch anywhere, but they show onPollInterval using it.
I have the following smart component and I am specifying the initialUpload function is a required func:
import React, { Component, PropTypes } from 'react';
import UploadForm from '../../components/UploadForm/UploadForm';
import HeaderSelection from '../../components/HeaderSelection/HeaderSelection';
import { initialUpload } from '../../redux/modules/Upload';
import { connect } from 'react-redux';
console.log(typeof initialUpload); //function
#connect((state) => {
return {
file: state.getIn(['upload', 'file'])
};
}, {
initialUpload
})
export default class Home extends Component {
static propTypes = {
initialUpload: PropTypes.func.isRequired
};
render() {
return (
<div>
<UploadForm handleFilesChange={this.props.initialUpload}/>
<HeaderSelection/>
</div>
);
}
}
But I get the error message:
warning.js:36Warning: Failed prop type: The prop initialUpload is
marked as required in Connect(Home), but its value is undefined.
The function is wrapped in dispatch and passed down the component hierarchy fine so I am confused as to what is going on.
#connect is assigning the static propTypes to the resulting rapping component and not to Home itself, which is rather a weird behaviour! I think it is caused by ES7 transformation, this behaviour was not present in previous versions
A work around is to define the propTypes on the wrappedComponent outside class definition:
#connect(...)
export default Home extends Component{
render() {
// ....
}
}
Home.WrappedComponent.propTypes = {
initialUpload: PropTypes.func.isRequired
}
another option would be not to use the decorator & use connect directly
I've tried various ways but I can't seem to find anything about specifically testing if an action creator is called from a component. The hard part seems to be getting enzyme and redux connected containers to work. Below I'm exporting both connected and component class for tests. I tried wrapping the connected one in a provider but then I don't know how to target the textarea from enzyme to trigger the simulated onChange. The below works but I'm not sure if it's the best way of doing this?
Simple container:
I want to assert that onChange will call sendCommandValueChange with the correct value.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sendCommandValueChange } from 'actions';
export class RobotCommand extends Component {
handleCommandChange(e){
this.props.sendCommandValueChange(e.target.value);
}
render(){
return (
<div>
<textarea
onChange={ e => this.handleCommandChange(e)}
/>
</div>
);
}
}
export default connect(null, { sendCommandValueChange })(RobotCommand);
and here is the test:
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import { shallow, mount } from 'enzyme';
import { RobotCommand } from './RobotCommand';
describe('RobotCommand', () => {
it('should call sendCommandValueChange action creator on value change', () => {
const props = { sendCommandValueChange: sinon.spy() };
const wrapper = shallow(<RobotCommand {...props} />);
wrapper.find('textarea').simulate('change', {target: {value: 'foo'}});
expect(props.sendCommandValueChange.calledWith('foo')).to.equal(true);
});
});