For a simple app like this, there is no need to integrate redux/mobx yet. What I recommend is something that is very common in React, and that is to lift your state up.
We can accomplish this through three steps:
Dumb down the Circleone, Circletwo, Circlethree components. They only need to know what the current angle is in order to render
ClawCircle should be told what image to render (or otherwise blank)
App needs to hold the state for all this information (and thus we've "lifted" the state up from CircleX to its parent, App).
Step 1
Instead of holding the currentAngle in the state, let's assume that information is given to us through the prop currentAngle. When a circle gets clicked, we'll just tell whoever created the circle that we were clicked on, because they will pass us a prop called onClick.
Since we now don't need to keep track of our state, we can make the component stateless and just turn it into a functional component.
For example, CircleOne might turn out to look more like this:
const CircleOne = ({ currentAngle, onClick }) => (
<div
className="App-logo small-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg)` }}
onClick={onClick}
>
<div className="little-circle one react">
{/* ... rest of your divs */}
</div>
);
Step 2
Next, let's change ClawCircle, we'll give it an optional imageClass prop that might be claw-react, claw-vue etc, or it might just be an empty string (update css accordingly to render the image too!). So the render method might change into this:
render() {
const circleStyle = { transform: `rotateZ(${this.props.currentAngle}deg)` };
return (
<div
className={`App-logo claw-circle ${this.props.imageClass}`}
alt="logo"
style={circleStyle}
onClick={this.rotateCircle.bind(this)}
/>
);
}
By the way, the bind call can be done in the constructor instead of the render method, this way we don't have to re-bind every time the component re-renders.
constructor(props) {
super(props);
// constructor code
this.rotateCircle = this.rotateCircle.bind(this);
}
// later: onClick={this.rotateCircle}
Step 3
This is the more complicated step, as we now have to delegate the heavy work to App instead of the individual Circles.
So App needs to know the angles of each individual circle, and handle what happens when each circle is clicked. Furthermore, when angles change, we want to check if all three of them are equal. If they are equal, we need to tell ClawCircle what image to render.
All in all, it would probably look something like this:
EDIT: I should have probably tried running this code before writing it on the fly here. Here's the full version (tested!) Just make sure you have claw-react claw-vue and claw-angular rules in your CSS
import React, { Component } from 'react';
import './App.css';
import { CSSTransitionGroup } from 'react-transition-group';
class HalfCircle extends Component {
render() {
return (
<div className="App-logo half-circle" alt="logo">
</div>
);
}
}
const Circleone = ({ currentAngle, onClick }) => (
<div
className="App-logo small-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg` }}
onClick={onClick}
>
<div className="little-circle one react"></div>
<div className="little-circle two angular"></div>
<div className="little-circle three vue"></div>
</div>
);
const Circletwo = ({ currentAngle, onClick }) => (
<div
className="App-logo big-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg` }}
onClick={onClick}
>
<div className="little-circle un react"></div>
<div className="little-circle dos angular"></div>
<div className="little-circle tres vue"></div>
</div>
);
const Circlethree = ({ currentAngle, onClick }) => (
<div
className="App-logo biggest-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg` }}
onClick={onClick}
>
<div className="little-circle ein react"></div>
<div className="little-circle zwei angular"></div>
<div className="little-circle drei vue"></div>
</div>
);
class ClawCircle extends Component {
constructor(props){
super(props)
this.state = {
currentAngle: 45,
anglePerClick: 360,
}
}
rotateCircle() {
const { currentAngle, anglePerClick } = this.state;
this.setState({
currentAngle: currentAngle + anglePerClick
})
}
render() {
const circleStyle = {
transform: `rotateZ(${this.state.currentAngle}deg)`
}
return (
<div
className={`App-logo claw-circle ${this.props.imageName}`}
alt="logo"
style={circleStyle}
onClick={this.rotateCircle.bind(this)}
/>
);
}
}
const getNameForAngle = (one, two, three) => {
if (one === two && one === three) {
switch(one) {
case 120:
return 'claw-react';
case 240:
return 'claw-vue';
case 360:
return 'claw-angular';
default:
return '';
}
}
return '';
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
oneAngle: 120,
twoAngle: 120,
threeAngle: 120,
};
this.handleOneClick = this.handleOneClick.bind(this);
this.handleTwoClick = this.handleTwoClick.bind(this);
this.handleThreeClick = this.handleThreeClick.bind(this);
}
handleClick(circle) {
const nextAngle = this.state[circle] + 120;
this.setState ({
[circle]: nextAngle
});
}
handleOneClick() {
this.handleClick('oneAngle');
}
handleTwoClick() {
this.handleClick('twoAngle');
}
handleThreeClick() {
this.handleClick('threeAngle');
}
render() {
const { oneAngle, twoAngle, threeAngle } = this.state;
const imageName = getNameForAngle(oneAngle, twoAngle, threeAngle);
return (
<div className="App">
<header className="App-header">
<Circleone
currentAngle={oneAngle}
onClick={this.handleOneClick}
/>
<Circletwo
currentAngle={twoAngle}
onClick={this.handleTwoClick}
/>
<Circlethree
currentAngle={threeAngle}
onClick={this.handleThreeClick}
/>
<ClawCircle imageName={imageName} />
<HalfCircle/>
</header>
</div>
);
}
}
export default App;