Call dynamically methods in React - reactjs

I'm trying to call a few methods dynamically in my React component.
so I have this code where I want to call a function stepOne, stepTwo, etc. whenever that step is achieved, but this needs to be dynamically called to add new steps in the future.
However I tried already a couple of methods (hasOwnProperty,typeof this[methodName], this.{methodName}()) and can't get to call the right method.
Here is my code:
class MyComponent extends React.Component<Props,State>{
steps = [
'stepOne',
'stepTwo',
'stepThree',
];
state = {step:1};
stepOne(){
return 'This is Step One';
}
stepTwo(){
return 'This is Step Two';
}
_getContent(){
let content = 'Step not exists';
const methodName = this.steps[this.state.step - 1];
if (typeof this[methodName] === 'function') {
content = this[methodName]();
}
return content;
}
render(){
return '<div>' + this._getContent() + '</div>'
}
}
In this example, I always get undefined in the typeof this[methodName] operation

Try creating the map of functions and bind this context to your created functions
class MyComponent extends React.Component<Props,State>{
constructor(props){
super(props);
this.stepOne = this.stepOne.bind(this);
this.stepTwo = this.stepTwo.bind(this);
this.funcMap = {
'1': this.stepOne,
'2': this.stepTwo
};
this.state = {step: '1'};
}
stepOne(){
return 'This is Step One';
}
stepTwo(){
return 'This is Step Two';
}
_getContent(){
let content = 'Step not exists';
const method = this.funcMap[this.state.step];
if (typeof method === 'function') {
content = method();
}
return content;
}
render(){
return '<div>' + this._getContent() + '</div>'
}
}

Related

Ionic 4: 'Typescript error' in helperService Cannot read property 'length' of undefined at HelperService

Getting error 'Cannot read property 'length' of undefined at HelperService.addCommasToArray' when trying to loop through an array that has been passed as a paramter in a helperService class [Typescript]
I'm really not sure why this is not working - I believe it should be straightforward - all I'm trying to do is pass in an array as a parameter and add a ',' to every value in the array (except the last value)
Here is my HelperService Class method:
export class HelperService {
constructor() { }
/*
* Add commas to every value in the array except for the last value
*/
addCommasToArray(array: Array<any>) : Array<any> {
for (let i = 0; array.length; i++){
array[i] += ", ";
}
return array;
}
}
I then call this method within the ngInit of another ts class
this.helperService.addCommasToArray(this.previousClubs);
Here is the ngInit method
public previousClubs: Array<any>;
constructor(private playersService: PlayersService,private
helperService: HelperService, private route: ActivatedRoute) { }
ngOnInit() {
const playerId: string = this.route.snapshot.paramMap.get('id');
this.playersService.getPlayerDetails(playerId).get()
.then(playerDetailsSnapshot=> {
this.currentPlayerDetails = playerDetailsSnapshot.data();
this.currentPlayerDetails.id = playerDetailsSnapshot.id;
});
/*
* Return Previous Clubs
*/
this.playersService.getPreviousClubs(playerId).get().then(
previousClubsSnapshot =>{
this.previousClubs = [];
previousClubsSnapshot.forEach(snap => {
this.previousClubs.push({
id: snap.id,
name: snap.data().name,
});
return false;
});
});
this.helperService.addCommasToArray(this.previousClubs);
}
so here:
this.playersService.getPreviousClubs(playerId).get().then(
previousClubsSnapshot =>{
this.previousClubs = [];
previousClubsSnapshot.forEach(snap => {
this.previousClubs.push({
id: snap.id,
name: snap.data().name,
});
return false;
});
});
// this line executes without awaiting for .then enclosed scope
this.helperService.addCommasToArray(this.previousClubs);
Basically you call addCommasToArray even before your previousClubs var gets array assigned to it and then gets all its items pushed in. To fix since your method is (.then) async you need to call for this method inside the .then execution scope:
ngOnInit() {
const playerId: string = this.route.snapshot.paramMap.get('id');
this.playersService.getPlayerDetails(playerId).get()
.then(playerDetailsSnapshot=> {
this.currentPlayerDetails = playerDetailsSnapshot.data();
this.currentPlayerDetails.id = playerDetailsSnapshot.id;
});
/*
* Return Previous Clubs
*/
this.playersService.getPreviousClubs(playerId).get().then(
previousClubsSnapshot =>{
this.previousClubs = [];
previousClubsSnapshot.forEach(snap => {
this.previousClubs.push({
id: snap.id,
name: snap.data().name,
});
return false;
});
});
this.helperService.addCommasToArray(this.previousClubs);
}

