refactor ReactJS component to remove getDerivedStateFromProps - reactjs

I have the following React component which is working as desired. After reading about fully controlled components in this blog post I'm wondering if this code could be refactored to make it fully controlled, which could eliminate the use of getDerivedStateFromProps, if I understand correctly. Being more familiar with Vue Native than React Native I'm looking for some guidance on how to go about doing such a refactor.
import React, { Component } from "react";
import { Container, Content, Picker } from "native-base";
export default class DynamicPicker extends Component {
constructor(props) {
super(props);
this.state = {
selected: this.props.selected
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.selected !== prevState.selected) {
return ({ selected: nextProps.selected });
} else {
return null;
}
}
onValueChange(value) {
this.setState({
selected: value
});
this.props.onValueChange(value);
}
itemsList = () => {
return (this.props.items.map( (item, index) => {
return (<Picker.Item label={item} key={index} value={item} />)
}));
}
render() {
return (
<Container>
<Content>
<Picker
// note
mode="dropdown"
style={{ borderColor: 'gray', borderWidth: 1, height: 40, margin: 5 }}
selectedValue={this.state.selected}
onValueChange={this.onValueChange.bind(this)}
>
{ this.itemsList() }
</Picker>
</Content>
</Container>
);
}
}

From what I'm seeing you actually don't need any state here or the method onValueChange.
Why do you want to get a value from props just to set it on the state? Can't you just use the selected value received as props?
Also, can't you just use the method onValueChange received from props? I don't see any need to make another one in this component, because you're setting a new state, but you're going to change the state after that because you call this.props.onValueChange. When any prop changes the component will rerender, therefore getDerivedStateFromProps will be called and it will modify the state.
Long story short, this is how I see this component:
import React, { Component } from "react";
import { Container, Content, Picker } from "native-base";
export default class DynamicPicker extends Component {
itemsList = () =>
// Implicit return from the arrow function
this.props.items.map( (item, index) => {
return (<Picker.Item label={item} key={index} value={item} />)
});
}
render() {
return (
<Container>
<Content>
<Picker
// note
mode="dropdown"
style={{ borderColor: 'gray', borderWidth: 1, height: 40, margin: 5 }}
selectedValue={this.props.selected}
onValueChange={this.props.onValueChange}
>
{ this.itemsList() }
</Picker>
</Content>
</Container>
);
}
}

Related

React not changing Value of variable

I try to learn React JS, however, I stumbled over a problem I can't solve. I try to use an API to get a value of something and show the value as a simple text, but the text is not updating even tho the Data gets correctly log in console.
The Code of my App.js is
import React, { Component } from "react";
import { Button } from "#material-ui/core";
import { Image } from "react-bootstrap";
import albedo from "#albedo-link/intent/lib/albedo.intent";
import albedologo from "./albedo.png";
class LogInWithAlbedo extends Component {
constructor(props) {
super(props);
this.state = {
value: null
};
}
Albedo() {
albedo.publicKey({}).then((res) => {
const { intent, pubkey, signature, signed_message } = res;
console.log({ intent, pubkey, signature, signed_message });
this.setState((currentState) => {
return { value: pubkey };
});
});
}
render() {
const { pubkey } = this.state;
return (
<div>
<div>
<Button
style={{ width: "207px", height: "40px" }}
variant="contained"
color="default"
onClick={this.Albedo.bind(this)}
>
Login With <Image style={{ width: "55px" }} src={albedologo} />
</Button>
<h2>It is {pubkey}.</h2>
</div>
</div>
);
}
}
export default LogInWithAlbedo;
You can also see it here:
https://codesandbox.io/s/vigilant-star-ntqv7

Communicating between a parent and child in the React Native tree without access to parent's parent component

Let's say I'm trying to make a Radio Group from scratch for a customer (thus, I don't have access to the component they us it in). I have a RadioGroup component and a RadioOption component. From a customer's usage perspective, it would look like this:
class customerComponent(props..) {
render() {
<View>
<RadioGroup {props..}>
<RadioOption {props} />
<RadioOption {props} />
<RadioOption {props} />
</RadioGroup>
</View>
}
}
How would I communicate between the two components? Without access to the component their being called from? For example, if a user selects a new option, how would I tell the RadioGroup?
You can keep the selected option as a state value in the parent component like this:
class CustomerComponent extends Component {
state = {
selectedOption: 0,
};
handleOptionClick = (option) => {
this.setState({ selectedOption: option });
}
render() {
<View>
<RadioGroup {props..} selectedOption={this.state.selectedOption}>
<RadioOption onClick={this.handleOptionClick} {props} />
<RadioOption onClick={this.handleOptionClick} {props} />
<RadioOption onClick={this.handleOptionClick} {props} />
</RadioGroup>
</View>
}
}
The higher order component would need to provide the options, as well as the callback to be ran when a selection is made. Here is a snack example of a simple radio group that may help you get started https://snack.expo.io/HJA88p3bL
And the code for that snack
import * as React from 'react';
import { Text, TouchableOpacity, View, StyleSheet } from 'react-native';
export default class App extends React.Component {
state = {
selected: []
}
onSelect = (x) => {
this.setState({
selected: this.state.selected.includes(x)
? [
...this.state.selected.slice(0, this.state.selected.indexOf(x)),
...this.state.selected.slice(this.state.selected.indexOf(x) + 1)
]
: [...this.state.selected, x]})
}
render() {
return (
<View style={styles.container}>
<RadioGroup options={["One", "Two", "Three"]} selected={this.state.selected} onSelect={this.onSelect} />
</View>
);
}
}
class RadioGroup extends React.Component {
render() {
return <View>
{this.props.options.map(x =>
<TouchableOpacity style={styles.row} onPress={() => this.props.onSelect(x)}>
<Text>{x}</Text>
<Text>{this.props.selected.includes(x) ? "X" : "O"}</Text>
</TouchableOpacity>)}
</View>
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between'
}
});

focus on input text in conditional rendering react component

I have Conditional Rendering component in reactJs. i use latest react version, and use MaterialUi in my application. this component use to show a span with a text and as soon as user click on it, it change to a input with a component of MaterialUi and user can change the field by this component
import React from 'react';
import EditIcon from '#material-ui/icons/Edit';
import TextField from '#material-ui/core/TextField';
import { grey400 } from 'material-ui/styles/colors';
class InlineEditInput extends React.Component {
constructor(props) {
super(props);
this.state = {
hover: false,
edit: false,
value: this.props.children
};
this.textInput = React.createRef();
}
handleClick = event => {
event.stopPropagation();
if (!this.state.edit) {
this.setState({ value: this.props.children });
this.setState({ edit: true, hover: false });
}
};
handleBlur = () => {
this.setState({ edit: false });
if (this.state.value.length > 0 && this.state.value !== this.props.children) this.props.onChange(this.state.value);
else this.setState({ value: this.props.children });
};
handleMouseEnter = () => this.setState({ hover: true });
handleMouseLeave = () => this.setState({ hover: false });
render() {
let { hover, edit, value } = this.state;
const originalValue = this.props.children;
const styles = {
label: { minHeight: '2em', marginTop: '10px' },
editIcon: { width: 20, height: 20, fill: grey400, marginLeft: 8 },
editIconHidden: { width: 20, height: 20, fill: 'none', marginLeft: 8 }
};
const setFocus = () => {
this.textInput.focus();
};
if (!edit)
return (
<div onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<span onClick={this.handleClick}>{originalValue}</span>
{hover ? <EditIcon style={styles.editIcon} /> : <EditIcon style={styles.editIconHidden} />}
</div>
);
else
return (
<TextField
id="EditField"
ref={input => {
this.textInput = input;
setFocus();
}}
value={value}
onClick={this.handleClick}
onBlur={this.handleBlur}
onChange={event => this.setState({ value:event.target.value })}
/>
);
}
}
export default InlineEditInput;
At first an span with value of originalvalue that comes get from its props of this component, is rendered and by click on this, edit state changed to ture and a TextField component of MaterialUi is rendered and I want to focus on this TextFieldAs soon as it is rendered.
For this purpose, I render a TextField and define its ref property that pass input to a function with name of setFocus and in this function I write focus method.
But when I click on span and re-render of component is occurred, i faced with the error that said :
this2.textInput.focus is not a function
how can i write this component?
Try using componentDidUpdate life cycle hook
componentDidUpdate(prevProps, prevState, snapshot) {
if(this.state.edit)
this.textInput.focus();
}
The reason your method is not getting the input to be focused might be due to the fact that the DOM is not yet inserted when you are actually creating the ref. More like the textInput element is created but it's not appended to the DOM. Just a hunch, not 100% sure.

Problem with passing Params between Stack and Drawer Navigator screens - React-Native

Hi I am new to react native and trying to learn few things. I am trying to pass a data from one screen to another.
I need to pass the video id to Web View on another page to play YouTube video. but the Video Id is not passed to another screen.
I've tried to pass Param to one screen to another.In this project, I am using stack and drawer navigators.
The param id is "ytId"
also i tried to pass the param with AsyncStorage. Please anyone assist me with this issue and thanks in advance.
Screen 3:
import React from 'react';
import { Text, View, FlatList, Image, TouchableWithoutFeedback} from 'react-native';
import { Button, Icon } from 'native-base';
export default class App extends React.Component {
navOptions
static navigationOptions = ({ navigation }) => {
navOptions = navigation;
const { params = {} } = navigation.state;
return {
headerLeft: (
<Button
transparent
onPress={() => params._onHeaderEventControl()}
>
<Icon
name="menu"
style={{ fontSize: 30, color: 'white' }}
/>
</Button>
)
}
}
constructor(props) {
super(props);
this.state = { listLoaded: false };
}
onHeaderEventControl() {
const { params = {} } = navOptions.state;
params._openNav()
}
componentDidMount() {
this.props.navigation.setParams({
_onHeaderEventControl: this.onHeaderEventControl,
_openNav: () => this.openDrawer()
})
return fetch(
'https://www.googleapis.com/youtube/v3/search?part=snippet&q=lcwell&type=video&key=AIzaSyCwCHIfFvkMZ1aR6eIvy4sUIgqV6hIZ3qU')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
listLoaded: true,
videoList: Array.from(responseJson.items)
})
})
.catch((error) => {
console.error(error);
});
}
openDrawer() {
this.props.navigation.openDrawer();
}
render() {
const { navigate } = this.props.navigation;
return (
<View>
{this.state.listLoaded && (
<View style={{ paddingTop: 0 }}>
<FlatList
data={this.state.videoList}
renderItem={({ item }) =>
<TubeItem
navigate={navigate}
id={item.id.videoId}
title={item.snippet.title}
imageSrc={item.snippet.thumbnails.high.url}
/>
}
keyExtractor={(item, index) => index.toString()}
/>
</View>
)}
{!this.state.listLoaded && (
<View style={{ paddingTop: 30 }}>
<Text>LOADING</Text>
</View>
)}
</View>
);
}
}
export class TubeItem extends React.Component {
onPress = () => {
this.props.navigate('Screen5', { ytId: this.props.id })
};
render() {
return (
<TouchableWithoutFeedback onPress={this.onPress}>
<View style={{ paddingTop: 20, alignItems: 'center' }}>
<Image
style={{ width: '100%', height: 200 }}
source={{ uri: this.props.imageSrc }}
/>
<Text>
{this.props.title}
</Text>
</View>
</TouchableWithoutFeedback>
);
}
}
Screen 5:
import React from 'react';
import { WebView } from 'react-native';
export default class VideoDetail extends React.Component {
navOptions
static navigationOptions = ({ navigation }) => {
navOptions = navigation;
const { params = {} } = navigation.state;
}
onHeaderEventControl() {
const { params = {} } = navOptions.state;
params._openNav()
}
componentDidMount() {
this.props.navigation.setParams({
_onHeaderEventControl: this.onHeaderEventControl,
_openNav: () => this.openDrawer()
})
}
render() {
let tubeId = this.props.navigation.getParam('ytId', 'NO VIDEO');
let tubeUrl = `https://www.youtube.com/embed/${tubeId}`;
return (
<WebView
style={{ marginTop: 20 }}
javaScriptEnabled={true}
source={{ uri: tubeUrl }}
/>
);
}
}
I would suggest you to use a state container like redux.
It allows you to pass variable and parameters from a component to another.
I didn't explain all in details, others do it better than me and there a re a lot of tutorials to implement redux.
You can find the official redux website https://redux.js.org/introduction/getting-started
Main steps would be:
Import redux in your package.json
Create a store with createStore method from imported package
Surround your main view with new created object <Provider store={store}>
Declare needed methods in your store
Connect Screen 3 & Screen 5 to redux
You will then be able to pass variable between your screens and access it very easy via props property.
It will simplify your life!
Otherwise we would need the way you declare your Stack and Drawer to be able to answer :-)

Why is my custom component not rendering in React Native?

I have created a component called OrderGuideSelect and I am trying to render it in another area of our app. The problem is the OrderGuideSelect component is not rendering. When I set up breakpoints I am able to hit inside of the renderOrderGuideOptions function but it never makes it into the OrderGuideSelect.js file. I also tried putting 'export default' in front of the class declaration instead of the connection but it didn't make a difference. Does anyone know how to get the OrderGuideSelect component rendering properly?
Here is where I call the function that renders the OrderGuideSelect component:
<TouchableOpacity onPress={() => this.renderOrderGuideOptions()}>
<MBIcon name="ico-24-filter" size={30} style={styles.filterIcon}/>
</TouchableOpacity>
And here is the rendering function:
renderOrderGuideOptions = () => {
return (
<View>
<OrderGuideSelect />
</View>
)
}
Here is the OrderGuideSelect.js file:
import React, {Component} from 'react';
import {View, FlatList, ActivityIndicator, StyleSheet} from 'react-native';
import {connect} from 'react-redux';
import {fetchOrderGuides} from '../../actions/AppActions';
import {orderGuideSelected} from '../../actions/ProductAction';
import Header from '../../components/Header/Header';
import {createIconSetFromIcoMoon} from 'react-native-vector-icons';
import selection from '../../selection';
import OrderGuideOption from './OrderGuideOption';
const MBIcon = createIconSetFromIcoMoon(selection);
class OrderGuideSelect extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.dispatch(fetchOrderGuides());
}
selectOrderGuide = id => {
this.props.dispatch(orderGuideSelected(id));
}
render() {
const {isLoading, orderGuides} = this.props.orderGuide;
return (
<View style={styles.wrapper}>
<Header />
<View style={styles.iconLine}>
<MBIcon name='ico-24-filter' style={styles.filterIcon} />
</View>
{isLoading &&
<ActivityIndicator
style={{alignSelf: 'center'}}
animating={true}
size='large'
/>
}
{!isLoading &&
<View style={styles.optionList}>
<FlatList
style={styles.optionList}
data={orderGuides}
keyExtractor={(item, index) => item.id.toString()}
renderItem={({item}) => <OrderGuideOption guideData={item} isSelected={item.id == this.props.selectedGuide.id} onSelected={this.selectOrderGuide} />}
/>
</View>
}
</View>
);
}
}
function mapStateToProps(state){
const {products, orderGuide} = state;
return {
selectedGuide: products.selectedOrderGuide,
orderGuide
}
}
export default connect(mapStateToProps)(OrderGuideSelect);
Also, I may be importing of the OrderGuideSelect component should be correct:
In your code calling this.renderOrderGuideOptions function on onPress event doesn't make sense, i.e. this.renderOrderGuideOptions returns the element but where to append it in DOM?
This should be achived using state in React. So you can set the state in onPress handler then use that state in render to show your OrderGuideOptions component.
So on onPress event bind the function handler:
<TouchableOpacity onPress={this.showOrderGuideOptions}>
<MBIcon name="ico-24-filter" size={30} style={styles.filterIcon}/>
</TouchableOpacity>
Now this showOrderGuideOptions will set the state named showOrderGuideFunction to true.
showOrderGuideOptions(){
this.setState({showOrderGuideFunction: true});
}
At last step use this showOrderGuideFunction state to render your component in the render function like this:
render() {
return (
<div>
...
{
this.state.showOrderGuideFunction &&
renderOrderGuideOptions()
}
</div>
)
}
You can do what you want probably holding a state property in your component and show your OrderGuideOptions according to this state property.
state = { showOrderGuideOptions: false };
renderOrderGuideOptions = () =>
this.setState( prevState => ( { showOrderGuideOptions: !prevState.showOrderGuideOptions }) );
render() {
return (
<View>
<TouchableOpacity onPress={this.renderOrderGuideOptions}>
<MBIcon name="ico-24-filter" size={30} style={styles.filterIcon}/>
</TouchableOpacity>
{ this.state.showOrderGuideOptions && <OrderGuideSelect /> }
</View>
)
}
I think you wanted to something similar to this
class RenderOrderGuideSelectComponent extends Component {
constructor(props) {
super(props);
this.state={
showOrderGuideSelect : false
};
}
renderOrderGuideOptions = () => {
this.setState({showOrderGuideSelect: true});
}
render() {
if(this.state.showOrderGuideSelect) {
return (
);
} else {
return (
this.renderOrderGuideOptions()}>
);
}
}
}

Resources