Dreaded Harvester
Dreaded Harvester

Reputation: 1547

How to update React component after changing state through redux?

I was learning React and Redux and while doing that I decided to make webpage with a button which on clicking would change the state. Below the button I wanted to display the current state in a different component. Though the button on clicking changes the state, but it is not getting reflected in the component. Here is my code:

App.js

import React from 'react'
import Name from './Name'
import {changeName} from './Action'; 

export default function App () {  
      return (
          <div>
            <button onClick={changeName}>Click me</button>
            <Name />
          </div>
      )
}

Name.js

import React from 'react'
import {store} from './Store'

function Name(props) {
    return (
        <div>           
            My name is: {store.getState()}
        </div>
    )
}

export default Name

Store.js

import { createStore } from 'redux';
import {reducer} from './Reducer';

export const store = createStore(reducer, 'Tarun');

Action.js

import {store} from './Store';

export const changeName = () => {
    if (store.getState() === "Tarun"){
        store.dispatch({ type: 'name', payload: 'Subhash' });
    }
    else{
        store.dispatch({ type: 'name', payload: 'Tarun' });
    }
}

Reducer.js

export const reducer = function(state, action) {
    if (action.type === 'name') {
      return action.payload;
    }
    return state;
};

When I click the button, The text inside the Name component does not change. What is the issue?

Upvotes: 8

Views: 8450

Answers (2)

Elder
Elder

Reputation: 1514

You need to set up your reducer and initial store properly following the Redux documentation.

You're missing a Provider, which will provide your store to your application.

const store = createStore(reducer, applyMiddleware(thunk));

const rootElement = document.getElementById("root");

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

Now, your store is available to your components.

Your reducer needs an initial state too and you're always supposed to return an updated copy of your state. That said, don't change the state directly, but make a copy, change it, then return that copy.

const initialState = {
  name: ""
};

const reducer = function(state = initialState, action) {
  if (action.type === "name") {
    return { ...state, name: action.payload };
  } else {
    return state;
  }
};

export default reducer;

You might have noticed that I added a middleware to your store, and that's because it's usually the way to go when accessing your current reducer's state in your actions. That said, I installed redux-thunk for that, so in your action, you can have something like this:

export const changeName = () => {
  return (dispatch, getState) => {
    if (getState().name === "Tarun") {
      dispatch({ type: "name", payload: "Subhash" });
    } else {
      dispatch({ type: "name", payload: "Tarun" });
    }
  };
};

Now, with your store being provided to your app, your reducer being done and your actions being ready to go, you can connect different components to your reducer.

You use the high order component in react-redux called connect for that. For example, in your Name component, we can connect the name to be displayed to your reducer by mapping your state to the component's props:

function Name(props) {
  return <div>My name is: {props.name}</div>;
}

const mapStateToProps = state => {
  return {
    name: state.name
  };
};

export default connect(mapStateToProps)(Name);

The nice thing here is that you can also leave the first parameter in the connect high order component empty and just pass the second, which would be the dispatch functions. Well, that's what you would do in your App component, you would connect it to the changeName action.

function App(props) {
  return (
    <div>
      <button onClick={props.changeName}>Click me</button>
      <Name />
    </div>
  );
}

const mapDispatchToProps = dispatch => {
  return {
    changeName: () => dispatch(changeName())
  };
};

export default connect(
  null,
  mapDispatchToProps
)(App);

Now, when App dispatches a changeName action, your reducer state will be updated and the other components that are connected to the reducer's state will re-render.

Summary: Try to think of your store as an empty jar of candies. Your jar starts empty, but different actions could change what's inside the jar. On top of that, different people in the house that know where the jar is can go get some candy. Translating to your problem, your app begins with an empty name and you have an action that sets up a name. The components that know where to find that name by being connected to your reducer will know when that name changes and will get the updated name.

The final code can be found here:

Edit vigilant-field-shfj7

Upvotes: 4

Nicholas Tower
Nicholas Tower

Reputation: 84902

The only way your name component will rerender is its props or state change, or if a parent component rerenders. Making a change in redux will not automatically do this. In order to see changes to the state, you'd need to subscribe to those changes. You could do this yourself, but a far better solution is to use react-redux, which is designed for connecting react components to redux stores.

For example, you'd add a provider to your app:

import { Provider } from 'react-redux';
import { store } from './Store'

export default function App () {  
  return (
    <Provider store={store}>
      <div>
        <button onClick={changeName}>Click me</button>
        <Name />
      </div>
    </Provider>
  )
}

And then you'd use connect with your Name component:

import { connect } from 'react-redux';
function Name(props) {
  return (
    <div>           
      My name is: {props.name}
    </div>
  )
}

const mapStateToProps = (state) => {
  return { name: state };
}

export default connect(mapStateToProps)(Name)

Upvotes: 0

Related Questions