Sebastianb
Sebastianb

Reputation: 2060

Dispatch an action from a component with data from a child component

So, I'm building a small app to learn some redux/react. The app includes a form that is built as a component with a Save button (and some more irrelevant code), and a child component that includes the form fields.

My intention is to update the app state, handled by redux, by dispatching a "SAVE" action (from the parent component) when clicking the "save" button, sending as payload the data from the fields inside the child component.

Is there a react-friendly way to do it?

Upvotes: 0

Views: 841

Answers (1)

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

If some portion of global state is shared between several components wrapped by common (parent) component, you may try to:

  • bind child components event handlers to parent component callbacks
  • store child components changes within parent's local state
  • dispatch SAVE action to update global state

You may find the quick demo below:

//dependencies
const { useState } = React,
      { render } = ReactDOM,
      { createStore } = Redux,
      { useDispatch, useSelector, Provider } = ReactRedux
      
//action, initial state, reducer, store
const SAVE_FEEDBACK = 'SAVE_FEEDBACK',
      initialState = {scores:[]},
      appReducer = (state=initialState, action) => {
        switch(action.type){
          case SAVE_FEEDBACK : {
            const {scores} = state,
                  {score} = action
            return {...state, scores:[...scores, score]}
          }
          default: return state
        }
      },
      store = createStore(appReducer)
      
//form component
const ScoreForm = ({onScoreInput, onNameInput}) => (
  <form>
    <select onChange={e => onScoreInput(e.target.value)}>
      <option value="" selected></option>
      <option value="awfull">awfull</option>
      <option value="awsome">awsome</option>
    </select>
    <input onKeyUp={e => onNameInput(e.target.value)} />
  </form>
)

//parent component
const MovieScore = () => {
  const [userScore, setScore] = useState(''),
        [userName, setName] = useState(''),
        dispatch = useDispatch(),
        userScores = useSelector(({scores}) => scores),
        handleScoreInput = score => setScore(score),
        handleNameInput = name => setName(name),
        onSave = () => dispatch({type: SAVE_FEEDBACK, score: {userName, userScore}})
  return (
    <div>
      <img 
        src="https://upload.wikimedia.org/wikipedia/uk/a/a4/Knockin.jpg" 
        style={{maxHeight:200}}
      />
      <ScoreForm 
        onScoreInput={handleScoreInput}
        onNameInput={handleNameInput}
       />
      <button onClick={onSave}>Save</button>
      <div>
        {userScores.map(({userScore,userName}) => <div>{userName} thinks, this movie is {userScore}</div>)}
      </div>
    </div>
  )
}

//wrap into Provider component
render (
  <Provider {...{store}}>
    <MovieScore />
  </Provider>,
  document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.3/react-redux.min.js"></script><div id="root"></div>

Upvotes: 1

Related Questions