How do I create an ag-Grid cell editor using React and TypeScript?

I see that the ag-grid-react repo has types, and I also see that the ag-grid-react-example repo has examples. But how do I put the two together and create a cell editor with React and Types?
I'm guessing it's something like this but I can't make TypeScript happy:
class MyCellEditor implements ICellEditorReactComp {
public getValue() {
// return something
}
public render() {
const { value } = this.props
// return something rendering value
}
}
I implemented ICellEditor and used ICellEditorParams for prop definitions. For example, this MyCellEditor example from their documentation:
// function to act as a class
function MyCellEditor () {}
// gets called once before the renderer is used
MyCellEditor.prototype.init = function(params) {
// create the cell
this.eInput = document.createElement('input');
this.eInput.value = params.value;
};
// gets called once when grid ready to insert the element
MyCellEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
MyCellEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
MyCellEditor.prototype.getValue = function() {
return this.eInput.value;
};
// any cleanup we need to be done here
MyCellEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
MyCellEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
became this:
class MyCellEditor extends Component<ICellEditorParams,MyCellEditorState> implements ICellEditor {
constructor(props: ICellEditorParams) {
super(props);
this.state = {
value: this.props.eGridCell.innerText
};
}
// returns the new value after editing
getValue() {
// Ag-Grid will display this array as a string, with elements separated by commas, by default
return this.state.value;
};
// Not sure how to do afterGuiAttached()
// if true, then this editor will appear in a popup
isPopup() {
return true;
};
render() {
return (
<div>
hello
</div>
);
}
}

Typescript: How to access and update class variable from public function

I am trying to access value of a class variable in function setRating() but the console print is "undefined".
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
document.addEventListener('DOMContentLoaded', function() {
let stars = document.querySelectorAll('.star');
stars.forEach(function(star) {
star.addEventListener('click', setRating);
});
let temps = parseInt(document.querySelector('.stars').getAttribute('data-rating'));
console.log("Rating 2: " + this.rating);
let target = stars[temps - 1];
target.dispatchEvent(new MouseEvent('click'));
});
}
function setRating(ev) {
//Printing 'undefined' in console.log
console.log('At top: ' + this.rating);
let span = ev.currentTarget;
let stars = document.querySelectorAll('.star');
let match = false;
let num = 0;
stars.forEach(function(star, index) {
if (match) {
star.classList.remove('rated');
} else {
star.classList.add('rated');
}
//are we currently looking at the span that was clicked
if (star === span) {
match = true;
num = index + 1;
}
});
this.rating = num;
console.log("value after update: " + this.rating)
document.querySelector('.stars').setAttribute('data-rating', num.toString());
}
}
the "value after update: " console log prints "undefined" unless this.rating is assigned to num. Can someone please help me with how to access the value of rating variable in setRating() function and how to update its value?
It's a context binding issue, you have to bind the setRating function to the class this otherwise it is going to use its own this which is different than the classes this no having access to this.rating. You can achieve this by using setRating.bind(this).
You can start by changing the DOMContentLoaded to an arrow function so that you inherit the context's this like so:
document.addEventListener('DOMContentLoaded', () => {
// this.rating is visible here now
...
})
Then you can do the same to the forEach handler:
stars.forEach((star) => {
// this.rating is visible here now too
...
});
Finally, you can bind the this of your external function to the classes this:
star.addEventListener('click', setRating.bind(this));
Your final code would be something like bellow:
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
document.addEventListener('DOMContentLoaded', () => {
let stars = document.querySelectorAll('.star');
stars.forEach((star) => {
star.addEventListener('click', setRating.bind(this));
});
let temps = parseInt(document.querySelector('.stars').getAttribute('data-rating'));
console.log("Rating 2: " + this.rating);
let target = stars[temps - 1];
target.dispatchEvent(new MouseEvent('click'));
});
}
function setRating(ev) {
//Printing 'undefined' in console.log
console.log('At top: ' + this.rating);
let span = ev.currentTarget;
let stars = document.querySelectorAll('.star');
let match = false;
let num = 0;
stars.forEach(function(star, index) {
if (match) {
star.classList.remove('rated');
} else {
star.classList.add('rated');
}
//are we currently looking at the span that was clicked
if (star === span) {
match = true;
num = index + 1;
}
});
this.rating = num;
console.log("value after update: " + this.rating)
document.querySelector('.stars').setAttribute('data-rating', num.toString());
}
}
Further observation: You are declaring a function inside a class, that is totally unnecessary, you can declare it as a member
export class UserFeedbackComponent implements OnInit {
...
setRating(ev) {
...
}
}
Then you don't even ave to bind it to call it like so:
star.addEventListener('click', ev => this.setRating(ev));
You do not have to define the function using the function keyword in the class. Declare it as a class member and you should be able to access it normally. This is the best practice in Typescript class declaration.
EX:
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
......................more code............
}
setRating(ev) {
//Printing 'undefined' in console.log
console.log(this.rating); //should work fine
// do other stuff with class members
}
}

