ReactJS - Rerender component - html

I'm trying to create a menu in ReactJS that has a SideBar with icons and also a BurgerMenu in which I display titles that match the icons. When selecting a menu item, either from the SideBar or the BurgerMenu, it changes its color. If I select an item from the burger menu, everything works fine, but if I select it from the Sidebar, then in the burger menu, the previous item remains colored. It seems that the items in the burger menu are not rerendered and I can't find a solution to this.
Here is the code:
import React from 'react';
import styled from "styled-components";
import NavItem from "./NavItem";
import BurgerSideNav from "./burger-nav/BurgerSideNav";
/* This defines the actual bar going down the screen */
const StyledSideNav = styled.div`
position: fixed; /* Fixed Sidebar (stay in place on scroll and position relative to viewport) */
height: 100%;
width: 75px; /* Set the width of the sidebar */
z-index: 1; /* Stay on top of everything */
top: 3.4em; /* Stay at the top */
background-color: #222; /* Black */
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 10px;
`;
class SideNav extends React.Component {
constructor(props) {
super(props);
this.state = {
activePath: this.props.activePath,
items: [
{
path: '/', /* path is used as id to check which NavItem is active basically */
name: 'Home',
css: 'fa fa-fw fa-home',
key: 1 /* Key is required, else console throws error. Does this please you Mr. Browser?! */
},
{
path: '/news',
name: 'News',
css: 'fas fa-newspaper',
key: 2
},
{
path: '/info',
name: 'Info',
css: 'fas fa-info',
key: 3
},
{
path: '/profile',
name: 'Profile',
css: 'fas fa-id-card',
key: 4
},
{
path: '/coordinator',
name: 'Coordinator',
css: 'fas fa-user-tie',
key: 5
},
{
path: '/contact',
name: 'Contact',
css: 'fas fa-address-book',
key: 6
},
]
}
}
onItemClick = (path) => {
this.setState({activePath: path}); /* Sets activePath which causes rerender which causes CSS to change */
};
render() {
const { items, activePath } = this.state;
return (
<div>
<StyledSideNav>
<BurgerSideNav
activePath = {activePath}
onItemClick={this.onItemClick}
/>
{
/* items = just array AND map() loops thru that array AND item is param of that loop */
items.map((item) => {
/* Return however many NavItems in array to be rendered */
return (
<NavItem
path={item.path}
name={item.name}
css={item.css}
onItemClick={this.onItemClick} /* Simply passed an entire function to onClick prop */
active={item.path === activePath}
key={item.key}
/>
)
})
}
</StyledSideNav>
</div>
);
}
}
export default SideNav
import React from "react"
import "../sideNav.css"
import BurgerNavItem from "./BurgerNavItem";
class BurgerSideNav extends React.Component {
constructor(props) {
super(props);
this.state = {
showNav: false,
activePath: this.props.activePath,
items: [
{
path: '/',
name: 'Acasa',
css: 'fa fa-fw fa-home',
key: 1
},
{
path: '/news',
name: 'Noutati',
css: 'fas fa-newspaper',
key: 2
},
{
path: '/info',
name: 'Despre lucrare',
css: 'fas fa-info',
key: 3
},
{
path: '/profile',
name: 'Profil student',
css: 'fas fa-id-card',
key: 4
},
{
path: '/coordinator',
name: 'Coordonator',
css: 'fas fa-user-tie',
key: 5
},
{
path: '/contact',
name: 'Contact',
css: 'fas fa-address-book',
key: 6
},
]
};
}
openNavClick = e => {
e.preventDefault();
this.openNav()
};
closeNavClick = e => {
e.preventDefault();
this.closeNav()
};
openNav = () => {
this.setState({
showNav: true
});
document.addEventListener("keydown", this.handleEscKey)
};
closeNav = () => {
this.setState({
showNav: false
});
document.removeEventListener("keydown", this.handleEscKey)
};
handleEscKey = e => {
if (e.key === "Escape") {
this.closeNav()
}
};
onItemClick = (path) => {
const {onItemClick} = this.props;
this.setState({ activePath: path });
onItemClick(path);
};
render() {
const { items, activePath, showNav } = this.state;
let navCoverStyle = { width: showNav ? "100%" : "0" }
let sideNavStyle = { width: showNav ? "250px" : "0" }
return (
<React.Fragment>
<span onClick={this.openNavClick}>
<i className="fas fa-bars open-nav"/>
</span>
<div
onClick={this.navCoverClick}
class="nav-cover"
style={navCoverStyle}
/>
<div name="side-nav" class="side-nav" style={sideNavStyle}>
<a href="#" onClick={this.closeNavClick} class="close-nav">
×
</a>
{
items.map((item) => {
return (
<BurgerNavItem
path={item.path}
name={item.name}
css={item.css}
onItemClick={this.onItemClick}
active={item.path === activePath}
key={item.key}/>
)
})
}
</div>
})
</React.Fragment>
)
}
}
export default BurgerSideNav

