- React - Used for the views
- Ruby on Rails - Mainly used as an API to communicate with React containers
- Bootstrap 4 - Used Reactstrap to style React components
- D3 - Used for the donut chart on stats page
- React Motion - Used to animate the donut chart updates
When someone wins a game, a POST request is sent to games#create
// client/app/bundles/TicTacToe/containers/GameContainer.jsx
componentDidUpdate(prevProps, prevState) {
const current = this.state.history[this.state.stepNumber];
const winner = this.calculateWinner(current.squares);
if (winner.name) {
$.ajax({
url:'/games',
type:'POST',
dataType:'json',
data:{
game: {winner: winner.name, history: this.state.history}
}
});
}
}
On successful save, the create
action broadcasts updated stats to the games
ActionCable channel
# app/controllers/games_controller.rb
def create
@game = Game.new(game_params)
if @game.save
ActionCable.server.broadcast 'games',
winner: @game.winner,
stats: Game.stats
render json: @game, status: :created, location: @game
else
render json: @game.errors, status: :unprocessable_entity
end
end
The StatsContainer
receives the updated stats and passes them to the Stats
component
// client/app/bundles/Stats/containers/StatsContainer.jsx
componentWillMount() {
var self = this;
if (typeof App !== 'undefined'){
App.messages = App.cable.subscriptions.create('GamesChannel', {
received: function(data) {
return self.setState({ data: data.stats });
}
});
}
}
render() {
return <Stats data={this.state.data} />
}
The Stats
component passes the new data to the D3DonutChart
component
// client/app/bundles/Stats/components/Stats.jsx
<Container>
<Row>
<Col lg={{ size: 4, offset: 4 }} className="align-self-center">
<h3 className="stats-label text-center">Wins</h3>
<div className="chart-wrapper text-center">
<D3DonutChart data={this.props.data} />
</div>
<ChartLabel label="X" data={this.props.data[0]} />
<ChartLabel label="O" data={this.props.data[1]} />
<NavLink name="game" link="/" />
</Col>
</Row>
</Container>
Where it is finally rendered using D3 and React Motion
// client/app/bundles/Stats/components/D3DonutChart.jsx
render() {
var width = 290,
height = 290,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#3299BB", "#FF9900"]);
var pie = d3.layout.pie()
.value(d => d)
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 20);
var displayedData = pie(this.props.data);
return (
<svg width={width} height={height}>
<g transform={"translate(" + width / 2 + "," + height / 2 + ")"}>
{displayedData.map((slice, i) =>
<Motion
key={i}
defaultStyle={{
startAngle: slice.startAngle,
endAngle: slice.endAngle,
padAngle: slice.padAngle,
}}
style={{
startAngle: spring(slice.startAngle),
endAngle: spring(slice.endAngle),
padAngle: spring(slice.padAngle)
}}>{value => <path
fill={color(i)}
d={arc(value)} />
}</Motion>
)}
</g>
</svg>
);
}
ruby -v # 2.3.4
node -v # 7.9.0
brew install yarn
rails -v # 5.0.2
gem install foreman
Install dependencies
bundle && yarn
Setup database
rails db:setup
Run server
foreman start -f Procfile.dev