getting stuck in class to functional component conversion in react native - javascript

I am new to react native here I tried to convert class components to functional components, I have tried to pass ref in the functional component in several ways also I have used hooks to handle the state but I am unable to do so please help me out thanks in advance.
export default class AddClick extends Component {
constructor(props) {
super(props);
this.state = {
changeAnim: false,
};
}
componentDidMount() {
setTimeout(() => {
// handleScreenNavigation("OtpScreen", {});
this.setState({ changeAnim: true }, () => {
if (this.state.changeAnim) {
this.animation.play(48, 48);
}
});
}, 1500);
this.animation.play();
}
render() {
return (
<View style={styles.container}>
<View>
<Animation
ref={(animation) => {
this.animation = animation;
console.log("------#######");
}}
style={styles.imageStyle}
resizeMode="cover"
loop={true}
source={anim}
/>
</View>
</View>
);
}
}
here i have mentioned my attempt by functional component.
const AddClick = (props) => {
const [changeAnimation, setChangeAnimation] = useState(false)
useEffect(() => {
setTimeout(()=>{
setChangeAnimation(true),()=>{
if(changeAnimation){
animation.play(48,48)
}
}
},1500)
animation.play();
}, [])
return (
<View style={styles.container}>
<View>
<Animation
ref={(animation) => {
this.animation = animation;
console.log("------#######");
}}
style={styles.imageStyle}
resizeMode="cover"
loop={true}
source={anim}
/>
</View>
</View>
);
}
AppRegistry.registerComponent("AddClick", () => AddClick);

You cannot use this in a functional component. You can find the updated code here:
const AddClick = (props) => {
const [changeAnimation, setChangeAnimation] = useState(false)
let animation; // Create a local variable
useEffect(() => {
setTimeout(()=>{
setChangeAnimation(true),()=>{
if(changeAnimation){
animation.play(48,48)
}
}
},1500)
animation.play(); // Make sure to check if animation is defined before calling any methods
}, [])
return (
<View style={styles.container}>
<View>
<Animation
ref={(anim) => {
animation = anim;
console.log("------#######");
}}
style={styles.imageStyle}
resizeMode="cover"
loop={true}
source={anim}
/>
</View>
</View>
);
}
AppRegistry.registerComponent("AddClick", () => AddClick);

Related

Re rendering a component with an async function inside

I am new to react native and my JS is a bit rusty. I need to be able to change the value of my collection for the firestore. I have two buttons that will change the value of typeOfPost by setting the state. Component1 can successfully get "this.state.typeOfPost". However, when I click one of the buttons and update the state my log inside of the async function is not being called. It is only called when the app initially renders. What I find weird is that my log on the top of Component1 will display as expected. Is there any better way of doing this?
class Forum extends Component {
state = {
typeOfPost: ' '
}
onPressSitter = () => {
this.setState({
typeOfPost: 'sitterPosts'
})
}
onPressNeedSitter = () => {
this.setState({
typeOfPost: 'needPosts'
})
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<TouchableOpacity
style={styles.button}
onPress={this.onPressSitter}
>
<Text>I am a sitter</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={this.onPressNeedSitter}
>
<Text>Need a sitter</Text>
</TouchableOpacity>
</View>
<View>
<Component1 typeOfPost = {this.state.typeOfPost}> </Component1>
</View>
</View>
)
}
}
const Component1 = (props) => {
console.log("type of post " + props.typeOfPost);
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [data, setData] = useState([]); // Initial empty array of data
const getData = async () => {
console.log("type of post inside async " + props.typeOfPost);
const subscriber = firestore()
.collection(props.typeOfPost) // need to be able to update this
.onSnapshot(querySnapshot => {
const data = [];
querySnapshot.forEach(documentSnapshot => {
data.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setData(data);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}
useEffect(() => {
getData();
}, [])
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
data={data}
ListEmptyComponent={
<View style={styles.flatListEmpty}>
<Text style={{ fontWeight: 'bold' }}>No Data</Text>
</View>
}
renderItem={({ item }) => (
<View>
<Text>User ID: {item.fullName}</Text>
</View>
)}
/>
)
}
There is a difference between mount and render. I see no problem with your code except the few remarks I have made. The thing is that when you change typeOfPost, the component is rerendered, but the useEffect is not called again, since you said, it's just called when it was first mounted:
useEffect(() => {
}, []) // ---> [] says to run only when first mounted
However here, you want it to run whenever typeOfPost changes. So here is how you can do this:
useEffect(() => {
getData();
}, [typeofPost])
class Forum extends Component {
state = {
typeOfPost: ' '
}
onPressSitter = () => {
this.setState({
typeOfPost: 'sitterPosts'
})
}
onPressNeedSitter = () => {
this.setState({
typeOfPost: 'needPosts'
})
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<TouchableOpacity
style={styles.button}
onPress={this.onPressSitter}
>
<Text>I am a sitter</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={this.onPressNeedSitter}
>
<Text>Need a sitter</Text>
</TouchableOpacity>
</View>
<View>
<Component1 typeOfPost = {this.state.typeOfPost}> </Component1>
</View>
</View>
)
}
}
const Component1 = (props) => {
const { typeOfPost } = props
console.log("type of post " + props.typeOfPost);
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [data, setData] = useState([]); // Initial empty array of data
const getData = () => {
setLoading(true)
console.log("type of post inside async " + props.typeOfPost);
const subscriber = firestore()
.collection(props.typeOfPost) // need to be able to update this
.onSnapshot(querySnapshot => {
const data = [];
querySnapshot.forEach(documentSnapshot => {
data.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setData(data);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}
useEffect(() => {
getData();
}, [typeofPost])
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
data={data}
ListEmptyComponent={
<View style={styles.flatListEmpty}>
<Text style={{ fontWeight: 'bold' }}>No Data</Text>
</View>
}
renderItem={({ item }) => (
<View>
<Text>User ID: {item.fullName}</Text>
</View>
)}
/>
)
}
you are using a class based component to access react hook which is a bad practice, i will advice you use a functional component and you have access to react useCallback hook which will handle your request easily
const ButtonPressed = useCallback(() => {
setLoading(true);
getData()
}).then(() => setLoading(false));
}, [loading]);

Changing style of specific component returned from map function onClick

I am trying to change the style of individual TouchableOpacity components that have been returned from a map function.
Here is the component:
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<React.Fragment key={id}>
<TouchableOpacity
style={styles.button}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
Let TouchableOpacity = TO.
The map function returns about 30 TOs with unique IDs. When I click the TOs, I can see their unique ID in the console log. I want to know how I can modify the style of an individual TO.
Here is my render function which uses the functional component Example.
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList() }} />
</View>
</ScrollView>
</View>
);
}
What I have tried:
referencing this stackoverflow post, I tried to create a function which changed the style of the TO when it is clicked. But the result of this changed all the TOs in the UI since of the way it is mapped.
I tried something like the following.
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
let buttonStyle = this.state.pressed ? styles.button : styles.buttonClicked
return (
<React.Fragment key={id}>
<TouchableOpacity
style={buttonStyle}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
But as previously stated, this changed all of the Touchable Opacitys. Is there a way to only change one?
Thanks
Edit - to show entire class
class Page extends Component {
constructor(props) {
super(props)
}
MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
);
}
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id}/>
);
})}
</View>
);
};
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList()}} />
</View>
</ScrollView>
</View>
);
}
}
It is easier to separate the component inside map to a separate component and then handle style changes on press there
const MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
)
}
so you can use in the map like this
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id} />
);
})}
</View>
);
};

Class component to functional component is not working as expected

I am implementing infinite scrolling with react-native, when I do a search the result is returned and if the result has many pages on the API, when I scroll the API returns more data .
my implementation works fine on the class component but when I try to convert it to a working component, when I do a search, the data is returned and if I did another search, the previous data from the previous search is still displayed
class component
class Exemple extends React.Component {
constructor(props) {
super(props);
this.searchedText = "";
this.page = 0;
this.totalPages = 0;
this.state = {
films: [],
isLoading: false,
};
}
_loadFilms() {
if (this.searchedText.length > 0) {
this.setState({ isLoading: true });
getFilmsWithSearch(this.searchedText, this.page + 1).then((data) => {
this.page = data.page;
this.totalPages = data.total_pages;
this.setState({
films: [...this.state.films, ...data.results],
isLoading: false,
});
});
}
}
_searchTextInputChanged(text) {
this.searchedText = text;
}
_searchFilms() {
this.page = 0;
this.totalPages = 0;
this.setState(
{
films: [],
},
() => {
this._loadFilms();
}
);
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder="Titre du film"
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._searchFilms()}
/>
<Button title="Rechercher" onPress={() => this._searchFilms()} />
<FlatList
data={this.state.films}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <FilmItem film={item} />}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (this.page < this.totalPages) {
this._loadFilms();
}
}}
/>
{this._displayLoading()}
</View>
);
}
}
the functional component
const Search = () => {
const [films, setFilms] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [searchedText, setSearchedText] = useState("");
const _loadFilms = () => {
if (searchedText.length > 0) {
setIsLoading(true);
getFilmsWithSearch(searchedText, page + 1).then((data) => {
setPage(data.page);
setTotalPages(data.total_pages);
setFilms([...films, ...data.results]);
setIsLoading(false);
});
}
};
useEffect(() => {
_loadFilms();
}, []);
const _searchTextInputChanged = (text) => {
setSearchedText(text);
};
const _searchFilms = () => {
setPage(0);
setTotalPages(0);
setFilms([]);
_loadFilms();
};
const _displayLoading = () => {
if (isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
};
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder="Titre du film"
onChangeText={(text) => _searchTextInputChanged(text)}
onSubmitEditing={() => _searchFilms()}
/>
<Button title="Rechercher" onPress={() => _searchFilms()} />
<FlatList
data={films}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <FilmItem film={item} />}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (page < totalPages) {
_loadFilms();
}
}}
/>
{_displayLoading()}
</View>
);
};
With functional components, you cannot run effects (like getFilmsWithSearch) outside of useEffect.
From https://reactjs.org/docs/hooks-reference.html#useeffect
Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.
When you are calling _loadFilms from within then onSubmitEditing={() => _searchFilms()} event handler, you are not running inside useEffect, unlike the call to _loadFilms from useEffect that runs with the component mounts (because the second parameter to useEffect is [], it runs once on mount).
To solve this issue, you would typically have _searchFilms set a state variable (something like reloadRequested, but it does not have to be a boolean, see the article below for a different flavor) and have a second useEffect something like this:
useEffect(() => {
if (reloadRequested) {
_loadFilms();
setReloadRequested(false);
}
}
, [reloadRequested])
For a more complete example with lots of explanation, try this article https://www.robinwieruch.de/react-hooks-fetch-data.

How to call a child method from the parent in React Native?

When a click event is fired within my parent component I need to call the method closeMenu() from the SearchBar child component. I have tried a couple of different ways to do that but none of them are working. Does anyone know how to do this?
class Products extends Component {
constructor(props) {
super(props);
this.state = { closeMenu: false};
this.hideSearchBar = this.hideSearchBar.bind(this);
}
hideSearchBar(e) {
console.log('e: ', React.Children)
this.setState({closeMenu: true});
this.refs.SearchBar.closeMenu();
this.setState({closeMenu: false});
}
componentWillMount() {
this.props.dispatch(getProductList());
}
render() {
const {isLoading, products} = this.props.products;
if (isLoading) {
return <Loader isVisible={true}/>;
}
return (
<TouchableWithoutFeedback onPress={(e) => this.hideSearchBar(e)} style={{zIndex: 0}}>
<View style={styles.wrapper}>
<Header/>
<View style={styles.bodyWrapper}>
<ScrollView style={styles.scrollView}>
<ProductsContainer data={{productsList: { results: products }}}/>
</ScrollView>
<SearchBar ref="SearchBar" style={styles.searchBar} />
</View>
<Footer/>
</View>
</TouchableWithoutFeedback>
);
}
}
I also tried calling closeMenu() without refs:
hideSearchBar(e) {
this.setState({closeMenu: true});
this.SearchBar.closeMenu();
}
Here is the SearchBar component:
class SearchBar extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.suggestions = [];
}
componentWillUpdate(nextProps, nextState) {
console.log("COMPONENT WILL UPDATE");
console.log(nextProps);
console.log(nextState);
}
suggestionClick = (value) => {
}
getSuggestionText = (suggestion) => {
}
onChangeText = (value) => {
this.selectedSuggestion = false
this.props.dispatch(handleSearchItemText(value));
console.log(this.props.products.searchResults);
}
onFocus() {
const {height} = Dimensions.get('window');
this.setState({
contentOffset: {
x: 0,
y: height * 1 / 3
}
});
}
onBlur() {
this.setState({
contentOffset: {x: 0, y: 0}
});
}
closeMenu = () => {
this.props.products.searchResults = {};
}
componentWillReceiveProps() {
if (!this.props.closeMenu) {
this.props.closeMenu = false;
}
}
renderSearches = () => {
this.suggestions = this.props.products.searchResults;
const suggestionTexts = Object.keys(this.props.products.searchResults || {})
console.log(suggestionTexts);
if (!suggestionTexts.length) {
return null
}
// for android absolute element: https://github.com/facebook/react-native/issues/16951
// https://gist.github.com/tioback/6af21db0685cd3b1de28b84981f31cca#file-input-with-suggestions-L54
return (
<View
ref="suggestionsWrapper"
style={autoStyles.suggestionsWrapper}
>
{
this.suggestions.map((text, index) => (
<TouchableHighlight
key={index}
suggestionText={text}
activeOpacity={0.6}
style={autoStyles.suggestion}
onPress={this.suggestionClick(this.suggestions[text])}
underlayColor='white'
>
<Text style={autoStyles.suggestionText}>
{text}
</Text>
</TouchableHighlight>
))
}
</View>
)
}
render() {
const myIcon = (<Icon name="search" size={30} style={styles.searchIcon}/>);
const slidersIcon = (<Icon name="sliders" size={30} style={styles.slidersIcon}/>);
return (
<TouchableWithoutFeedback style={{zIndex: 0}}>
<View style={[styles.searchBar, this.props.style]}>
<View style={styles.searchContainer}>
<View>
{slidersIcon}
</View>
<View style={styles.search}>
<View style={styles.searchSection}>
{myIcon}
<TextInput
style={styles.input}
placeholder="Search"
placeholderTextColor="rgba(0,0,0,0.7)"
onChangeText={(searchString) => {
this.setState({searchString})
}}
underlineColorAndroid="transparent"
editable={true}
autoCorrect={false}
autoFocus={false}
autoCaptialize={'none'}
autoCorrect={false}
onChangeText={this.onChangeText}
enablesReturnKeyAutomatically={true}
onFocus={() => this.onFocus()}
onBlur={() => this.onBlur()}
/>
</View>
</View>
</View>
{this.renderSearches()}
</View>
</TouchableWithoutFeedback>
);
}
}
There are some issues which you should avoid:
Never mutate props: this.props.something = {} is an anti-pattern. Think about props as data that your component does not own and which are not mutable. If they change then only because the parent passed new props.
Also you have multiple handlers in your SeachBar that are not bound to this but use this. It will not work. Use arrow functions if you want to use this or bind them in the constructor.
You should overthink the architecture of your app. Maybe it is a good idea to split the search bar and the result list into two separate components. When the user types something to search for update your redux store and display the results in a separate component that you only render if there are search results.
I'm affraid it would exceed the length of a stackoverflow answer to solve all these issues. Maybe you should go back to the basics first and do the really good redux tutorial.

React native. losing context of this in touchable highlight on press.

I'm trying to add a TouchableHighlight component to a row in a list view.
The onPress function is throwing an undefined error in the code below. It works outside of the list view.
I suspect this is because I'm losing context of this but unsure how to fix. Anyone able to help?
export default class ConversationsList extends Component {
constructor(props) {
super(props);
this._handleChangePage = this._handleChangePage.bind(this);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(Coversations.chats)
};
}
_handleChangePage(chat) {
this.props.navigator.push({
title: 'foo',
component: Chat,
passProps: {
chat: chat
}
});
}
renderRow(chat){
return (
<View>
<TouchableHighlight onPress={ () => this._handleChangePage.bind(this, chat) }>
<View>
/* more content removed */
</View>
</TouchableHighlight>
</View>
);
}
render() {
return (
<View style={styles.container}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
</View>
);
}
}
I also suspect that I shouldn't really be doing things this way, that my component should be structured differently, so it is passed the press handler as a prop perhaps. any advice appreciated.
You can declare another variable globally like
var _this;
initialise it in render method
render:function(){
_this = this;
return(
...
)
}
and use it in your touchableHightlight
renderRow(chat){
return (
<View>
<TouchableHighlight onPress={ () => _this._handleChangePage(chat) }>
<View>
/* more content removed */
</View>
</TouchableHighlight>
</View>
);
}
I suggest to read this helpful article
export default class ConversationsList extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(Coversations.chats)
};
}
_handleChangePage = () => {
this.props.navigator.push({
title: 'foo',
component: Chat,
passProps: {
chat: this
}
});
}
renderRow = (chat) => {
return (
<View>
<TouchableHighlight onPress={ this._handleChangePage }>
<View>
/* more content removed */
</View>
</TouchableHighlight>
</View>
);
}
render() {
return (
<View style={styles.container}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
</View>
);
}
}

Categories

Resources