I know how to handle this inside a function, but in my case none of those solutions works and I still get
this is undefined
The problem is I have a function inside render() method and I dont know how to handle it.
This is a part of my code
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
}
this.changePage = this.changePage.bind(this);
}
changePage (event) {
// some codes
}
render() {
function PrevPage() {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
)
}
return (
<div>
<PrevPage />
...
</div>
)
}
}
I get the error at this line
<li key="n-page" onClick={this.changePage}>
I tried putting this line in constructor:
this.PrevPage= this.PrevPage.bind(this);
but PrevPage is not recognized by this.
Also if i convert PervPage to arrow function:
PrevPage = () => {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
)
}
I get this error:
'PrevPage' is not defined no-undef
I know I'm missing somthing but I cant figure out what
Why not just remove the function outside the render? Generally speaking you want to keep the render logic as clean as possible and your PrevPage component can really just be a normal method. Seems kind of superfluous to define a child-component within render, and then return it immediately.
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
}
this.changePage = this.changePage.bind(this);
this.prevPage = this.prevPage.bind(this);
}
changePage (event) {
// some codes
}
prevPage() {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
)
}
render() {
return (
<div>
{this.prevPage()}
...
</div>
)
}
}
Or if you want, just create a brand-new component for PrevPage, and pass down the changePage handler as props. The changeHandler will still be bound to the Pagination component context.
PrevPage.js
const PrevPage = (props) => {
return (
<li key="p-page" onClick={props.changePage}>
<
</li>
)
}
export default PrevPage
Pagination.js
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
}
this.changePage = this.changePage.bind(this);
}
changePage (event) {
// some codes
}
render() {
return (
<div>
<PrevPage changePage={this.changePage}/>
</div>
)
}
}
You have multiple options:
bind PrevPage inside render:
render() {
function PrevPage() {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
)
}
PrevPage = PrevPage.bind(this);
return (
<div>
<PrevPage />
...
</div>
)
}
}
Save this in a different variable:
render() {
const that = this;
function PrevPage() {
return (
<li key="p-page" onClick={that.changePage}>
<
</li>
)
}
return (
<div>
<PrevPage />
...
</div>
)
}
}
Your PrevPage should be a separate component. It is not a good pattern to define a new component in the render function.
You can define it as a function component:
const PrevPage => (props) {
return (
<li key="p-page" onClick={props.onChangePage}>
<
</li>
)
}
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
}
this.changePage = this.changePage.bind(this);
}
changePage (event) {
// some codes
}
render() {
return (
<div>
<PrevPage onChangePage={this.changePage}/>
...
</div>
)
}
}
What you've defined is a private function inside render method, therefore your attempt to bind it in the constructor not works, you have 2 choices:
Convert PrevPage function to arrow-function.
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
};
this.changePage = this.changePage.bind(this);
}
changePage(event) {
// some codes
}
render() {
const PrevPage = () => {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
);
};
return (
<div>
<PrevPage />
...
</div>
);
}
}
Make PrevPage function as a class method, and then bind it in the constructor as you did - this method will preform better, cause you won't make a closure each time render method is called.
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
};
this.changePage = this.changePage.bind(this);
this.renderPrevPage = this.renderPrevPage.bind(this);
}
changePage(event) {
// some codes
}
render() {
return (
<div>
{this.renderPrevPage()}
...
</div>
);
}
renderPrevPage() {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
);
}
}
There are 2 issues in your code.
You are creating a function PrevPage in render method and calling it as a component instead you have to call it as a function.
You are not binding the method changePage correctly to OnClick.
This is how I will write this code.
render() {
function PrevPage() {
return (
<li key="p-page" onClick={ () => this.changePage }>
<
</li>
)
}
return (
<div>
{PrevPage()}
...
</div>
)
I will suggest you to write PrevPage function outside the render method and call it like this.
{this.PrevPage}
Hope it helps you.
You have to bind you function to contructor and call the function on your li tag as an arrow function and also put your PrevPage function outside render function like this:
class Pagination extends React.Component {
constructor(props) {
super(props);
this.state = {
//states
}
this.changePage = this.changePage.bind(this);
this.PrevPage = this.PrevPage.bind(this);
}
changePage (event) {
// some codes
}
PrevPage() {
return (
<li key="p-page" onClick={this.changePage}>
<
</li>
)
}
render() {
return (
<div>
{ this.PrevPage() }
...
</div>
)
}
}
Hope it helps
try
<li key="n-page" onClick={() => this.changePage()}>
change it to arrow function to get "this" instance like in below code,
changePage = (event) => {
/* here you can access to "this" instance
eg: this.setState({ pageName:'myPage' )}
*/
}
Related
I faced the problem of passing props while coding in React. Yes, I have seen this issue before, but this time it's a second level-children component and things are a bit weird. My code (comments along the way):
class EditForm extends React.Component {
handleSubmit = (event, idx) => {
event => event.preventDefault();
postData('/', {idx: idx})
.then(data => {if (data.success) {window.location = '/';}});
console.log(idx); // results printed from here
}
render() {
return (
<Form
onFinish={() => this.handleSubmit(idx)} // output 1
onFinish={() => this.handleSubmit(this.props.idx)} // output 2
>
</Form>
);
}
}
class UpdateModal extends React.Component {
render() {
return (
<Modal>
<EditForm idx={ this.props.idx } /> // value is still not undefined
</Modal>
);
}
}
Outputs:
// 1
useForm.js:766 ReferenceError: idx is not defined
// 2
undefined
Can anyone please explain why I can't pass the props two times in a row? As a matter of fact, the values are still valid when they are in UpdateModal but is gone somehow afterward.
Thanks in advance.
You should pass in the event object to your handlers:
class EditForm extends React.Component {
handleSubmit = (event, idx) => {
event => event.preventDefault();
postData('/', {idx: idx})
.then(data => {if (data.success) {window.location = '/';}});
console.log(idx); // results printed from here
}
render() {
return (
<Form
onFinish={(event) => this.handleSubmit(event, idx)} // output 1
onFinish={(event) => this.handleSubmit(event, this.props.idx)} // output 2
>
</Form>
);
}
}
class UpdateModal extends React.Component {
render() {
return (
<Modal>
<EditForm idx={ this.props.idx } /> // value is still not undefined
</Modal>
);
}
}
class EditForm extends React.Component {
constructor(props) {
super(props);
}
// ...
}
class UpdateModal extends React.Component {
constructor(props) {
super(props);
}
// ...
}
// <EditForm idx={this.state.idx}></EditForm>
// <UpdateModal idx={this.state.idx}></UpdateModal>
I'm using refs for calculating block height in child component, it works fine inside, so after each removeHandler() function "doCalculating" is called
But if I tried to call this into parent component, doCalculating() always return the initial value. Like just after componentDidMount()
Seems like doCalculating() into parent component refers to this.refs.tagList.clientHeight just once and not recalc even after child component update
React version 14.7 is used here, so I cannot use hooks
class ChildComponent extends Component {
componentDidMount() {
this.doCalculating()
}
doCalculating = () => {
const defaultHeight = 50
const newHeight = this.refs.tagList.clientHeight
if (newHeight > defaultHeight ) {
// do logic
}
}
render() {
return (
<ul
ref={"tagList"}
>
{array.map((item, index) => (
<li key={index}>
<button>
{item}
<span onClick={
(e) => {
this.removeHandler()
this.doCalculating()
}
} ></span>
</button>
</li>
)
)}
</ul>
)
}
}
class ParentComponent extends Component {
actionFunc = () => {
// some logic
// call recalculate function, that always return initial value
this.responsesTags.doCalculating()
}
render() {
return (
<div>
<ChildComponent
ref={instance => { this.responsesTags = instance }}
/>
<button onClick={() => this.actionFunc()} />
</div>
)
}
}
What is missing to recalculate a function when called in the parent component?
In my opinion your code works correctly, I've fiddle with your example (a little different), maybe it will be useful to you: https://jsfiddle.net/tu7vxfym/ . If I calculate height of the ul from child and parent component it will calculate correctly.
class ChildComponent extends React.Component {
constructor(){
super();
this.doCalculating = this.doCalculating.bind(this);
this.addDiv = this.addDiv.bind(this);
this.state = {
list: [],
height:undefined
}
}
componentDidMount() {
this.doCalculating()
}
doCalculating (){
const defaultHeight = 50
const newHeight = this.refs.tagList.clientHeight;
this.setState(state=>{
return state.height = this.refs.tagList.clientHeight
})
console.log(newHeight)
}
addDiv(){
this.setState(function(state){
return state.list.push(this.refs.tagList.clientHeight)
})
}
render() {
return (
<div>
<ul ref={"tagList"}>
{this.state.list.map((e,i)=>{
return (<li key={i}>{e}</li>)
})}
</ul>
<h1>Calculated height: {this.state.height}</h1>
<button onClick={this.addDiv}>Add list</button>
<button onClick={this.doCalculating}>Child button</button>
</div>
)
}
}
class ParentComponent extends React.Component {
constructor(){
super();
this.actionFunc = this.actionFunc.bind(this)
}
actionFunc(){
this.responsesTags.doCalculating()
}
render() {
return (
<div>
<ChildComponent ref={instance => { this.responsesTags = instance }}/>
<button onClick={this.actionFunc}>Parent button</button>
</div>
)
}
}
I have a tab navigation at the top of my page, and I want to add an 'active' class to the tab when it's clicked and make sure it's not on any of the other tabs.
So far what I have adds the 'active' class to the first tab, but doesn't update if you click on any of the other tabs. So the first tab is always the active tab regardless of what you click on.
import React from 'react'
import { string } from 'prop-types'
class TabNav extends React.Component {
constructor(props) {
super(props)
this.state = {
currentTab: ''
}
this.handleClick = this.handleClick.bind(this)
this.createTabItems = this.createTabItems.bind(this)
}
shouldComponentUpdate (nextProps, nextState) {
return nextProps.navItems !== this.props.navItems
}
handleClick (currentTab) {
this.setState({
currentTab: currentTab
})
this.createTabItems()
}
createTabItems () {
const { navItems = false } = this.props
if (!navItems) return false
const splitItems = navItems.split(',')
if (!splitItems.length) return false
return splitItems.map((item, currentTab) => {
const items = item.split('_')
if (items.length !== 3) return null
const itemLink = items[1]
return (
<li key={currentTab} className={this.state.currentTab == currentTab ? 'side-nav-tab active' : 'side-nav-tab'} onClick={this.handleClick.bind(this, currentTab)}>
<a href={itemLink}>
<p>{ items[0] }</p>
</a>
</li>
)
})
}
render () {
const tabItems = this.createTabItems()
if (!tabItems) return null
return (
<div>
<ul id='tabNavigation'>
{tabItems}
</ul>
</div>
)
}
}
TabNav.propTypes = {
navItems: string.isRequired
}
export default TabNav
I have also tried calling this.createTabItems asynchronously in setState to try and force an update but that didn't work:
handleClick (currentTab) {
this.setState({
currentTab: currentTab
}, () => this.createTabItems)
}
I think your shouldComponentUpdate is causing the issue. Can you try removing it? Also, you don't need to call this.createTabItems in your handleClick()
I feel like a mindset shift is required here to think more declaratively, you don't need to tell React when you want to create the tabs, it will determine this itself from the render method and the data that's passed to it.
I've removed your componentShouldUpdate function because React will already do the comparison of those props for you. I've also tracked the selected item by index because it simplifies the logic in my mind.
Try something like this:
import React from 'react';
import { string } from 'prop-types';
class TabNav extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
handleClick = index => {
this.setState({
selectedIndex: index
});
};
render() {
const { navItems = [] } = this.props;
return (
<div>
<ul id="tabNavigation">
{navItems.map((navItem, index) => {
if (navItem.length !== 3) return;
const [text, link] = navItem;
return (
<li
className={
this.state.selectedIndex === index
? 'side-nav-tab active'
: 'side-nav-tab'
}
onClick={this.handleClick.bind(null, index)}
>
<a href={link}>
<p>{text}</p>
</a>
</li>
);
})}
}
</ul>
</div>
);
}
}
TabNav.propTypes = {
navItems: string.isRequired
};
export default TabNav;
There are a couple other changes in there, like destructuring from the array rather than using indexes so it's clearer what the properties you're pulling out of a navItem array are.
I also used a fat arrow function for the handleClick function because it means you don't have to bind to this in your constructor.
You got two problems, first the way you are using this in your function and secondly your shouldUpdateComponent, just like the other mentioned before me. If you want to reference your class using this you need to use arrow functions. remember that the ES6 arrow function uses lexical scoping which means that this references the code that contains the function. I made the changes in the code below with a working example.
class TabNav extends React.Component {
constructor(props) {
super(props)
this.state = {
currentTab: ''
}
this.handleClick = this.handleClick.bind(this)
this.createTabItems = this.createTabItems.bind(this)
}
handleClick (currentTab) {
this.setState({
currentTab: currentTab
})
}
createTabItems = () => {
const { navItems = false } = this.props
if (!navItems) return false
const splitItems = navItems.split(',')
if (!splitItems.length) return false
return splitItems.map((item, currentTab) => {
const items = item.split('_')
if (items.length !== 3) return null
const itemLink = items[1]
return (
<li key={currentTab} className={this.state.currentTab == currentTab ? 'side-nav-tab active' : 'side-nav-tab'} onClick={this.handleClick.bind(null, currentTab)}>
<a href={itemLink}>
<p>{ items[0] }</p>
</a>
</li>
)
})
}
render () {
const tabItems = this.createTabItems()
if (!tabItems) return null
return (
<div>
<ul id='tabNavigation'>
{tabItems}
</ul>
</div>
)
}
}
class Nav extends React.Component {
render() {
return(
<TabNav navItems={'Test1_#_Test1,Test2_#_Test2'} />)
}
}
ReactDOM.render(<Nav />, document.getElementById('root'))
.active {
font-size: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
.
I have 3 components. App.js - Main. localLog.jsx stateless, LoadBoard.jsx statefull. I want to Take string of data from LoadBoard and display it in localLog.jsx. The problem is that I can't figure it out why LocalLog is not displaying on screen.
console.log(this.data.Array) in App.jsx localLog is ["configuration"]
(2) ["configuration", "It's good configuration"]
App.jsx
class App extends Component {
constructor(props) {
super(props);
this.dataArray = [];
this.state = {
headers: []
};
this.localLog = this.localLog.bind(this);
}
localLog(data) {
if (data) {
this.dataArray.push(data);
console.log(this.dataArray);
this.dataArray.map(data => {
return <LocalLog info={data} />;
});
}
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.localLog} />
<pre id="log_box">{this.localLog()}</pre>
</>
);
}
}
localLog.jsx
let localLog = props => {
return (
<pre className={classes.background}>
<ul className={classes.ul}>
<li>{props.info}</li>
<li>hello world</li>
</ul>
</pre>
);
};
export default localLog;
LoadBoard.jsx
class LoadBoard extends Component {
constructor(props) {
super(props);
this.state = {
positionToId: []
};
}
componentDidMount() {
this.props.localLog("configuration");
this.props.localLog(`It's good configuration`);
}
render() {
return (
<div>
<h1>Nothing interesting</h1>
</div>
);
}
}
You are not returning anything from the localLog method, should be:
return this.dataArray.map(data => {
return <LocalLog info={data} />;
});
EDIT:
here is what your App component should look like.
class App extends Component {
constructor(props) {
super(props);
this.state = {
headers: [],
logs: []
};
this.addLog = this.addLog.bind(this);
}
// Add log to state
addLog(log) {
this.setState(state => ({
...state,
logs: [...state.logs, log]
}));
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.addLog} />
<pre id="log_box">
{this.state.logs.map(log => {
return <LocalLog info={log} />;
})}
</pre>
</>
);
}
}
you should use setState method in order to re-render the component.
you can try this.
class App extends Component {
constructor(props) {
super(props);
this.state = {
headers: [],
dataArray: []
};
this.localLog = this.localLog.bind(this);
}
localLog(data) {
if (data) {
this.state.dataArray.push(data);
this.setState({dataArray: this.state.dataArray})
}
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.localLog} />
<pre id="log_box">{this.state.dataArray.map(i => <LoaclLog info={i}/>)}</pre>
</>
);
}
}
I try to map an array and put click event on the array items. I know it's a bit different because of how JavaScript handles functions but I can't make it work. I get the error: Cannot read property 'saveInStorage' of undefined. Can anyone tell me what I'm doing wrong? Thanks in advance! Here is my code:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
this is undefined in renderUser()
You need to bind this for renderUser() in your constructor.
Also, you are calling saveInStorage() every time the component is rendered, not just onClick, so you'll need to use an arrow function in renderUser
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this);
this.renderUser = this.renderUser.bind(this);
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
Instead of binding you can also use an arrow function (per mersocarlin's answer). The only reason an arrow function will also work is because "An arrow function does not have its own this; the this value of the enclosing execution context is used" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The enclosing execution in your case is your render, where this is defined.
You need to make two changes to your code which are outlined below.
You are invoking the function when the component is rendered. To fix this update this line to the following
<p key={i} onClick={() => this.saveInStorage(user)}>
This means that the function will only be invoked when you click on the item.
You also need to bind the renderUser in your constructor or else use an arrow function.
this.renderUser = this.renderUser.bind(this);
See working example here.
Your onClick event handler is wrong.
Simply change it to:
onClick={() => this.saveInStorage(user)}
Don't forget to also bind renderUser in your constructor.
Alternatively, you can choose arrow function approach as they work the same as with bind:
class Gebruikers extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
}
}
saveInStorage = (e) => {
alert("test")
}
renderUser = (user, i) => {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
{user.name}
</p>
)
}
render() {
return (
<div>{this.state.users.map(this.renderUser)}</div>
);
}
}
ReactDOM.render(
<Gebruikers />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Paul Fitzgeralds answer is the correct one, although I'd like to propose a different way of handling this, without all the binding issues.
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
}
saveInStorage = (e) => {
console.log("test");
};
render() {
return (
<div>
{this.state.users.map((user, i) => {
return (<p key={i} onClick={() => this.saveInStorage(user)}>f</p>);
})}
</div>
);
}
}
With saveInStorage = (e) => {}; you are binding the saveInStorage function to the this context of your class. When invoking saveInStorage you'll always have the (at least I guess so in this case) desired this context.
The renderUser function is basically redundant. If you return one line of JSX, you can easily do this inside your render function. I think it improves readability, since all your JSX is in one function.
You are not sending the parameters to this.renderUser
this.state.users.map((user, i) => this.renderUser(user, i))
Also your onClick function should be slightly changed. Here's the full code changed:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map((user, i) => this.renderUser(user, i))
}
</div>
);
}
}