0
votes

I have create a very simple React/Redux application and I am struggling with getting a simple component to re-render when the state changes. I am using create-react-app as a starting point. In the video series I've been watching by Dan Abramov (https://egghead.io/lessons/react-redux-react-todo-list-example-adding-a-todo), he subscribes a root render function to the store, which gets called each time a change is dispatched. Without using Connect, I'd like to understand how to cause the Value component to re-render on state change.

Thanks!

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import registerServiceWorker from './registerServiceWorker'
import { createStore } from 'redux'

const counter = (state = 0, action) => {
  console.log(action)
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

const store = createStore(counter)

ReactDOM.render(<App store={store} />, document.getElementById('root'))
registerServiceWorker()

app.js

import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
import Counter from './components/Counter'

class App extends Component {
  render() {
    return (
      <div className="App">
        <Counter
          value={this.props.store.getState()}
          onIncrement={() =>
            this.props.store.dispatch({
              type: 'INCREMENT'
            })
          }
          onDecrement={() =>
            this.props.store.dispatch({
              type: 'DECREMENT'
            })
          }></Counter>
      </div>
    )
  }
}

export default App;

Counter.js

import React from 'react'
import Value from './Value'

const Counter = ({ value, onIncrement, onDecrement }) => {
  return (
    <div>
      <Value value={value}></Value>
      <button onClick={onIncrement}>+</button>
      <button onClick={onDecrement}>-</button>
    </div>
  )
}

export default Counter

Value.js

import React from 'react'

const Counter = ({ value }) => {
  return (
    <h1>{value}</h1>
  )
}

export default Counter
2
I think you have a typo under value.js. - Josan Iracheta

2 Answers

0
votes

Does this help clarify the process?

class App extends Component {
  constructor (props) {
    super(props)
    this.state = { counter: 0 }
    const { store } = props
    this.unsubscribe = store.subscribe(() => {
      const counter = store.getState()
      if (counter !== this.state.counter) {
        this.setState({ counter })
      }
    })
  }

  render () {
    return (
      <div className='App'>
        <Counter
          value={this.state.counter}
          onIncrement={() =>
            this.props.store.dispatch({
              type: 'INCREMENT'
            })
          }
          onDecrement={() =>
            this.props.store.dispatch({
              type: 'DECREMENT'
            })
          } />
      </div>
    )
  }
}

A few things to note:

  1. This isn't a good solution for anything other than learning. react-redux does this in a much more maintainable and performant way.
  2. Obviously, Redux state would normally be an object and not a single integer.
  3. .subscribe is essentially just a way to set an event listener. When the state updates, any attached handlers will be called. What you do in your handler is pretty much up to you, although sensible precautions against unnecessary rendering would be pretty high on the list.
-1
votes

Without using Connect, I'd like to understand how to cause the Value component to re-render on state change

First thing we have to do is subscribe to any redux-state changes. We will set the redux-state to your app's local state and update the state every time the redux-state is updated:

/* app.js */

componentDidMount(){
  this.props.store.subscribe(() => {
    this.setState({reduxState: this.props.store.getState()});
  });
}

Then you pass the state to your child components:

render(){
  return (
    <Counter value={this.state.reduxState} ... />
  )
}