-
Notifications
You must be signed in to change notification settings - Fork 9
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
base: master
Are you sure you want to change the base?
Conversation
* input for board size * input for default state * then Game of Life itself * reset button * Unit Tests * Decomposition to Components
There was a problem hiding this 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"; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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} />) |
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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. 👍
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely written component! 👍
There was a problem hiding this comment.
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.
src/GameOfLife.test.js
Outdated
fireEvent.change(getByLabelText('input-dimension-cells'), {target: { value: 2}}); | ||
fireEvent.click(getByText('Vygeneruj herní plán')); | ||
|
||
expect(getAllByLabelText('input-cell')[0]).toBeVisible(); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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'); | ||
}); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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 :).
There was a problem hiding this comment.
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 😉
There was a problem hiding this comment.
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" /> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
}
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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: () => {}
};
There was a problem hiding this comment.
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
!
There was a problem hiding this comment.
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)
… used in `GameOfLifeBoard.test.js`.
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. :-) |
No problem, @Podbi I am happy to help. Let me know if you want some more personal discussion :-) |
No description provided.