If I understand the question correctly, if you want to re-render, you can pass the activePath as part of the key to easily force a re-render:
return (
<NavItem
path={item.path}
name={item.name}
css={item.css}
onItemClick={this.onItemClick}
active={item.path === activePath}
key={`${item.key}-${activePath}`}
/>
);
One thing to note is that you probably only need activePath defined in the Parent Component (SideNav), not in both if you don't want both to be styled, hence why both stay 'enabled'.
Also, your items won't change, so they should be constant somewhere else, instead of being part of your state. Maybe in a constants file:
// PathContants.js
export default PathConstants = [
{
id: 1,
path: '/home',
},
..
];
If the paths are always unique, you can just use the path itself, as a key key={item.path}, since the key is actually a string. No need to store the key itself.

in BurgerSideNav component you have set the state in the constructor function saying
activePath: this.props.activePath
but when the state changes in the StyledSideNav component you are not setting the state again, to set the state again, implement componentWillReceiveProps
eg:
componentWillReceiveProps(nextProps) {
if (this.props.activePath !== nextProps.activePath) {
this.setState({
activePath: nextProps.activePath
})
}
}

Related

Mouse Out of Element and Mouse Into Another Element Does Not Reset State

Code: https://codesandbox.io/s/objective-darwin-w0i5pk?file=/src/App.js
Description:
This is just 4 gray squares that each get their own shade of gray. I want to change the background color of each square when the user hovers over each, but I want the hover color to be +10 in RGB of what it was originally.
Issue:
When I mouse/hover out of one of the gray squares and mouse/hover into another gray square, the first square does not switch back to its initial color state.
Help:
Can someone explain why it is doing this and how to fix it because I have no idea?
Note:
I am trying not to use CSS for the hover because I am specifying the backgroundColor with JS.
import React, { useState } from "react";
import "./styles.css";
const tabs = [
{ name: "1", img: [] },
{ name: "2", img: [] },
{ name: "3", img: [] },
{ name: "4", img: [] }
];
const initialState = {};
tabs.forEach((t, i) => {
initialState[i] = false;
});
export default function App() {
const [hover, setHover] = useState(initialState);
return (
<div className="App">
{tabs.map((t, i) => {
const v = 50 - (i + 1) * 10;
const val = hover[i] ? v + 10 : v;
return (
<div
key={t.name}
className="tab"
onMouseOver={() => {
setHover({
...hover,
[i]: true
});
}}
onMouseLeave={() => {
setHover({
...hover,
[i]: false
});
}}
onMouseOut={() => {
setHover({
...hover,
[i]: false
});
}}
style={{
backgroundColor: `rgb(${val}, ${val}, ${val})`,
height: "100px",
width: "100px"
}}
>
<p>{t.name}</p>
</div>
);
})}
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
}
This picture only shows the initial state:
setState calls are not what a human would consider "immediate". Instead, the calls to the state setter as queued inside React internal mechanisms. Consider this:
const [state, setState] = useState(0)
// somewhere
setState(state + 1)
setState(state + 1)
In this case, you do not end up with 2 but 1, because while you call setState twice to increment by one, you really are calling it as:
setState(1)
setState(1)
This is the exact issue in your code with the callbacks, you have
// enter
setState({ ...state, [i]: true })
// leave
setState({ ...state, [i]: false })
so when both get called, you apply the "leave" with the wrong previous state.
This is why setState has another pattern, setState(prevState => nextState)
setState(prevState => prevState + 1)
setState(prevState => prevState + 1)
Like this, you do end up with the value 2 because the second call is then using the "correct" previous state.
In your case, you need:
// enter
setState(prevState => ({ ...prevState, [i]: true }))
// leave
setState(prevState => ({ ...prevState, [i]: false }))
This is happening because you also keep the previous values in your state. You should update in this way
onMouseOver={() => {
setHover({
[i]: true
});
}}
onMouseLeave={() => {
setHover({
[i]: false
});
}}
onMouseOut={() => {
setHover({
[i]: false
});
}}

Mapping through an Array of Images created in a local JSON File and rendering it

I am learning RN and I am having trouble setting up a mapping method to go through an array of images of some players that I created in a local JSON file, and render them in their respective profile pages.
This is how I have set my json.
//PlayerImages.js
const PlayerImages = [
{
id: "1",
name: "Cristiano Ronaldo",
images:["https://www.thesun.co.uk/wp-content/uploads/2019/05/NINTCHDBPICT000485852530.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg"]
},
{
id: "2",
name: "Lionel Messi",
images:["https://www.thesun.co.uk/wp-content/uploads/2019/05/NINTCHDBPICT000485852530.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg"]
},
{
id: "3",
name: "Neymar",
images: mages:["https://www.thesun.co.uk/wp-content/uploads/2019/05/NINTCHDBPICT000485852530.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg"]
},
{
id: "4",
name: "Gabriel de Jesus",
images:["https://i.pinimg.com/474x/f1/36/ca/f136ca04817e60fa12f4a5680101ff8b.jpg",
"https://i.pinimg.com/474x/b1/da/e2/b1dae2fe6ca1620e5d1949a2dcd33a0c.jpg",
"https://i.pinimg.com/564x/7b/53/32/7b5332ef6a981b3c54e855495ea1c828.jpg"]
},
{
id: "5",
name: "Roberto Firmino",
images:mages:["https://www.thesun.co.uk/wp-content/uploads/2019/05/NINTCHDBPICT000485852530.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg",
"https://e00-marca.uecdn.es/assets/multimedia/imagenes/2019/05/18/15582064666477.jpg"]
}
]
export default PlayerImages;
This is how I have the ImageGallery component set up. Unfortunately, the way I have componentDidMount set up, shows Cristiano Ronaldos images in all profiles. How can I map it in order to make sure that each profile has the pictures of that particular player when you tap the gallery button on their profile?
//ImageGallery.js
import React, { Component } from "react";
import { StyleSheet, Text, View } from "react-native";
import { SliderBox } from "react-native-image-slider-box";
import { withNavigation } from "react-navigation";
import PlayerImages from "../Data/PlayerImages";
class ImageGallery extends React.Component {
static navigationOptions = {
title: "Player Gallery",
headerStyle: {
backgroundColor: "#53b4e6"
},
headerTintColor: "#f6c945",
headerTitleStyle: "bold"
};
constructor(props) {
super(props);
this.state = {
images: []
};
}
componentDidMount() {
let images = PlayerImages[0].images;
this.setState({ images });
}
render() {
return (
<View style={styles.container}>
<SliderBox
images={this.state.images}
sliderBoxHeight={900}
onCurrentImagePressed={index =>
console.warn(`image ${index} pressed`)
}
dotColor="yellow"
inactiveDotColor="white"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
export default withNavigation(ImageGallery);
Finally, when you are on the players profile, you should have access to their gallery by tapping the button in the header. As I mentioned earlier in the post, when you tap it, you get only the images of Cristiano Ronaldo.
//PlayerProfile.js
headerRight: (
<Button
onPress={() => navigation.navigate("ImageGallery")}
title="Gallery"
color="#f6c945"
/>
)
Pass the id of the player or the index of the player you want to display images of using navigation parameters , then use it to get the images of the player, right now you are using images at the 0th index of array.
componentDidMount() {
let images = PlayerImages[0].images;
this.setState({ images });
}
change this to
componentDidMount() {
let images = PlayerImages[index].images; //index is the index of the player whose images you want to show
this.setState({ images });
}

ReactGridLayout.children[0].y must be a number

I get the following error message when I'm trying to run the website in my development environment:
Uncaught Error: ReactGridLayout:
ReactGridLayout.children[0].y must be a number!
at validateLayout (app.js:6171)
at app.js:6132
at forEachSingleChild (app.js:62734)
at traverseAllChildrenImpl (app.js:62638)
at traverseAllChildrenImpl (app.js:62654)
at traverseAllChildren (app.js:62709)
at Object.forEachChildren [as forEach] (app.js:62754)
at synchronizeLayoutWithChildren (app.js:6117)
at ReactGridLayout._initialiseProps (app.js:40638)
at new ReactGridLayout (app.js:40089)
There is also an error telling me this:
app.js:77841 The above error occurred in the component:
in ReactGridLayout (created by ResponsiveReactGridLayout)
in ResponsiveReactGridLayout (created by WidthProvider)
in WidthProvider (created by Grid)
in div (created by Grid)
in Grid (created by Test)
in Test
This is my Test.js file:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import '../../../public/css/app.css';
import '../../../public/css/all.css';
import Grid from '../components/Grid';
class Test extends Component{
render() {
return (
<Grid/>
)
}
}
export default Test;
if (document.getElementById('example')) {
ReactDOM.render(<Test />, document.getElementById('example'));
}
This is my Grid.jsx file:
import '../../../public/css/all.css';
import React from 'react';
import _ from "lodash";
import {WidthProvider, Responsive} from 'react-grid-layout';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
import Clock from './Clock.jsx';
import Weather from './Weather.jsx';
const ResponsiveReactGridLayout = WidthProvider(Responsive);
const originalLayouts = getFromLS("layouts") || [];
/* This class generates the layout for the web app. It renders the grid
* and it's items, but also button's and a dropdown menu, to control the grid.
*/
class Grid extends React.PureComponent {
static defaultProps = {
className: "layout",
cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
rowHeight: 100,
autoSize: true,
};
constructor(props) {
super(props);
this.state = {
items: originalLayouts.map(function(i, key, list) {
return {
i: originalLayouts[key].i,
x: originalLayouts[key].x,
y: originalLayouts[key].y,
w: originalLayouts[key].w,
h: originalLayouts[key].h,
widget: originalLayouts[key].widget,
minW: originalLayouts[key].minW,
minH: originalLayouts[key].minH,
maxH: originalLayouts[key].maxH
};
}),
selectedOption: '',
newCounter: originalLayouts.length
};
this.onAddItem = this.onAddItem.bind(this);
this.onBreakPointChange = this.onBreakPointChange.bind(this);
this.onLayoutChange = this.onLayoutChange.bind(this);
this.onLayoutReset = this.onLayoutReset.bind(this);
}
/* This function renders all grid items in the layout array. It creates a div
* with a remove button, and content. The content managed by a switch statement,
* which output is based on the widget property from the grid items.
*/
createElement(el) {
const removeStyle = {
position: 'absolute',
right: '2px',
top: 0,
cursor: 'pointer'
};
const i = el.i;
const widget = el.widget;
return (
<div key={i} data-grid={el}>
{(() => {
switch(widget) {
case 'Clock':
return <Clock/>;
case 'Photo':
return <div className='photo'></div>;
case 'Weather':
return <Weather/>;
default:
return <span>{widget}</span>;
}
})()}
<span
className='remove'
style={removeStyle}
onClick={this.onRemoveItem.bind(this, i)} >
x
</span>
</div>
);
}
/* The onAddItem() function is called when the user clicks on the 'Add Item' button.
* It adds a new grid item to the state, and takes the selected item in the dropmenu
* into account. This way the correct widget is loaded by the createElement() function.
*/
onAddItem() {
var selection = this.state.selectedOption ? this.state.selectedOption : 0;
var widgetProps = returnProps(selection.value);
if(selection) {
console.log('adding', 'n' + this.state.newCounter + '; ' + selection.value);
} else {
console.log('adding', 'n' + this.state.newCounter + '; empty');
}
this.setState({
items: this.state.items.concat({
i: 'n' + this.state.newCounter,
x: (this.state.items.length * 2) % (this.state.cols || 12),
y: Infinity,
w: widgetProps.w,
h: widgetProps.h,
widget: selection ? selection.value : '',
minW: widgetProps.minW,
minH: widgetProps.minH,
maxH: widgetProps.maxH,
}),
newCounter: this.state.newCounter + 1
});
}
/* onLayoutReset() is called when the user clicks on the 'Reset Layout' button.
* It clears the localStorage and then issues a window refresh.
*/
onLayoutReset() {
localStorage.clear();
window.location.reload();
}
/* Calls back with breakpoint and new # cols */
onBreakPointChange(breakpoint, cols) {
this.setState({
breakpoint: breakpoint,
cols: cols
});
}
/* Is called whenever the layout is changed. The for loop adds widget attribute
* from items array to objects in layout array, so that the widget props
* are also saved to localStorage. This is because objects in the layout array
* do not include a widget property by default.
*/
onLayoutChange(layout) {
this.setState({ layout: layout });
for (var i = 0; i < this.state.items.length; i++) {
layout[i].widget = this.state.items[i].widget;
}
saveToLS('layouts', layout);
}
/* When a user presses the little 'x' in the top right corner of a grid item,
* this function is called. It removes the corresponding grid item.
*/
onRemoveItem(i) {
this.setState({ items: _.reject(this.state.items, {i: i }) });
}
/* handleChange passes the selected dropdown item to the state. */
handleChange = (selectedOption) => {
this.setState({ selectedOption });
if (selectedOption) {
console.log(`Selected: ${selectedOption.label}`);
}
};
/* This render function, renders the grid, dropdown-menu, 'Add Item'-button
* and 'Reset Layout'-button. This is also where the createElement() function
* is called for each grid item.
*/
render() {
const { selectedOption } = this.state;
return (
<div>
<div className='widgetselecter'>
<Select className='dropdown'
name="form-field-name"
value={selectedOption}
onChange={this.handleChange}
options={[
{ value: 'one', label: 'One' },
{ value: 'Clock', label: 'Clock' },
{ value: 'Photo', label: 'Photo' },
{ value: 'Weather', label: 'Weather' },
]}
/>
<button className='addButton' onClick={this.onAddItem}>Add Item</button>
<button className='reset' onClick={this.onLayoutReset}>Reset Layout</button>
<span className='title'>/Dash</span>
</div>
<ResponsiveReactGridLayout
onLayoutChange={this.onLayoutChange}
onBreakPointChange={this.onBreakPointChange}
{...this.props}>
{_.map(this.state.items, el => this.createElement(el))}
</ResponsiveReactGridLayout>
</div>
);
}
}
/* Retrieve layout from local storage. */
function getFromLS(key) {
let ls = {};
if (global.localStorage) {
try {
ls = JSON.parse(global.localStorage.getItem("rgl-8")) || {};
} catch (e) {
/*Ignore*/
}
}
return ls[key];
}
/* Save layout to local storage. */
function saveToLS(key, value) {
if (global.localStorage) {
global.localStorage.setItem(
"rgl-8",
JSON.stringify({
[key]: value
})
);
}
}
/* returnProps function returns widget-specific properties like width, min width,
* heigth, etc.
*/
function returnProps(selection) {
switch(selection) {
case 'Clock':
return {
w: 1.5,
h: 1,
minW: 1.5,
minH: 1,
maxH: 1000
};
case 'Weather':
return {
w: 3,
h: 3,
minW: 3,
minH: 3,
maxH: 3
};
default:
return {
w: 2,
h: 2,
minW: 1,
minH: 1,
maxH: 1000,
};
}
}
export default Grid;
I can't remember that I changed anything in the code and I also can't find anything related to the error message on Google. Can anyone tell me more about it or explain it to me? So i can look for a solution.
Seems I had to change this bit of code:
<ResponsiveReactGridLayout
onLayoutChange={this.onLayoutChange}
onBreakPointChange={this.onBreakPointChange}
{...this.props}>
{_.map(this.state.items, el => this.createElement(el))}
>
</ResponsiveReactGridLayout>
to this:
<ResponsiveReactGridLayout
{...this.props}
onBreakpointChange={this.onBreakpointChange}
onLayoutChange={this.onLayoutChange}>
{_.map(this.state.items, el => this.createElement(el))}
</ResponsiveReactGridLayout>
I think it has something to do with the order of rules of code and then especially this part:
>
{_.map(this.state.items, el => this.createElement(el))}
because this piece is outside the <ResponsiveReactGridLayout> now. I'm not sure if this is the right solution but it works for me. So if anyone has some additional information let me know please.

How to let one component correspond with the other for a specific function

I've got a component where I click a color of a machine, when I change colors, the machine gets loaded with a different color inside a image carousel.
Now I also created a component in the bottom with a image gallery of the same machine. How can I make it that the image gallery also changes color when I click the color button in the top of the page?
Important notice: The two components are not in the same parent component but they do load in the same machine images already, so the methods are not wrong I believe.
this is the clickable color button:
<li
v-for="(color, index) in machine.content[0].machine_colors"
:key="color.color_slug"
v-if="color.inStock"
v-on:click="selectColor(index)"
v-bind:class="{ active: (color.color_slug === selectedColor.color_slug)}">
<img v-bind:src="color.color_dash">
</li>
this is the component that changes color:
<div class="product__carousel">
<Carousel showIcon v-if="selectedColor" :machineColor="selectedColor"/> <!-- Image carousel gets loaded in -->
</div>
and the component that needs to change color but does not:
<div id="tab-two-panel" class="panel">
<footerGallery v-if="selectedColor && machine" :machineColor="selectedColor"/>
</div>
Heres the script of the partent component:
export default {
name: 'aboutMachine',
components: {
Collapse,
footerGallery,
},
data() {
return{
selectedColor: this.getMachineColorContent(),
}
},
props: {
main: {
default () {
return {};
},
},
machine: {
default () {
return {};
},
},
},
methods: {
getMachineColorContent() {
if (this.selectedColor) {
return null;
}
return this.machine.content[0].machine_colors[0];
},
selectColor(index) {
this.selectedColor = this.machine.content[0].machine_colors[index];
},
},
}
and the component itself:
export default {
name: 'footerGallery',
props: {
showIcon: Boolean,
machineColor: {
default () {
return {};
},
},
},
data() {
return {
highLightedThumbIndex: 0,
isActive: undefined,
};
},
created() {
this.highLightedThumbIndex = this.highLightedThumbIndex || 0;
},
methods: {
selectThumb(index) {
this.highLightedThumbIndex = index;
},
},
};
This is my main.js
import Vue from 'vue';
import VueYouTubeEmbed from 'vue-youtube-embed'
import FontAwesome from './libs/fa';
import App from './App';
const eventHub = new Vue();
Vue.use(VueYouTubeEmbed);
Vue.component('font-awesome-icon', FontAwesome);
Vue.config.productionTip = false;
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
template: '<App/>',
});
I would use events to accomplish this. The migration guide to Vue2 has a good short explanation of how to do simple event routing without using a full Vuex solution. In your case, you would declare a global event hub in one of your js files:
var eventHub = new Vue();
In your selectColor method you would emit the index selected:
selectColor(index) {
this.selectedColor = this.machine.content[0].machine_colors[index];
eventHub.$emit("select-color",index);
}
And in the footer, you would register a listener for the select-color event that calls selectThumb with the payload of the event (which is the selected index):
created() {
this.highLightedThumbIndex = this.highLightedThumbIndex || 0;
eventHub.$on("select-color",this.selectThumb);
}

calling function from PhotoGrid render function Library

i am using a PhotoGrid Library in react native to populate the list of photo on my apps. how to call a function from the render function ? it show this error when i call a function called "deva" on my OnPress method in <Button onPress={()=>{this.deva()}}><Text>Bondan</Text></Button> . here is my code...
import React from 'react';
import { StyleSheet, Text, View, WebView, TouchableOpacity, Image, Alert, Dimensions} from 'react-native';
import {DrawerNavigator} from 'react-navigation'
import {Container, Header, Button, Icon, Title, Left, Body, Right, Content} from 'native-base'
import PhotoGrid from 'react-native-photo-grid'
import HomeScreen from './HomeScreen'
export default class Recomended extends React.Component {
constructor() {
super();
this.state = { items: [],
nama : ""
}
}
goToBufetMenu(){
this.props.navigation.navigate("BufetMenu");
}
componentDidMount() {
// Build an array of 60 photos
let items = Array.apply(null, Array(60)).map((v, i) => {
return { id: i, src: 'http://placehold.it/200x200?text='+(i+1) }
});
this.setState({ items });
//this.setState({ nama: "Bondan"});
//this.props.navigation.navigate("BufetMenu");
}
deva() {
Alert.alert('deva');
}
render() {
return (
<Container style={styles.listContainer}>
<PhotoGrid
data = { this.state.items }
itemsPerRow = { 3 }
itemMargin = { 3 }
renderHeader = { this.renderHeader }
renderItem = { this.renderItem }
style={{flex:2}}
/>
</Container>
);
}
renderHeader() {
return(
<Button onPress={()=>{this.deva()}}><Text>Bondan</Text></Button>
);
}
renderItem(item, itemSize) {
return(
<TouchableOpacity
key = { item.id }
style = {{ width: itemSize, height: itemSize }}
onPress = { () => {
this.deva();
}}>
<Image
resizeMode = "cover"
style = {{ flex: 1 }}
source = {{ uri: item.src }}
/>
<Text>{item.src}</Text>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
height: 587,
},
gridText: {
color: '#fff',
textAlign: 'center',
fontStyle: 'normal',
fontSize : 12
},
listContainer: {
height: Dimensions.get('window').height - (Dimensions.get('window').height*53/100),
}
});
You are loosing context of this. You need to either use arrow functions or bind the functions.
Example
constructor() {
super();
this.state = { items: [],
nama : ""
};
this.renderHeader = this.renderHeader.bind(this);
this.renderItem = this.renderItem.bind(this);
}
OR
renderHeader = () => {
// rest of your code
}
renderItem = (item, itemSize) => {
// rest of your code
}
Either change your deva method definition to an arrow function -
deva= () => {
Alert.alert('deva');
}
Or bind the deva method to this inside your constructor
constructor() {
super();
this.state = { items: [],
nama : ""
}
this.deva = this.deva.bind(this)
}
You get the error because when the deva method is invoked using this.deva(), the javascript runtime cannot find the property/function deva on the this it's called with (which is the anonymous callback passed to onPress in this case). But if you bind this to deva beforehand, the correct this is being searched by the javascript runtime.