I am trying to programatically focus TextInput element with a certain delay after it has mounted, I've got following component (a view that shows input and button)
For some reason this gets error saying
_this2.inputRef.focus is not a function
I'm not sure why. One out of place thing is that I get flow saying that createRef() doesn't exist on React, but I am assuming this is just missing flow definition now, as I am using react 16.3.1 and this was added in 16.3, plus there is no error when its called.
// #flow
import React, { Component, Fragment } from 'react'
import Button from '../../composites/Button'
import TextInput from '../../composites/TextInput'
import OnboardingStore from '../../store/OnboardingStore'
import withStore from '../../store'
import { durationNormal } from '../../services/Animation'
/**
* Types
*/
export type Props = {
OnboardingStore: OnboardingStore
}
/**
* Component
*/
class CharacterNameView extends Component<Props> {
componentDidMount() {
this.keyboardTimeout = setTimeout(() => this.inputRef.focus(), durationNormal)
}
componentWillUnmount() {
clearTimeout(this.keyboardTimeout)
}
keyboardTimeout: TimeoutID
inputRef = React.createRef()
render() {
const { OnboardingStore } = this.props
return (
<Fragment>
<TextInput
ref={this.inputRef}
enablesReturnKeyAutomatically
value={OnboardingStore.state.username}
onChangeText={username => OnboardingStore.mutationUsername(username)}
placeholder="Username"
blurOnSubmit
returnKeyType="done"
onSubmitEditing={/* TODO */ () => null}
/>
<Button disabled={!OnboardingStore.state.username} color="GREEN" onPress={() => null}>
Create
</Button>
</Fragment>
)
}
}
export default withStore(OnboardingStore)(CharacterNameView)
TextInput component I use is imported from this file
// #flow
import React, { Component } from 'react'
import { StyleSheet, TextInput as Input } from 'react-native'
import RatioBgImage from '../components/RatioBgImage'
import { deviceWidth } from '../services/Device'
/**
* Types
*/
export type Props = {
style?: any
}
/**
* Component
*/
class TextInput extends Component<Props> {
static defaultProps = {
style: null
}
render() {
const { style, ...props } = this.props
return (
<RatioBgImage source={{ uri: 'input_background' }} width={70} ratio={0.1659}>
<Input
{...props}
placeholderTextColor="#4f4a38"
selectionColor="#797155"
autoCapitalize="none"
autoCorrect={false}
keyboardAppearance="dark"
style={[styles.input, style]}
/>
</RatioBgImage>
)
}
}
export default TextInput
/**
* Styles
*/
const styles = StyleSheet.create({
input: {
width: '96.4%',
height: '97.45%',
color: '#797155',
fontSize: deviceWidth * 0.043,
marginLeft: '1%',
paddingLeft: '5%'
}
})
Below is what this.innerRef looks like, I don't see any focus property on it at the moment
inputRef is a property of the class instance, set it inside a class method.
Using a constructor, for instance:
class CharacterNameView extends Component<Props> {
constructor() {
this.inputRef = React.createRef()
}
...
}
My issue was in using React.createRef() and assuming I am setting it on a child component. Unfortunately I missed part of the docs about React.forwardRef That I had to use in a child component so ref gets set properly.
Related
I'm successfully passing a React ref through a factory function and down the component hierarchy to a child component that renders a <canvas>. For the factory function, I'm using forwardRef to pass the ref to the created component.
The following works as I can see the simple green rectangle in the browser, rendered by the child component ObservationPlotCanvas
App.jsx
import { createRef, Component } from 'react'
import ObservationPlotFactory from './ObservationPlotFactory'
class App extends Component {
constructor(props) {
super(props)
this.canvasRef = createRef()
}
render() {
const factory = ObservationPlotFactory({ config: { something: true } })
return (
<div className="App">
<header className="App-header"></header>
<factory.Component ref={this.canvasRef} />
</div>
)
}
}
export default App
ObservationPlotFactory.jsx
import React from 'react'
import ObservationPlot from './ObservationPlot'
const ObservationPlotFactory = config => {
return {
Component: forwardRef((props, ref) => {
// do something with config ...
return <ObservationPlot ref={ref} {...props} />
}),
Key: props => {
return <div>Key</div>
},
}
}
export default ObservationPlotFactory
ObservationPlot.jsx
import React, { forwardRef, Component } from 'react'
import ObservationPlotCanvas from './ObservationPlotCanvas'
export class ObservationPlot extends Component {
render() {
return <ObservationPlotCanvas ref={this.props.innerRef} />
}
}
export default forwardRef((props, ref) => <ObservationPlot innerRef={ref} {...props} />)
ObservationPlotCanvas.jsx
import React, { forwardRef, Component } from 'react'
class ObservationPlotCanvas extends Component {
componentDidMount() {
this.draw()
}
componentDidUpdate() {
this.draw()
}
render() {
return (
<div
style={{
position: 'absolute',
top: `100px`,
left: `100px`,
}}
>
<canvas width="600" height="400" ref={this.props.innerRef} />
</div>
)
}
draw() {
if (!this.props?.innerRef?.current) {
return
}
const g = this.props.innerRef.current.getContext('2d')
g.fillStyle = 'green'
g.fillRect(10, 10, 150, 100)
}
}
export default forwardRef((props, ref) => <ObservationPlotCanvas innerRef={ref} {...props} />)
But in the root level App, these components are rendered as a list using map and need to be created dynamically. I'm aware of Callback Refs and normally I would do something like this in the App constructor:
this.canvasRef = []
this.setCanvasRef = index => element => {
this.canvasRef[index] = element;
}
and then:
ref={(el) => this.setCanvasRef(index)}
on the element of interest in the render. But doing this provides the callback function when the child looks at this.props.innerRef and it needs the actual element to reference for it's canvas drawing.
I have React Native project(with Expo). I'm trying to make my text selectable and add custom actions to context menu. After some googling I found react-native-selectable-text library which throws errors when I use SelectableText component.
This is my initial code:
class RfcItem extends Component {
static navigationOptions = {
title: "RfcItem",
...navStyles,
};
render() {
const { RFC, loading } = this.props;
if (loading) return null;
const { rfc: c } = RFC.content;
return (
<ScrollView contentContainerStyle={styles.container}>
{/* TODO: Make this text selectable */}
<Text category="h1" status="primary">
{RFC.content}
</Text>
</ScrollView>
);
}
This is the code with react-native-selectable-text:
import React, { Component } from "react";
import navStyles from "../../styles/navStyles";
import {
StyleSheet,
View,
ScrollView,
PureComponent,
TextInput,
} from "react-native";
import { graphql } from "react-apollo";
import gql from "graphql-tag";
import Markdown from "react-native-markdown-renderer";
import { instanceOf } from "prop-types";
import mdStyles from "../../styles/md";
import sharedStyles from "../../styles/shared";
import { Layout, Text } from "react-native-ui-kitten";
import SelectableText from "react-native-selectable-text";
class RfcItem extends Component {
static navigationOptions = {
title: "RfcItem",
...navStyles,
};
render() {
const { RFC, loading } = this.props;
if (loading) return null;
const { rfc: c } = RFC.content;
return (
<ScrollView contentContainerStyle={styles.container}>
<SelectableText
selectable
multiline
contextMenuHidden
scrollEnabled={false}
editable={false}
onSelectionChange={event => {
const {
nativeEvent: {
selection: { start, end },
},
} = event;
const str = text.substring(start, end);
onSelectionChange({ str, start, end });
}}
style={{
color: "#BAB6C8",
}}
value={"some text"}
/>
</ScrollView>
);
}
}
It throws an error: Tried to register two views with the same name RCTMultipleTextInputView
How can I add text selection(with context menu)? What is the best way to do it?
When I look into the react-native-selectable-text library, I can't see onSelectionChange props just onSelection. Also you have to items to display as you have not added menuItems props
I'm new to React Native (and React), and I'm trying to pass a function as a prop to a component.
My goal is to create a component where its onPress functionality can be set by the instantiator of the component, so that it is more reusable.
Here is my code so far.
App.js
import React, { Component } from 'react';
import { View } from 'react-native';
import TouchableButton from './components/touchable-button';
export default class App extends Component<Props> {
constructor () {
super();
}
handlePress () {
// this should be called when my custom component is clicked
}
render () {
return (
<View>
<TouchableButton handlePress={this.handlePress.bind(this)}/>
</View>
);
}
}
TouchableButton.js
import React, { Component } from 'react';
import { TouchableHighlight } from 'react-native';
import AppButton from "./app-button";
export default class TouchableButton extends Component {
handlePress;
constructor(props){
super(props);
}
render () {
return (
<TouchableHighlight onPress={
this.props.handlePress
}>
<AppButton/>
</TouchableHighlight>
);
}
}
I am passing the handlePress function as the prop handlePress. I would expect the TouchableButton's props to contain that function, however it isn't there.
Solution
Use arrow function for no care about binding this.
And I recommend to check null before calling the props method.
App.js
export default class App extends Component<Props> {
constructor () {
super();
}
handlePress = () => {
// Do what you want.
}
render () {
return (
<View>
<TouchableButton onPress={this.handlePress}/>
</View>
);
}
}
TouchableButton.js
import React, { Component } from 'react';
import { TouchableHighlight } from 'react-native';
import AppButton from "./app-button";
export default class TouchableButton extends Component {
constructor(props){
super(props);
}
handlePress = () => {
// Need to check to prevent null exception.
this.props.onPress?.(); // Same as this.props.onPress && this.props.onPress();
}
render () {
return (
<TouchableHighlight onPress={this.handlePress}>
<AppButton/>
</TouchableHighlight>
);
}
}
When writing handlePress={this.handlePress.bind(this)} you passing a statement execution ( which when and if executed returns a function). What is expected is to pass the function itself either with handlePress={this.handlePress} (and do the binding in the constructor) or handlePress={() => this.handlePress()} which passes an anonymous function which when executed will execute handlePress in this class context.
// Parent
handleClick( name ){
alert(name);
}
<Child func={this.handleClick.bind(this)} />
// Children
let { func } = this.props;
func( 'VARIABLE' );
Alright I have a component called <TestButton />. Inside the <TestButton /> there are two Semantic UI React component, <Button /> and <Header>.
Basically, when the <Button> is clicked, it toggles display: none; to <Header>.
I want to check (I want to learn) on how to assert <Header>'s display: none; when <Button> is clicked.
TestButton.js
const TestButton = (props) => {
return (
<div id='test-button'>
<Header id='please-hide-me' size='huge'>Please Hide Me</Header>
<Button
onClick={
() => {
hiding = !hiding;
let findDOM = document.getElementById(searchForThisID);
if (findDOM) { findDOM.style.display = hiding ? 'none' : ''; }
return hiding;
}
}
>
Sample Toggle
</Button>
</div>
);
};
My unit test is based on How to test style for a React component attribute with Enzyme. It looks like this:
test(`
`, () => {
const wrapper = shallow(<TestButton />);
const button = wrapper.find('Button');
const header = wrapper.find('Header');
const headerStyle = header.get(0).style;
expect(headerStyle).to.have.property('display', '');
wrapper.find('Button').simulate('click');
expect(headerStyle).to.have.property('display', 'none');
}
);
But it has this error:
TypeError: Cannot read property 'have' of undefined
What should I do?
There are a few mistakes in your provided code:
You should not be using DOM element's style property because React does not manage it. Shift the hidden property into the state instead.
I believe headerStyle is a shallow copy of the style object. After you simulate click, it does not get updated. You will have to query the element again for the style object.
to.have.property is not valid Jest syntax. It should be toHaveProperty.
Please refer to the corrected code here. If you paste the following into create-react-app, it should just work.
app.js
import React, { Component } from 'react';
function Header(props) {
return <h1 style={props.style}>Header</h1>;
}
function Button(props) {
return <button onClick={props.onClick}>Click Me</button>;
}
export class TestButton extends React.Component {
constructor(props) {
super(props);
this.state = { hiding: false };
}
render() {
return (
<div>
<Header
id="please-hide-me"
style={{
display: this.state.hiding ? 'none' : '',
}}
>
Please Hide Me
</Header>
<Button
onClick={() => {
this.setState({
hiding: !this.state.hiding,
});
}}
>
Sample Toggle
</Button>
</div>
);
}
}
class App extends Component {
render() {
return (
<div className="App">
<TestButton />
</div>
);
}
}
export default App;
app.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
import { TestButton } from './App';
it('renders without crashing', () => {
const wrapper = shallow(<TestButton />);
expect(wrapper.find('Header').get(0).props.style).toHaveProperty(
'display',
'',
);
wrapper.find('Button').simulate('click');
expect(wrapper.find('Header').get(0).props.style).toHaveProperty(
'display',
'none',
);
});
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}