ReactJS sorting column with dynamic class creation

I've inherited a large pile of code and it generally works. This is running on v0.13.3.
The problem I am having is in sorting columns. Whenever I invoke the handler for the click event below, I am getting a null this.state. I must be missing something simple because I have done a lot of stare/compare vs other working code and I just don't see it but this example component is hassling me.
I've simplified down to just the problem component as a standalone html file with static sample data:
<div id="content">Loading ...</div>
<script>
var theData = [{"id":"47483648","labName":"Lab0"},{"id":"47483650","labName":"Lab1"},{"id":"47483651","labName":"Lab2"},{"id":"47483654","labName":"Lab3"}];
function render() {
React.render(React.createElement(aTable, {data: theData}),document.getElementById("content"));
}
var aTable = React.createClass({displayName: "aTable",
handleHeaderClick: function(sortBy) {
console.log("firing aTable handleHeaderClick");
//this.state is null here
var newState = this.state;
if (this.state.sortBy === sortBy && this.state.sortOrder === 'asc') {
newState.sortOrder = 'desc';
}
else {
newState.sortOrder ='asc';
}
newState.sortBy = sortBy;
this.setState(newState);
},
render: function(){
var theItems = $.map(this.props.data, function (key, value) {
console.log("the items", key);
return (
React.createElement("tr",{key: key.id + "-row"},
React.createElement("td", {key: key.agentid}, key.id),
React.createElement("td", {key: key.labName}, key.labName),
)
);
});
return (
React.createElement("div", {id: "holder"},
React.createElement("div", {id: "a-table"},
React.createElement("table",null,
React.createElement("thead",null,
React.createElement("tr",null,
React.createElement("th",null," The ID"),
React.createElement("th",
{onClick: this.handleHeaderClick.bind(this, "labName")},
"Lab Name")
)
),
React.createElement("tbody",null,
theItems
)
)
)
)
);
}
});
$(document).ready(function () {
render();
});
This throws up an exception on the null state:
basic.html:37 Uncaught TypeError: Cannot read property 'sortBy' of null
Which refers to this line:
if (this.state.sortBy === sortBy && this.state.sortOrder === 'asc') {
I have latitude to change the handler logic, but am stuck with this pattern for the time being.
Thanks in advance.
EDIT 1
This is a working fiddle where I worked out the sorting by doing a field sort on the data array after Sy pointed out where I was missing the initial state: working fiddle
state is null because you didn't set an initial value.
Test = React.createClass({
getInitialState: function() {
return {};
}
})
this is equivalent to
class Test extends Component {
constructor(props) {
this.state = {}
}
}

Variable accessible outside the function

I have this React Native code and I want to use the gameOfTheUser variable (which is created in the arrow function: userRef. ounce (' value'). then (snapshot =>)outside this one. How to do it?
export default class RouterScreen extends React.Component {
componentDidMount() {
const { navigate } = this.props.navigation;
fb.auth().onAuthStateChanged(user => {
if (user) {
// user exist
var userRef = fb.database().ref('users/' + user.uid);
userRef.once('value').then(snapshot => {
var gameOfTheUser = snapshot.child('game').val();
console.log('0 : ' + gameOfTheUser);
});
if (gameOfTheUser !== null) {
// gameOfTheUser --> error variable is not created
// user in game --> redirect to Game (#user, #game)
console.log('InGame');
var gameID = gameOfTheUser;
console.log('1 : ' + gameOfTheUser);
console.log('1 : ' + gameID);
navigate('Game', { user, gameID });
} else {
// user not in game --> redirect vers Choose (#user)
console.log('NotInGame');
navigate('Choose', { user });
}
} else {
// user doesn't exist
navigate('Auth');
}
});
}
You could :
1) update a var which isn't in this scope but in the global scope (using events for example)
2) set a state/props (depend if it change or not) and share it with a main component

Resources