Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Podbom01 JS Academy Lesson 01 (Game of Life) #13

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

Podbi
Copy link

@Podbi Podbi commented Sep 30, 2019

No description provided.

Dominik Tilp and others added 4 commits September 13, 2019 12:07
 * input for board size
 * input for default state
 * then Game of Life itself
 * reset button
 * Unit Tests
 * Decomposition to Components
Copy link

@vysinsky vysinsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General note: It is always better to use the least amount of stateful components. Because when you start handling state in each component separately, you will get headaches from trying to keep the state synchronized.

The best way for this app would be to have all logic and state in App.js or GameOfLife component and pass state and callbacks to children. This way all child components could be "dumb" components and would just render according to passed props and trigger passed callbacks for correct events.

Nice work, @Podbi!

src/App.js Outdated
@@ -0,0 +1,18 @@
import React from "react";
import GameOfLife from "./GameOfLife.js";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just fyi: .js is not mandatory in imports.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

<table style={tableStyles}>
<tbody>
{props.boardState.map(function(row, index) {
return (<Row row={row} key={index} />)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this usecase it is probably ok. But generaly, using array index as key is not good practice and can lead to buggy behaviour.

More resources:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the hint and notes for further reading. 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've read looks like that in this case it might not be a big issue to use index, otherwise I could put there some unique ID based on shortid for example.

}
}

export default GameOfLife;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely written component! 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @vysinsky. It was really interesting to put together this one as there are important changes of state that result in different components to be rendered.
I was surprised how easy it was in the end with render() function and just a simple list of stateful properties.

fireEvent.change(getByLabelText('input-dimension-cells'), {target: { value: 2}});
fireEvent.click(getByText('Vygeneruj herní plán'));

expect(getAllByLabelText('input-cell')[0]).toBeVisible();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal but getAllByLabelText('input-cell') could be assigned to a variable so you would not have to call this function six times.

}
}

export default GameOfLifeBoard;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if GameOfLife component will have some logic and changes boardState sent in props?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I'm following your question, @vysinsky. What if the boardState structure is different? I could use PropSpec to make sure that the initial state is according expectations. Truth is that I have not applied much of "defensive" programming in the pull request.

Are you suggesting something else?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean something else. Let's talk about this tomorrow in person :).


fireEvent.click(getByText('Resetovat'));
expect(baseElement).toHaveTextContent('1001001111000101011010001Další generace!Resetovat');
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are testing something else than GameOfLifeBoard component here :-). Ideally, next function should be mocked if this should be a unit test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a good idea. Depends on the "Unit". Would you give me a hint for a mocking tool, @vysinsky ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jest has it included: https://jestjs.io/docs/en/mock-functions.html

You need to mock the gol module and mock implementation of next function.

I can help you more (give you the code) if you want, let me know :).

Copy link

@vysinsky vysinsky Oct 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And hint: if you change GameOfLifeBoard component to receive next as prop (so you don't import it in the GameOfLifeBoard module) mocking gets much easier 😉

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for these ideas. :-) Dependency injection is a good way.


render() {
return (
<input type="number" name={this.state.name} data-row={this.state.row} data-cell={this.state.cell} min="0" max="1" value={this.state.value} onChange={this.handleOnChange} aria-label="input-cell" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those data attributes just for testing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Could I use something more appropriate?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm...

you could test the state values. But I am afraid the state is an implementation detail of the component. And react-testing library encourages devs to not test these details (I think it is not even possible to read the state of the component you test).

As you include row and cell in name it should be enough to select by the name value.

this.setState(currentState => {
return {
...currentState,
value: value

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a hint: In case object property and variable you are assigning to it have same names, you can use shorthand:

{
  ...currentState,
  value
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know. Thanks!

src/InputCell.js Outdated
cell : parseInt(props.cell),
value : 0
};
this.onInputChangeCallback = props.onChange;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you doing this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I can use the callback later on, is there a more appropriate way?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can call props.onChange directly anywhere in the component. The binding should be done in parent component.

src/InputCell.js Outdated
});

if (this.onInputChangeCallback) {
this.onInputChangeCallback({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be just this.props.onChange({

Also, giving onChange default value of () => {} would remove the need of checking if it is set:

InputCell.defaultProps = {
  onChange: () => {}
};

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. I didn't know that it is possible to access this.props!

Copy link

@vysinsky vysinsky Oct 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, props is normal class property, you can access it anywhere in the component (if method is not static ofc)

@vysinsky vysinsky mentioned this pull request Oct 24, 2019
@Podbi
Copy link
Author

Podbi commented Oct 25, 2019

Thank you for the review, @vysinsky. Great hints and a huge help for me! :-)

I've applied most of your comments.

Regarding the components the idea was to decompose them as much as I can, but the truth is that holding the state might be tricky. The idea to have just one component owning the state sounds right. :-)

@vysinsky
Copy link

No problem, @Podbi I am happy to help.

Let me know if you want some more personal discussion :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants