简体   繁体   中英

Confused with REDUX actions and reducers

so I am trying to refactor some code from my previous question:

React: How to update one component, when something happens on another component

So I started digging deep into the existing code template to see how it was implemented.

I found a reducers.js where I added a new reducer: ActiveTenant

import Auth from './auth/reducer';
import App from './app/reducer';
import ThemeSwitcher from './themeSwitcher/reducer';
import LanguageSwitcher from './languageSwitcher/reducer';
import ActiveTenant from './activetenant/reducer';

export default {
  Auth,
  App,
  LanguageSwitcher,
  ThemeSwitcher,
  ActiveTenant
};

That new reducer is like this:

import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';

const initState = new Map({
    tenantId: ''
});

export default function(state = initState, action) {
  switch (action.type) {
    case actions.SET_TENANT_ACTIVE:
    {
        const options = { 
            method: 'post'
        };

        adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
        .then(response =>{
            if(response.status === 200){
                console.log("Tenant activated");
            }else{
                throw "error";
            }
        })
        .catch(error => {
            console.error(error);
        });

        return state.set('tenant', state.Name);
    }
    default:
      return state;
  }
}

and actions for that reducer

const actions = {
  SET_TENANT_ACTIVE: 'SET_TENANT_ACTIVE',
  setTenantActive: () => ({
    type: actions.SET_TENANT_ACTIVE
  }),
};
export default actions;

Then from the component itself, I need to call the action when a row is selected on the front end, so I have refactored the commented code, into one line.

import React, { Component } from 'react';
import {  Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';

const { setTenantActive } = actions;

class ListTenants extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: []
        };
    }



    fetchData = () => {
        adalApiFetch(fetch, "/Tenant", {})
          .then(response => response.json())
          .then(responseJson => {
            if (!this.isCancelled) {
                const results= responseJson.map(row => ({
                    key: row.id,
                    TestSiteCollectionUrl: row.TestSiteCollectionUrl,
                    TenantName: row.TenantName,
                    Email: row.Email
                  }))
              this.setState({ data: results });
            }
          })
          .catch(error => {
            console.error(error);
          });
      };


    componentDidMount(){
        this.fetchData();
    }

    render() {
        const columns = [
                {
                    title: 'TenantName',
                    dataIndex: 'TenantName',
                    key: 'TenantName',
                }, 
                {
                    title: 'TestSiteCollectionUrl',
                    dataIndex: 'TestSiteCollectionUrl',
                    key: 'TestSiteCollectionUrl',
                }, 
                {
                    title: 'Email',
                    dataIndex: 'Email',
                    key: 'Email',
                }
        ];

        // rowSelection object indicates the need for row selection
        const rowSelection = {
            onChange: (selectedRowKeys, selectedRows) => {
                if(selectedRows[0].TenantName != undefined){
                    console.log(selectedRows[0].TenantName);
                    const options = { 
                        method: 'post'
                    };

                    setTenantActive(selectedRows[0].TenantName);
                    /* adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
                        .then(response =>{
                        if(response.status === 200){
                            Notification(
                                'success',
                                'Tenant set to active',
                                ''
                                );
                        }else{
                            throw "error";
                        }
                        })
                        .catch(error => {
                        Notification(
                            'error',
                            'Tenant not activated',
                            error
                            );
                        console.error(error);
                    }); */
                }
            },
            getCheckboxProps: record => ({
                type: Radio
            }),
        };

        return (
            <Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
        );
    }
}

export default ListTenants;

However, its not clear to me the relationship between the action and the reducer, if I check the debugger the action is executed, and none parameter is received, but the reducer is never executed.

DO i have to put a dispatch somewhere?, what I am missing in this puzzle?

You are using reducers wrong. Reducers are supposed to be pure. Yours has side-effects showing that you haven't understood Redux, yet.

Instead of writing down a solution for you (which would take forever anyways since one would have to explain Redux in total), I suggest you invest the 3 hours and go through the Redux docs and follow the tutorials (they are great).

Afterwards you might want to look into Redux Thunk . But, you might not need thunks .

PS: (Small thing to bring up, but I haven't seen anyone using Map s in Redux. Is there a reason you do that? You might want to use plain objects instead.)

Your action is not correct you should pass an active tenant name as parameter.

Ref. https://redux-starter-kit.js.org/api/createaction

We could have written the action types as inline strings in both places. The action creators are good, but they're not required to use Redux - a component could skip supplying a mapDispatch argument to connect, and just call this.props.dispatch({type : "CREATE_POST", payload : {id : 123, title : "Hello World"}}) itself.

Ref. https://redux-starter-kit.js.org/usage/usage-guide

So the first thing to understand is the Redux Cycle :

Action Creator-->Action-->dispatch-->Reducers-->State

Action Creator : An action creator is a function that is going to create or return a plain JavaScript object knowns as an Action with a type property and payload property which describes some change you want to make on your data.

The payload property describes some context around the change we want to make.

The purpose of an Action is to describe some change to the data inside our application.

The Action Creator is to create the Action .

The dispatch function is going to take in an Action and make copies of that object and pass it off to a bunch of different places inside our application which leads us to the Reducers .

In Redux, a reducer is a function responsible for taking in an Action . Its going to process that Action , make some change to the data and return it so it can be centralized in some location.

In Redux, the State property is a central repository of all information produced by our reducers . All the information gets consolidated inside the State object so our React application can easily reach into our Redux side of the app and get access to all the data inside the application.

So this way the app does not have to go around to each separate reducer and ask for the current State .

So digest that for a couple of minutes and then look at your architecture.

Let's skip over to reducers .

Reducers are called with an Action that was created by an Action Creator . The reducer will take a look at that Action and decide whether it needs to modify some data based on that Action .

So in other words, the job of a reducer is not to execute API requests but to process actions sent to it by the action creator.

So instead of this:

import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';

const initState = new Map({
    tenantId: ''
});

export default function(state = initState, action) {
  switch (action.type) {
    case actions.SET_TENANT_ACTIVE:
    {
        const options = { 
            method: 'post'
        };

        adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
        .then(response =>{
            if(response.status === 200){
                console.log("Tenant activated");
            }else{
                throw "error";
            }
        })
        .catch(error => {
            console.error(error);
        });

        return state.set('tenant', state.Name);
    }
    default:
      return state;
  }
}

Your reducer should look something like this:

import { SET_TENANT_ACTIVE } from "../actions/types";

const initialState = {
    tenantId: ''
};

export default (state = initialState, action) {
  switch (action.type) {
    case SET_TENANT_ACTIVE:
      return {...state, [action.payload.id]: action.payload };
    default:
      return state;
  }
}

Then inside your action creators file, you should have an action creator that looks something like this:

import axios from 'axios';
import { SET_TENANT_ACTIVE } from "../actions/types";


export const setTenant = id => async (dispatch) => {
  const response = await axios.post(`/tenants/${id}`);

  dispatch({ type: SET_TENANT_ACTIVE, payload: response.data });
};

You also need to learn about Redux project structure because after the above refactor, you are missing how to wire all this up to your component. In your component file there is no connect() function which also requires the Provider tag and you have none of that.

So for this I recommend first of all your set up your folder and file structure like so:

  /src
    /actions
    /components
    /reducers
    index.js

So inside your index.js file it should look something like this:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";

import App from "./components/App";
import reducers from "./reducers";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducers,
  composeEnhancers(applyMiddleware(reduxThunk))
);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")

So your goal here is to ensure that you get that Provider tag at the very top of your component hierarchy and ensure that you pass it a reference to your Redux store that gets all the reducers loaded up into it.

So above I have created the store and passed it our set of reducers and it will return back to you all your applications State .

Lastly, what you see above is I created an instance of <Provider> and wrapped the <App /> component with it and then you want to pass the <Provider> component is a single prop called store . The store is the result of calling createStore() and calling the reducers .

The <Provider> is what interacts with the Redux store on our behalf.

Notice, I also have wired up Redux-Thunk that J. Hesters mentioned, you are making an ajax request as far as I can see from your code which is why I offered an asynchronous action creator for you, which means you will need Redux-Thunk or some middleware like that, let me not offend the Redux-Saga fans, so you have those two choice at least. You seem relatively new to Redux, just go with Redux-Thunk.

Now you can use the connect() component inside your component file to finish wiring up those action creators and reducers to your component or your React side of the application.

import React, { Component } from 'react';
import { connect } from "react-redux";
import {  Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';

After importing connect , you create an instance of it below:

export default connect()(ListTenants);

Please don't argue with me on the above syntax ( actually had a former student report me to administrators for using this syntax as evidence of not knowing what I was doing ).

Then you need to configure this connect() React component by adding mapStateToProps if you are going to need it, but definitely pass in actions as the second argument to connect() . If you realize you don't need mapStateToProps , then just pass in null as the first argument, but you can't leave it empty.

Hope all this was helpful and welcome to the wonderful world of React-Redux.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM