React Native binding functions over .map() - reactjs

So I am having some trouble combining concepts of .map() and function binding. I am using .map() in the same way ngFor is used in angular, to place a custom button component on the page for every item in a user's account.
Here is some example code:
class MyButton extends Component {
constructor(props) {
super();
this.state = {
progress: 0
}
}
render() {
return(
<TouchableWithoutFeedback onPress={this.pressFunction}>
(...more code inside)
</TouchableWithoutFeedback>
)
}
pressFunction = () => {
(animate progress from 0 to 1 for some animation)
}
}
/////////////////////////////////////////////////////////////////
class Parent extends Component {
render() {
return(
{
this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton data={obj} />
</View>
)
})
}
)
}
}
So in the Parent Component, multiple MyButtons are rendered properly, each according to the passed object from the array. However, when any button is pressed, all of the pressFunctions for all MyButtons fire.
My question is I guess, how do I ensure that each pressFunction of each MyButton is bound only to the specific instance of the MyButton? I am having trouble with the scope here.
My understanding is that
functionName = () => {}
should properly bind the function to the instance, but I have tried the older ways as well with the same result.

I solved this by creating a dynamic ref on each object mapped to a MyButton, using a unique property of each obj in the array:
this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton ref={obj.name} data={obj} />
</View>
)
})
Still don't know why my it didn't bind uniquely without a ref

You should pass onPress as a props. Below is the updated code
class MyButton extends Component {
constructor(props) {
super();
this.state = {
progress: 0
}
}
render() {
return(
<TouchableWithoutFeedback onPress={this.props.onPress}>
(...more code inside)
</TouchableWithoutFeedback>
)
}
}
/////////////////////////////////////////////////////////////////
class Parent extends Component {
pressFunction = () => {
(animate progress from 0 to 1 for some animation)
}
render() {
return this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton
data={obj}
onPress={this.pressFunction}
/>
</View>
)
})
}
}

Related

Child components not updating while in array React Native

I'm trying to update child component which has a prop with parent state. While changing state of parent with setState() to my knowlage chlid should rerender. But when child is in an array rerender does not occure. How can i fix that?
Thanks in advance.
My child class:
class Child extends Component {
render() {
return(
<Text>{this.props.state.isActive.toString()}</Text>
)
}
}
My parent class:
class Parent extends Component {
state = {
isActive: false
}
children = []
constructor(props) {
super(props)
this.createChildren()
this.changeState()
}
changeState() {
this.change = setInterval(() => {
if(this.state.isActive){
this.setState({isActive: false})
} else {
this.setState({isActive: true})
}
}, 1000)
}
componentWillUnmount() {
clearInterval(this.change)
}
createChildren() {
for(let i=0; i<5; i++) {
this.children.push(<Child key={i} state={this.state}/>)
}
}
render() {
return(
<View>
{this.children}
</View>
)
}
}
My app function:
export default function App() {
return (
<View style={style.conteiner}>
<Parent/>
</View>
);
}
React elements should always be created during render. Do not store them in a property or in component state:
renderChildren = () => {
children = [];
for(let i=0; i<5; i++) {
children.push(<Child key={i} state={this.state}/>)
}
return children;
}
render() {
return(
<View>
{this.renderChildren()}
</View>
)
}
You are basically rendering always the same elements that you created once in the constructor (which also is an anti-pattern).
What you are doing is not going to work, since you are pushing <Child/> components to a class attribute which is only called inside the constructor; so these <Child/> components will refer to only initial state, not the now updated state. You will have to call a getChildren() function inside the render. I've fixed the issue in react, you can translate to react native replacing the divs with Views, see codesandbox : https://codesandbox.io/s/pedantic-jackson-wgtnh?file=/src/App.js

How to get scroll properties in react-simplebar (in stateful function)

I am new with refs in react.js and in the react-simplebar documentation it just shows how to get the scroll ref for a stateless function.
class App extends React.Component {
render() {
console.log(this.refs.scroll) // => Undefined
return (
<Simplebar ref={this.refs.scroll}><h1>scrollable element</h1></Simplebar>
)
}
}
For anyone coming to this at a later date. This is how I managed to set the scrolling position after a few hours of digging around in a function component
const SimpleScrollerComponent = () => {
// Create a reference for the SimpleBar component so we can acces it
const scrollableNodeRef = React.createRef();
const handleScrollDownBtnClicked = () => {
// This is where we set the scroll position
scrollableNodeRef.current.scrollTop = 1200;
};
return (
<div>
{/* We attach the reference to the component */}
<SimpleBar scrollableNodeProps={{ ref: scrollableNodeRef }}>
{/* This is where your code goes inside the scroll box */}
</SimpleBar>
<Button onClick={handleScrollDownBtnClicked}>Scroll to the Bottom</Button>
</div>
);
};
try this
class App extends React.Component {
constructor(props) {
super(props);
this.scrollableNodeRef = React.createRef();
}
onChangeScrollToTop() {
this.scrollableNodeRef.current.scrollTop = 0;
}
render() {
console.log(this.refs.scroll) // => Undefined
return (
<Simplebar scrollableNodeProps={{ ref:this.scrollableNodeRef }}>
<h1>scrollableelement</h1>
</Simplebar>
)
}
}

Pass parameters to prop function without using an arrow function

I've heard that passing an arrow function as a prop is not ideal because it creates a new function every time which will lead to performance issues. However, I'm not entirely sure how to completely move away from them, as can be seen by the example below:
class Home extends Component {
onCardPress = (message) =>{
alert(message)
}
render(){
return(
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={()=>{onCardPress(message)}}
/>
)
}
}
I have tried changing onPress in Card to be onPress={onCardPress(message)}, but I know this doesn't work because I am invoking the function rather than passing a function object to the onPress of TouchableOpacity. What is the 'proper' way or best practice to remove the arrow function in TouchableOpacity while still being able to pass the message parameter from the parent component Home?
You could do:
class Card extends Component {
pressHandler = () => this.props.onCardPress(this.props.message);
render() {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={this.pressHandler.bind(this)}
/>
);
} }
If you want to avoid arrow function, you have to use bind(). Arrow functions will automatically bind "this".
class Home extends Component {
constructor(props) {
super(props);
this.onCardPress = this.onCardPress.bind(this);
}
onCardPress (message) {
alert(message)
}
render(){
return(
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={onCardPress(message)}
/>
)
}
}
As I understand it, the issue lies with calling bind inside of render, or returning the handler from yet another lambda, as this will create a new function each time. The conventional way to get around this problem is to bind your handler functions elsewhere -- like in the constructor. In your case, that could look like this:
constructor(props) {
....
this.onCardPress = this.onCardPress.bind(this);
}
...
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
Given you alternative option as arrow function already answered in above post.
class Card extends Component {
onClick = () => {
const { onCardPress, message } = this.props;
onCardPress(message);
}
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onClick}
/>
)
}
}
You don't need to pass the message prop because you can access it anywhere in the component.
Just supply a function in the onPress prop. And in that function, just access the message prop of the component.
class Home extends Component {
onCardPress = (message) => {
alert(message)
}
render() {
return (
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
onClick = () => {
const { message, onCardPress } = this.props;
onCardPress(message);
};
render() {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onClick}
/>
)
}
}

How to render one React Native component after another component had rendered?

Let's say I have 2 components A and B. I want to render component A and after that render component B. To make that more obvious, I would also like to set some delay (let's say 2 seconds) between the rendering of A and B. What would be the best way to approach this? I guess I should somehow trigger the rendering of B in the componentDidMount of A, but I don't really know how to do that triggering.
Thanks :)
Your question is very vague and open to multiple implementation.
Here's my take:
export default class App extends Component {
constructor() {
super()
this.state = { displayComponentB: false }
this.displayComponentB = this.displayComponentB.bind(this)
}
displayComponentB() {
setTimeout(() => {
this.setState({ displayComponentB: true })
}, 2000) // delay
}
render() {
return (
<View style={styles.container}>
<ComponentA onComponentDidMount={this.displayComponentB}/>
{
this.state.displayComponentB &&
<ComponentB />
}
</View>
);
}
}
export class ComponentA extends Component {
componentDidMount() {
this.props.onComponentDidMount && this.props.onComponentDidMount()
}
render() {
return (
<View style={[styles.component, styles.componentA]}>
<Text style={styles.text}>Component A</Text>
</View>
);
}
}
Full code and live demo: https://snack.expo.io/SkIOLJ3eM
use setTimeout in componentDidMount.
here is a sample
constructor(props){
super(props);
this.state={
isBVisible:false
};
}
componentDidMount(){
setTimeout(() => {
this.setState({isBVisible:true});
}, 2000);
}
render(){
return(<View>
<View style={{width:100,height:100,backgroundColor:"red"}}/>
{this.state.isBVisible ? <View style={{width:100,height:100,backgroundColor:"green"}}/>:null}
</View>)
}

How do I call the openDrawer function on DrawerLayoutAndroid from parent component?

I have made a component which sets up the DrawerLayoutAndroid, which I want to call in my index file.
drawer.js:
export default class MenuDrawer extends Component {
constructor(props) {
super(props);
this.openDrawer = this.openDrawer.bind(this);
}
render() {
var navigationView = (
// ...
);
return (
<DrawerLayoutAndroid
ref={(_drawer) => this.drawer = _drawer}
drawerWidth={200}
drawerPosition={DrawerLayoutAndroid.positions.Left}
renderNavigationView={() => navigationView}>
{this.props.children}
</DrawerLayoutAndroid>
);
}
openDrawer() {
this.drawer.openDrawer();
}
}
I then have everything in the render() function in my index file wrapped around since I want the drawer to be accessible from anywhere. I just cannot figure out how I open the drawer from the index file. I have tried several different ways to call the function, but I always end up getting undefined is not an object.
index.android.js
export default class AwesomeProject extends Component {
constructor(props) {
super(props);
this.openMenu = this.openMenu.bind(this);
}
render() {
const { region } = this.props;
return (
<MenuDrawer
ref={(_menudrawer) => this.menudrawer = _menudrawer}
style={styles.layout}>
<TouchableHighlight
style={styles.topbar}
onPress={this.openMenu}>
<Text>Open</Text>
</TouchableHighlight>
</MenuDrawer>
);
}
openMenu() {
this.refs.menudrawer.openDrawer();
}
}
This gives me the error "undefined is not an object (evaluating 'this.refs.menudrawer.openDrawer')".
How do I go about solving this?
Thanks
It looks good, you're just accessing the menudrawer incorrectly. It should be:
this.menudrawer.openDrawer();
Because your ref is:
ref={(_menudrawer) => this.menudrawer = _menudrawer}

Resources