2
\$\begingroup\$

TL;DR what should I do if I have too many methods in my parent component??

I'm making a card game (Pai Gow Tiles) using react and I am having trouble finding a way to format my parent component. I've always heard that you should keep your components small and mine is over 300 lines. Most of it are methods that I need for my logic to set my hand 'house-way'. Think of house way as a set of rules on how to play a hand depending on what cards you're dealt. I tried separating the methods into different files, but some methods use 'this' and it throws an error.

export default class Layout extends Component {
 constructor(props) {
 console.log("starting up");
 super(props);
 //each hand holds a randomly generated tile object from { tilesSet }
 this.state = {
 //needs empty spots for when (mounting) <Hands hand1={this.state.hand[0].img} /> else error since hand[0] doesnt exist.
 hand: ["", "", "", ""],
 cards: false,
 pairName: '',
 rule: '',
 show: false,
 history: [],
 input1: 'GJ3',
 input2: 'GJ6',
 input3: 'teen',
 input4: 'teen'
 };
 //binding in the constructor is recommended for performance.
 this.handleToggle = this.handleToggle.bind(this);
 this.handleClick = this.handleClick.bind(this);
 this.handleHW = this.handleHW.bind(this);
 this.assignHands = this.assignHands.bind(this);
 this.checkPair = this.checkPair.bind(this);
 this.checkTeenDey = this.checkTeenDey.bind(this);
 this.hiLowMiddle = this.hiLowMiddle.bind(this);
 this.compare = this.compare.bind(this);
 this.handleSubmit = this.handleSubmit.bind(this);
 this.userInput1 = this.userInput1.bind(this);
 this.userInput2 = this.userInput2.bind(this);
 this.userInput3 = this.userInput3.bind(this);
 this.userInput4 = this.userInput4.bind(this);
 this.inputClick = this.inputClick.bind(this);
 this.baccaratCount = this.baccaratCount.bind(this);
 }
 baccaratCount = (n, m) => {
 //recognizing gong , wong , or pair...
 let number = n.val + m.val;
 if(n.rank === m.rank){
 return 'Pair';
 }
 else if (n.val === 2 || m.val === 2){
 if (number === 10){
 return "Gong";
 }
 else if (number === 11){
 return 'Wong';
 }
 }
 //regular baccarat counting...
 else if (number >= 10 && number < 20){
 if (number >= 20) {
 return number -= 20;
 }
 return number -= 10;
 }
 //value is under 10, return the sum.
 return number;
 } 
 //n = pairTL, n2 = otherTL
 split(n, n2){
 //Gee Joon
 if (n[0].pair === 1) {
 let combo1 = this.baccaratCount(n2[0], n[0]);
 let combo2 = this.baccaratCount(n2[1], n[1]);
 //if it meets the split requirements...
 if((combo1 >= 7 && combo2 >= 9) || (combo1 >= 9 && combo2 >= 7)){
 console.log('got em', combo1, combo2);
 return true;
 } 
 else {
 return true;
 }
 }
 }
 //checks for pairs. takes an array as arg
 checkPair(hand){
 for(let i = 0; i < hand.length; i++) {
 for (let ii = 0; ii < hand.length; ii++) {
 // if there is a pair and it is not comparing to itself.
 if (hand[i].pair === hand[ii].pair && i !== ii) {
 let pairTL = hand.filter((x) => x.rank === hand[i].rank); //array of the pair tiles
 let otherTL = hand.filter((x) => x.rank !== hand[i].rank); // array of the other 2 tiles. use these two to move tiles accordingly
 //if pair has split rules...
 if (hand[i].split !== false) {
 //returns true if it split..
 if(this.split(pairTL, otherTL)) {
 let copyArr = [pairTL[0], otherTL[0], pairTL[1], otherTL[1]];
 this.setState(() => ({hand: copyArr}));
 }
 else {
 let copyArr = otherTL.concat(pairTL);
 this.setState(() => ({hand: copyArr}));
 return true;
 }
 }
 //don't split
 else {
 let copyArr = otherTL.concat(pairTL); //concats the two small arrays together and renders.
 this.setState(() => ({hand: copyArr, pairName: pairTL[0].name, rule: 'Don\'t Split'}))
 return true;
 }
 }
 }
 }
 return false; // no pairs
 }
 //will not execute if there is a pair...(checkPair returns true)
 checkTeenDey(hand){
 //true if we have teen or dey
 let teenDey = hand.find((el) => el.val === 2) !== undefined;
 //if true...
 if(teenDey){
 let tile = hand.find((el) => el.val === 2); // teen/ dey object
 let tempArr = hand.filter((el) => el.name !== tile.name); //new arr without marked teen/dey. arr.length = 3
 let secondTeenDey = tempArr.find((el) => el.val === 2); //second teen/dey (not pair)
 let seven = tempArr.find((el) => el.val === 7);
 let eight = tempArr.find((el) => el.val === 8);
 let nine = tempArr.find((el) => el.val === 9);
 //if there is a second teen/dey
 if (secondTeenDey){
 let twoArr = tempArr.filter((el) => el.name !== secondTeenDey.name);
 console.log(tile, secondTeenDey, twoArr);
 return true;
 }
 //look for 7,8,9
 else if (seven){
 let without7 = tempArr.filter((el) => el.name !== seven.name); 
 let sevenAndTeenOrDey = [tile, seven];
 let newHand = sevenAndTeenOrDey.concat(without7);
 this.setState(() => ({hand: newHand, rule: 'Teen/Dey'}));
 return true; 
 }
 else if(eight){
 let without8 = tempArr.filter((el) => el.name !== eight.name); 
 let eightAndTeenOrDey = [tile, eight];
 let newHand = eightAndTeenOrDey.concat(without8);
 this.setState(() => ({hand: newHand, rule: 'Teen/Dey'}));
 return true;
 }
 else if(nine){
 let without9 = tempArr.filter((el) => el.name !== nine.name); 
 let nineAndTeenOrDey = [tile, nine];
 let newHand = nineAndTeenOrDey.concat(without9);
 this.setState(() => ({hand: newHand, rule: 'Teen/Dey'}));
 return true;
 }
 }
 // no teen or dey...
 else{
 return false;
 }
 }
 //point system used for sort() in hiLowMiddle()
 compare(a,b){
 let comparison = 0;//no change
 if(a.realValue < b.realValue){
 comparison = -1;//a comes before b
 }
 else if(a.realValue > b.realValue){
 comparison = 1;//b comes before a
 }
 return comparison;
 }
 //will not execute if there is a teen dey...
 hiLowMiddle(hand){
 //makes a new copy of hand and sorts it using sort()'s point system.
 let sortedArr = hand.slice().sort(this.compare); //slice used, else mutates hand.
 let tempHair = [sortedArr[0], sortedArr[3]];
 let tempBack = [sortedArr[1], sortedArr[2]];
 let hiLow = tempHair.concat(tempBack); //newly sorted arr
 this.setState(() => ({hand: hiLow, rule: 'Hi-Low-Middle'}));
 }
 //generates new hand and updates them to state.
 assignHands() {
 let tempArr = [0, 0, 0, 0]; //filler array
 let testArr = tilesSet.slice(); //filler array. tilesSet is untouched
 //loops through and assigns random tile from deck
 let newArr = tempArr.map((x) => {
 let counter = Math.floor(Math.random()* (testArr.length - 1));
 //used to hold the selected obj. needed since splice changes arr.length and we splice/display the different indexes.
 let dummyArr = testArr[counter];
 testArr.splice(counter, 1);
 return dummyArr;
 })
 //updates state
 this.setState({hand: [newArr[0], newArr[1], newArr[2], newArr[3]], show: true, history: [...this.state.history, [...newArr]]});
 }
 handleSubmit = (e) => {
 e.preventDefault();
 }
 //toggle effect.
 handleToggle = () => {
 this.setState(() => ({cards: !this.state.cards}));
 }
 handleClick = () => {
 this.assignHands(); 
 //works, but not 100% properly. the changes are one step behind. fix async.
 //check to see the history length. max set @ 10
 if(this.state.history.length >= 10){
 let temp = this.state.history.slice();
 temp.shift();
 this.setState(() => ({history: temp}))
 }
 }
 //House Way
 handleHW(){
 if(!this.checkPair(this.state.hand)){
 if(!this.checkTeenDey(this.state.hand)){
 this.hiLowMiddle(this.state.hand);
 }
 }
 }
 //used for dropdown options. One per card.
 userInput1(e){
 this.setState({input1: e.target.value});
 }
 userInput2(e){
 this.setState({input2: e.target.value})
 }
 userInput3(e){
 this.setState({input3: e.target.value})
 }
 userInput4(e){
 this.setState({input4: e.target.value})
 }
 //updates state and changes hands.
 inputClick(){
 let first = tilesSet.filter((x) => x.name === this.state.input1);
 let second = tilesSet.filter((x) => x.name === this.state.input2);
 let third = tilesSet.filter((x) => x.name === this.state.input3);
 let fourth = tilesSet.filter((x) => x.name === this.state.input4);
 let newArr = [first[0], second[0], third[0], fourth[0]];
 this.setState(() => ({hand: newArr, history: [...this.state.history, [...newArr]]}));
 }
 render() {
 return (
 <div>
 <Answer 
 show={this.state.show}
 baccaratCount={this.baccaratCount}
 hand={this.state.hand}
 />
 <Hands 
 cards={this.state.cards}
 hand1={this.state.hand[0].img}
 hand2={this.state.hand[1].img}
 hand3={this.state.hand[2].img}
 hand4={this.state.hand[3].img} 
 />
 <Input 
 handleSubmit={this.handleSubmit}
 userInput1={this.userInput1}
 userInput2={this.userInput2}
 userInput3={this.userInput3}
 userInput4={this.userInput4}
 inputClick={this.inputClick}
 input1={this.state.input1} 
 input2={this.state.input2} 
 input3={this.state.input3} 
 input4={this.state.input4} 
 />
 <Buttons 
 type="button" 
 className="btn btn-dark" 
 handleClick={this.handleClick} 
 handleHW={this.handleHW}
 hand={this.state.hand}
 />
 <Features 
 hand={this.state.hand}
 pairName={this.state.pairName}
 rule={this.state.rule}
 history={this.state.history}
 />
 </div>
 );
 }
}
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Nov 15, 2018 at 4:11
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

If you think you have too many methods you should first know, thats completely fine. Until you can accurately prove that all the extra methods are hurting performance in a measurable way there is no need to refactor. That being said, we can move some of these methods out.

baccaratCount() doesnt set anything on state and isn't reliant on any other methods, we can move this one out into a separate file, maybe into a /utils folder?

/utils/getBaccaratCount.js

export const getBaccaratCount = (n, m) => {
 //recognizing gong , wong , or pair...
 let number = n.val + m.val;
 if (n.rank === m.rank) {
 return "Pair";
 } else if (n.val === 2 || m.val === 2) {
 if (number === 10) {
 return "Gong";
 } else if (number === 11) {
 return "Wong";
 }
 }
 //regular baccarat counting...
 else if (number >= 10 && number < 20) {
 if (number >= 20) {
 return (number -= 20);
 }
 return (number -= 10);
 }
 //value is under 10, return the sum.
 return number;
};

you can then import this into whatever files needs this like:

import { getBaccaratCount } from 'path/to/utils/getBaccaratCount

split() can also be moved out into a separate file just like the above method, except this one will need to import the above method into it since it uses it. We can create a new file just like how we did above. Lastly, compare() can be moved into a separate file and imported like how we did above, this method isn't actually used anywhere in the code you supplied, so maybe it can be removed?

We can also refactor your userInput() methods to be more modular. Instead of creating a new method for each input we could add a name prop to the inputs and have one handleInput(e) method that looks like this:

handleInput(e) {
 this.setState({
 [e.taget.name]: e.target.value
 });
}

Closing thought, if the method just returns a value and isn't setting state, it can most likely be moved to a utility file.

answered Nov 29, 2018 at 21:29
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.