I'm learning react (coming from a native iOS/Swift background) and I'm a bit confused about something I can't get to work.
I have a component that accepts props, so I figured I would write a class to model those props:
class HeaderProps {
text: string;
constructor(headerText:string) {
this.text = headerText;
}
}
// Make a component
const Header = (props:HeaderProps) => {
const { textStyle, viewStyle } = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{props.text}</Text>
</View>
);
};
and I'm exporting from my component like so:
export {Header, HeaderProps};
I'm then importing it:
import {Header, HeaderProps} from './src/components/header';
// Create a component
const App = () => ( <Header headerText={ new HeaderProps('Album') } />);
No text is appearing in my component.
If I just pass a string through as props it works fine, can't think of any reason why sending a class through wouldn't work.
I'm using flow type to declare the types of my arguments, not sure if that might be causing any issues.
A point in the right direction would be much appreciated!
Related
I am using useState hook in my react-native project. I have a screen which renders my custom component named MyComponent. The setter function of state is called in MyComponent 's onSelected callback.
import React, {useState} from 'react';
import MyComponent from './components/MyComponent'
const MyScreen=()=> {
...
const {parts, setParts} = useState(initialParts);
return (<View>
<MyComponent onSelected={()=> {
...
setParts(newParts)
}}/>
</View>)
}
...
MyComponent looks like this, in the onPress callback of TouchableOpacity, it calls the passed in onSelected function:
const MyComponent= ({onSelected})=> {
...
return (<TouchableOpacity onPress={()=>{
onSelected();
...
}}>
...
</TouchableOpacity>)
}
When I run my app on iOS emulator, the screen shows, when I tap on MyComponent, I get error TypeError: setParts is not a function. (In setParts(newParts)), 'setParts' is undefined.
Why I get this error?
Your destructuring here seems wrong:
const {parts, setParts} = useState(initialParts);
Shouldn't be this:
const [parts, setParts] = useState(initialParts);
?
Hmmm, it seems like you have to read the React official documentation to know more about UseState.
here is fix to your code:
const MyScreen = () => {
const [parts, setParts] = useState(initialParts) // see this fix.
return (
<View>
<MyComponent
onSelected={() => {
setParts(newParts)
}}
/>
</View>
)
}
basically, it is in the form of array de-structuring instead of object like you wrote above.
learn more: https://reactjs.org/docs/hooks-state.html
I have a custom component like this
const MyCustomComponent = ({ value, style }) => {
// I can access value & style with value and style
return <View style={style}>
<Text>{value}</Text>
</View>
}
I can called it with
<MyCustomComponent value="123" style={{ color: "blue" }} />
My question is how to get arguments or alyelse to get all props passed to my component?
In native function, i can use arguments to get allProps as an Array and set it in a new variable like const allProps = arguments[0]
What about in arrow function?
What you have here is a functional component, it is built as such that it only receives one object - that is props. When you did this: const MyComponent({value, style}) you destructured the prop object, extracting only the two variables.
You should instead do this:
const MyCustomComponent = (props) => {
//you can access the values like this
console.log(props.style, props.value)
//or you can access them like this which is the same thing you did
//earlier
const {style, value} = props;
console.log(style, value)
return (
...
)
}
Keep in mind that you need to have React in scope, so be sure to import it at the top:
import React from 'react';
Functional components are very well explained in React documentation: https://reactjs.org/docs/components-and-props.html
Do you want to get 'value' and 'style' props using a single variable?
You can use:
const MyCustomComponent = (props) => {
// I can access value & style with value and style
return <View style={props.style}>
<Text>{props.value}</Text>
</View>
}
I am trying to type annotate my React Native project using Flow, but I am having trouble finding the type definitions for the Text and TouchableOpacity elements so I can reference their style prop type definitions. How can I import and/or reference the type definitions for these elements?
Code below:
// #flow
import * as React from "react";
import { Text, TouchableOpacity } from "react-native";
type Props = {
segmentIndex: number,
segmentInfo: {
title: string
},
segmentStyle: ???, // WHAT SHOULD I USE HERE?
titleStyle: ???, // WHAT SHOULD I USE HERE?
onSelection: (number) => void
}
export const SegmentButton = (props: Props) => {
const _segmentPressed = () => {
props.onSelection(props.segmentIndex)
}
return (
<TouchableOpacity style={props.segmentStyle} onPress={_segmentPressed}>
<Text style={props.titleStyle}>{props.segmentInfo.title.charAt(0).toUpperCase() + props.segmentInfo.title.substring(1)}</Text>
</TouchableOpacity>
)
}
There are many ways to define StyleSheet type.
1. react-native generic way
reference from react-native library,
SwipeableQuickActionButton.js
import { View } from 'react-native';
type Prop = {
style?: ?View.propTypes.style,
}
2. react-navigation generic way
reference from react-navigation library,
TypeDefinition.js
export type Style =
| { [key: string]: any }
| number
| false
| null
| void
| Array<Style>;
If you already installed react-navigation, import it from:
import type { Style } from 'react-navigation/src/TypeDefinition';
type Prop = {
style?: Style,
}
Or you can still use it by define in your own file.
3. make Style type specific for <Text /> and <TouchableOpacity />
It would be the hard way -- although doable, see if that worth the trouble.
take <Picker /> for reference, it defined itemStyle for <Text /> like below, which corresponding to text style for each picker item.
var StyleSheetPropType = require('StyleSheetPropType');
var TextStylePropTypes = require('TextStylePropTypes');
var TextStyle = StyleSheetPropType(TextStylePropTypes);
type Prop = {
itemStyle: TextStyle
}
To use it in your own library, import them from:
import TextStylePropTypes from 'react-native/Libraries/Text/TextStylePropTypes';
import StyleSheetPropType from 'react-native/Libraries/StyleSheet/StyleSheetPropType';
let TextStyle = StyleSheetPropType(TextStylePropTypes);
type Prop = {
style?: ?TextStyle,
}
I have a component in React Native which updates it's state once it knows what size it is.
Example:
class MyComponent extends Component {
...
render() {
...
return (
<View onLayout={this.onLayout.bind(this)}>
<Image source={this.state.imageSource} />
</View>
);
}
onLayout(event) {
...
this.setState({
imageSource: newImageSource
});
}
...
}
This gives the following error:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
I guess the onLayout function is called while still rendering (which can be good, the sooner the update, the better). What is the correct way to solve this problem?
Thanks in advance!
We got around this by using the measure function, you will have to wait until the scene is fully complete before measuring to prevent incorrect values (i.e. in componentDidMount/componentDidUpdate). Here's an example:
measureComponent = () => {
if (this.refs.exampleRef) {
this.refs.exampleRef.measure(this._logLargestSize);
}
}
_logLargestSize = (ox, oy, width, height, px, py) => {
if (height > this.state.measureState) {
this.setState({measureState:height});
}
}
render() {
return (
<View ref = 'exampleRef' style = {{minHeight: this.props.minFeedbackSize}}/>
);
}
Here is a solution from documentation for such cases
class MyComponent extends Component {
...
render() {
...
return (
<View>
<Image ref="image" source={this.state.imageSource} />
</View>
);
}
componentDidMount() {
//Now you can get your component from this.refs.image
}
...
}
But for my opinion it's better to do such things onload
From what I have read its best to try and structure react apps with as many components as "dumb" renderers. You have your containers which fetch the data and pass it down to the components as props.
That works nicely until you want to pass functions down the chain that require arguments other than events.
class MyClass extends Component {
_onItemPress (myId) {
// do something using myId
}
render () {
return <MyComponent myID={10} onPress={(myId) => this._onItemPress(myId)} />
}
}
If I simply pass that as my onPress handler to MyComponent it won't return myId when called. To get around this I end up doing something like this.
export default ({myId, onPress) => {
const pressProxy = () => {
onPress(myId)
}
return (
<TouchableHighlight onPress={pressProxy}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
Am I doing this completely incorrectly? I would like to be able to have a simple component that I can re-use for list items where its sole function is to take a title, onpress function and return a list item to be used in ListViews renderRow function. Many of the onPress functions will require variables to be used in the onPress however.
Is there a better way?
The proper syntax would be something like this:
render () {
let myId = 10;
return <MyComponent myID={myId} onPress={() => this._onItemPress(myId)} />
}
Also, if you plan to use this inside _onItemPress (for example to call other methods in MyClass), you need to bind the scope like this:
render () {
let myId = 10;
return <MyComponent
myID={myId}
onPress={() => this._onItemPress(myId).bind(this)} />
}
...or you can bind it already in the constructor if you prefer:
constructor(props) {
super(props);
this._onItemPress = this._onItemPress.bind(this);
}
You did it correctly.
MyComponent is as "dumb" as it should be: it does not care about the source of its props, it acts independently from higher level of logic of the app and it can be reused somewhere else in the app.
Some improvements you can work on:
MyComponent does not need myId itself. Exclude it from the component and let parental component deals with related logics to id
Provide a safe check for props onPress. If you want to reuse MyComponent somewhere, it is better to check for existence of onPress property before calling it, or provide a default value for onPress in case developer adds in unwanted props types.
Example of MyComponent
class MyComponent extends Component {
handlePress = (e) => {
if (typeof this.props.onPress === 'function') {
this.props.onPress()
}
}
render() {
return (
<TouchableHighlight onPress={this.handlePress}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
}
and to call MyComponent in MyClass:
class MyClass extends Component {
_onItemPress(myId) {
}
render () {
return <MyComponent onPress={() => this._onItemPress(10)} />
}
}