Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to simulate events using React-Redux?

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?

like image 409
demian85 Avatar asked Dec 13 '16 22:12

demian85


1 Answers

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:

  1. Display DOM elements based on props
  2. Handle user input (dispatch events)

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:

enter image description here

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.

like image 179
azium Avatar answered Oct 05 '22 02:10

azium