I'm building a desktop app using React and Electron. Since it's growing fast, I realized I need some kind of state management like Redux to avoid passing many properties between components. I started reading Redux official documentation but still cannot figure out how to implement it in my case. I'm stuck!
For example, I have a main App
component that renders many sub-components. One of them has a button. When clicked, it should dispatch an "event" to the store so the main App
can act in consequence. How can I accomplish that?
I cannot find the concept of events and I've hit a wall on how to even start using Redux.
Why events? Because it seems silly to me to dispatch an action and modify app state in this case. I just want to inform the root component to dispatch an action based on a user action. User interacts with a presentational component that should tell a container component to make an API call or start capturing audio/camera for example.
For what I know up to now, the only way to accomplish this is to mutate state so another component listening for changes detects a special value that means "hey, let's do this", then mutate state again to say "hey, I'm doing this", and when it's done state changes again with "hey, it's done".
Can someone point me in the right direction please?
User interacts with a presentational component that should tell a container component to make an API call or start capturing audio/camera for example.
Perhaps your container component is doing more than it should. Consider a situation where React components do no more than two things:
If you were not using redux and wanted to make an API call when clicking a button, that might look something like:
class App extends Component {
state = { data: {} }
makeAPICall() {
fetch(url).then(data => this.setState({ data }))
}
render() {
<Child
data={this.state.data}
makeAPICall={this.makeAPICall}
/>
}
}
let Child = ({ data, makeAPICall }) => (
<button onClick={makeAPICall}>Call API!</button>
)
The App
component is responsible for storing global state and handling events, but we have to pass down that state and App's handlers through the component tree, quite possibly through components that will never themselves use those props.
By adding Redux your application now has a much better place to handle side effects like API calls or turning a camera on. Middleware!
Let this (crappy) illustration help you:
So now instead your App
component can be just a normal presentational component like all of the others, simply displaying data based on store props and handling any user input / dispatching actions if need be. Let's update the above example using the thunk
middleware
// actions.js
export let makeAPICall = () => {
return dispatch => {
fetch(url).then(data => dispatch({
type: 'API_SUCCESS',
payload: data,
})).catch(error => dispatch({ type: 'API_FAIL', payload: error }))
}
}
// Child.js
import { connect } from 'react-redux'
import { makeAPICall } from './actions'
let Child = ({ dispatch }) => (
<button onClick={() => dispatch(makeAPICall())}>Call API!</button>
)
export default connect()(Child)
Thinking about React applications this way is very powerful. The separation of concerns is very well laid out. Components display stuff and handle events. Middleware takes care of any side effects (if there need to be any) and the store simply is an object that will cause React to re-render in case its data changes.
UPDATE: "The Modal Problem"
React apps may have some global stuff like modals and tooltips. Don't think about the "open modal" event.. think "what is the current modal content?".
A modal setup may look something along these lines:
// modalReducer.js
function reducer (state = null, action) {
if (action.type === 'UPDATE_MODAL') {
return action.payload
}
// return default state
return state
}
// App.js
let App = connect(state => ({ modal: state.modal }))(
props =>
<div>
<OtherStuff />
<Modal component={props.modal} />
</div>
)
// Modal.js
let Modal = props =>
<div
style={{
position: 'fixed',
width: '100vw', height: '100vh',
opacity: props.component ? 1 : 0,
}}
>
{props.component}
</div>
// Child.js
let Child = connect()(props =>
<button onClick={e =>
dispatch({
type: 'UPDATE_MODAL'
payload: <YourAwesomeModal />
})
}>
Open your awesome modal!
</button>
)
This is just an example, but would work great! when state.modal
is null
your Modal has 0 opacity and won't show. When you dispatch UPDATE_MODAL
and pass in a component, the modal will show whatever you dispatch and change the opacity to 1 so you can see it. Later you can dispatch { type: 'UPDATE_MODAL', payload: null }
to close the modal.
Hopefully this gives you some things to think about!
Definitely read this answer by Dan. His approach is similar but stored modal "metadata" vs the component itself which lends itself better to Redux fanciness like time travel etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With