प्रतिक्रिया / Redux - ऐप लोड / init पर प्रेषण कार्रवाई


85

मेरे पास एक सर्वर से टोकन प्रमाणीकरण है, इसलिए जब मेरा Redux ऐप शुरू में लोड होता है, तो मुझे इस सर्वर से यह जांचने के लिए अनुरोध करना होगा कि उपयोगकर्ता प्रमाणित है या नहीं, और यदि हाँ, तो मुझे टोकन मिलना चाहिए।

मैंने पाया है कि Redux कोर INIT क्रियाओं का उपयोग करने की अनुशंसा नहीं की जाती है, इसलिए मैं एप्लिकेशन रेंडर करने से पहले एक कार्रवाई कैसे भेज सकता हूं?

जवाबों:


77

आप रूट componentDidMountमेथड में एक्शन भेज सकते हैं और renderमेथड में आप स्टेटस स्टेटस को वेरिफाई कर सकते हैं।

कुछ इस तरह:

class App extends Component {
  componentDidMount() {
    this.props.getAuth()
  }

  render() {
    return this.props.isReady
      ? <div> ready </div>
      : <div>not ready</div>
  }
}

const mapStateToProps = (state) => ({
  isReady: state.isReady,
})

const mapDispatchToProps = {
  getAuth,
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

1
मेरे लिए componentWillMount()किया। मैंने mapDispatchToProps()App.js से संबंधित सभी प्रेषण संबंधित कार्यों को बुलाते हुए एक साधारण फ़ंक्शन को परिभाषित किया और इसे अंदर बुलाया componentWillMount()
फ्राक्स

यह बहुत अच्छा है, लेकिन MapDispatchToProps का उपयोग करना अधिक वर्णनात्मक लगता है। इसके बजाय mapStateToProps का उपयोग करने के पीछे आपका तर्क क्या है?
tcelferact

@ adc17 Oooops :) टिप्पणी के लिए धन्यवाद। मैंने अपना उत्तर बदल दिया है!
सेरही बरनियुक

@ सिरहि बरनिउक कोई चिंता नहीं। एक अन्य बात: मान getAuthलेना एक एक्शन क्रिएटर है, मुझे लगता है कि आप dispatchएक परम के रूप में परिभाषित करना चाहते हैं mapDispatchToProps, अर्थात const mapDispatchToProps = dispatch => {और फिर कुछ ऐसा करेंगे:getAuth: () => { dispatch(getAuth()); }
tcelferact

2
इस समाधान को लागू करने की कोशिश करते समय मुझे यह त्रुटि मिलीUncaught Error: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)".
अंक

35

मैं इसके लिए आगे रखे गए किसी भी समाधान से खुश नहीं हूं, और फिर मेरे साथ यह हुआ कि मैं कक्षाओं के बारे में सोच रहा था, जिन्हें प्रस्तुत करने की आवश्यकता थी। क्या होगा अगर मैंने अभी स्टार्टअप के लिए एक क्लास बनाई है और फिर चीजों को componentDidMountविधि में धकेल दिया है और बस renderडिस्प्ले को लोडिंग स्क्रीन है?

<Provider store={store}>
  <Startup>
    <Router>
      <Switch>
        <Route exact path='/' component={Homepage} />
      </Switch>
    </Router>
  </Startup>
</Provider>

और फिर कुछ इस तरह है:

class Startup extends Component {
  static propTypes = {
    connection: PropTypes.object
  }
  componentDidMount() {
    this.props.actions.initialiseConnection();
  }
  render() {
    return this.props.connection
      ? this.props.children
      : (<p>Loading...</p>);
  }
}

function mapStateToProps(state) {
  return {
    connection: state.connection
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Startup);

फिर अपने ऐप को इनिशियलाइज़ करने के लिए कुछ रिडक्स एक्शन लिखें। एक इलाज करता है।


अब यही वह उपाय है जिसकी मुझे तलाश है! मेरा मानना ​​है कि यहां आपकी अंतर्दृष्टि पूरी तरह से सही है। धन्यवाद।
यानिवजीके

26

यहां सभी उत्तर एक मूल घटक बनाने और इसे घटकडाउन में फायर करने पर भिन्नता प्रतीत होती है। Redux के बारे में मुझे जो सबसे ज्यादा पसंद है, वह यह है कि यह घटक जीवन चक्रों से प्राप्त होने वाले डेटा को कम कर देता है। मुझे कोई कारण नहीं दिखता कि इस मामले में कोई अलग क्यों होना चाहिए।

यदि आप अपने स्टोर को रूट index.jsफाइल में इम्पोर्ट कर रहे हैं , तो आप बस अपने एक्शन क्रिएटर (इसे कॉल कर सकते हैं initScript()) को उस फाइल में भेज सकते हैं और कुछ भी लोड होने से पहले उसमें आग लग जाएगी।

उदाहरण के लिए:

//index.js

store.dispatch(initScript());

ReactDOM.render(
  <Provider store={store}>
    <Routes />
  </Provider>,
  document.getElementById('root')
);

1
मैं एक प्रतिक्रिया नौसिखिया हूँ, लेकिन प्रतिक्रिया और redux अवधारणाओं पर प्रारंभिक दस्तावेज़ीकरण पढ़ने के आधार पर, मेरा मानना ​​है कि यह सबसे उपयुक्त तरीका है। क्या किसी componentDidMountईवेंट पर इन इनिशियलाइज़ेशन को बनाने का कोई फायदा है ?
कुजदितोमी

1
वास्तव में यह स्थिति पर निर्भर करता है। तो componentDidMountएक विशिष्ट घटक आरोह से पहले आग लग जाएगी। store.dispatch()ReactDOM.render से पहले फायरिंग () `ऐप माउंट होने से पहले आग। यह componentWillMountपूरे ऐप के लिए एक तरह का है । नौसिखिया के रूप में, मुझे लगता है कि घटक जीवन चक्र विधियों का उपयोग करने के लिए छड़ी करना बेहतर है क्योंकि यह तर्क को कसकर युग्मित रखता है जहां इसका उपयोग किया जा रहा है। जैसे-जैसे ऐप्स अधिक से अधिक जटिल होते जाते हैं, यह करते रहना कठिन हो जाता है। मेरी सलाह होगी कि जब तक आप कर सकते हैं, तब तक इसे सरल रखें।
जोश पिटमैन

1
मुझे हाल ही में ऊपर दिए गए दृष्टिकोण का उपयोग करना पड़ा। मेरे पास एक Google लॉगिन बटन था और मुझे ऐप लोड होने से पहले इसे काम करने के लिए एक स्क्रिप्ट को आग लगाने की आवश्यकता थी। अगर मैं ऐप को लोड करने के लिए इंतजार करता हूं और फिर कॉल करता हूं, तो प्रतिक्रिया मिलने में अधिक समय लगेगा और ऐप में कार्यक्षमता में देरी होगी। यदि जीवनचक्र में चीजें करना आपके उपयोग के मामले में काम करता है, तो जीवनचक्र से चिपके रहें। वे सोचने में सरल होते हैं। इसे जज करने का एक अच्छा तरीका यह है कि अब से 6 महीने बाद कोड को देखते हुए खुद को तस्वीर दें। सहजता से समझने के लिए आपके लिए कौन सा दृष्टिकोण आसान होगा। उस दृष्टिकोण को उठाओ।
जोश पिटमैन

नमस्कार @JoshPittman, आपको अभी भी रूट घटक को कनेक्ट करने की आवश्यकता है, उदाहरण के लिए "ऐप" या कुछ इसी तरह की स्थिति redux राज्य के अपडेट की सदस्यता के लिए। तो यह वैसा ही है जैसे आप इसके कंपोनेंटडिमाउंट () विधि से केवल एक्शन भेजने से बचें।
तुआन्हकवर्स

1
मैं प्रेषण के बारे में आपकी बात को हां कहता हूं। Redux यह नहीं कहता कि हमें प्रतिक्रिया घटक के अंदर से क्रियाओं को भेजना है। Redux निश्चित रूप से प्रतिक्रिया से स्वतंत्र है।
तुआन्हकवर्स

16

यदि आप रिएक्ट हुक का उपयोग कर रहे हैं, तो एक एकल लाइन समाधान होगा:

useEffect(() => store.dispatch(handleAppInit()), []);

खाली सरणी यह ​​सुनिश्चित करेगी कि इसे केवल एक बार, पहले रेंडर पर कहा जाए।

पूर्ण उदाहरण:

import React, { useEffect } from 'react';
import { Provider } from 'react-redux';

import AppInitActions from './store/actions/appInit';

function App() {
  useEffect(() => store.dispatch(AppInitActions.handleAppInit()), []);
  return (
    <Provider store={store}>
      <div>
        Hello World
      </div>
    </Provider>
  );
}

export default App;

11

अद्यतन 2020 : अन्य समाधानों के साथ, मैं असफल लॉगिन प्रयासों के लिए प्रत्येक अनुरोध की जांच करने के लिए Redux मिडलवेयर का उपयोग कर रहा हूं:

export default () => next => action => {
  const result = next(action);
  const { type, payload } = result;

  if (type.endsWith('Failure')) {
    if (payload.status === 401) {
      removeToken();

      window.location.replace('/login');
    }
  }

  return result;
};

अपडेट 2018 : यह उत्तर रिएक्ट राउटर 3 के लिए है

मैंने प्रतिक्रिया-राउटर ऑनर प्रॉपर का उपयोग करके इस समस्या को हल किया। इस तरह कोड दिखता है:

// this function is called only once, before application initially starts to render react-route and any of its related DOM elements
// it can be used to add init config settings to the application
function onAppInit(dispatch) {
  return (nextState, replace, callback) => {
    dispatch(performTokenRequest())
      .then(() => {
        // callback is like a "next" function, app initialization is stopped until it is called.
        callback();
      });
  };
}

const App = () => (
  <Provider store={store}>
    <IntlProvider locale={language} messages={messages}>
      <div>
        <Router history={history}>
          <Route path="/" component={MainLayout} onEnter={onAppInit(store.dispatch)}>
            <IndexRoute component={HomePage} />
            <Route path="about" component={AboutPage} />
          </Route>
        </Router>
      </div>
    </IntlProvider>
  </Provider>
);

11
बस स्पष्ट प्रतिक्रिया होने के लिए-राउटर 4 ऑनर का समर्थन नहीं करता है।
रोब एल

IntlProvider आपको एक बेहतर समाधान पर संकेत देना चाहिए .. नीचे मेरा जवाब देखें।
क्रिस केम्प

यह पुराने प्रतिक्रिया-राउटर v3 का उपयोग करता है, मेरे उत्तर को देखें
स्टेन्डडेव

3

साथ redux-गाथा मिडलवेयर आप इसे अच्छी तरह से कर सकते हैं।

बस एक गाथा को परिभाषित करें जो ट्रिगर होने से पहले प्रेषणित क्रिया (जैसे कि साथ takeया takeLatest) के लिए नहीं देख रही है। जब forkरूट गाथा से एड की तरह है कि यह एप्लिकेशन के स्टार्टअप पर एक बार चलेगा।

निम्नलिखित एक अधूरा उदाहरण है जिसके लिए redux-sagaपैकेज के बारे में थोड़ी जानकारी की आवश्यकता होती है, लेकिन बिंदु को दर्शाता है:

कहानियों / launchSaga.js

import { call, put } from 'redux-saga/effects';

import { launchStart, launchComplete } from '../actions/launch';
import { authenticationSuccess } from '../actions/authentication';
import { getAuthData } from '../utils/authentication';
// ... imports of other actions/functions etc..

/**
 * Place for initial configurations to run once when the app starts.
 */
const launchSaga = function* launchSaga() {
  yield put(launchStart());

  // Your authentication handling can go here.
  const authData = yield call(getAuthData, { params: ... });
  // ... some more authentication logic
  yield put(authenticationSuccess(authData));  // dispatch an action to notify the redux store of your authentication result

  yield put(launchComplete());
};

export default [launchSaga];

ऊपर दिया गया कोड एक launchStartऔर launchCompleteredux कार्रवाई भेजता है जिसे आपको बनाना चाहिए। इस तरह के कार्यों को बनाने के लिए एक अच्छा अभ्यास है क्योंकि जब भी लॉन्च शुरू या पूरा हो जाता है तो वे राज्य को अन्य सामान करने के लिए सूचित करने के लिए काम में आते हैं।

आपकी मूल गाथा को इस launchSagaगाथा को समझना चाहिए :

कहानियों / index.js

import { fork, all } from 'redux-saga/effects';
import launchSaga from './launchSaga';
// ... other saga imports

// Single entry point to start all sagas at once
const root = function* rootSaga() {
  yield all([
    fork( ... )
    // ... other sagas
    fork(launchSaga)
  ]);
};

export default root;

कृपया इसके बारे में अधिक जानकारी के लिए redux-saga का वास्तव में अच्छा प्रलेखन पढ़ें ।


जब तक यह क्रिया सही नहीं हो जाती, तब तक पेज लोड नहीं होगा?
मार्कोव

1

यहाँ प्रतिक्रिया (16.8), हुक में नवीनतम का उपयोग कर एक उत्तर दिया गया है:

import { appPreInit } from '../store/actions';
// app preInit is an action: const appPreInit = () => ({ type: APP_PRE_INIT })
import { useDispatch } from 'react-redux';
export default App() {
    const dispatch = useDispatch();
    // only change the dispatch effect when dispatch has changed, which should be never
    useEffect(() => dispatch(appPreInit()), [ dispatch ]);
    return (<div>---your app here---</div>);
}

0

मैं ऐप इनिट पर एक एपीआई एंड-पॉइंट से एक उपयोगकर्ता के तहत खातों को लाने के लिए Redux-thunk का उपयोग कर रहा था, और यह async था इसलिए मेरे ऐप के रेंडर होने के बाद डेटा आ रहा था और ऊपर के अधिकांश समाधान मेरे लिए चमत्कार नहीं थे और कुछ हैं मूल्यह्रास हुआ। तो मैंने देखा घटकव्यूडडेट ()। इसलिए मूल रूप से APP init पर मुझे API से खातों की सूची मिलनी थी, और मेरे redux स्टोर खाते शून्य या [] होंगे। इसके बाद सहारा लिया।

class SwitchAccount extends Component {

    constructor(props) {
        super(props);

        this.Format_Account_List = this.Format_Account_List.bind(this); //function to format list for html form drop down

        //Local state
        this.state = {
                formattedUserAccounts : [],  //Accounts list with html formatting for drop down
                selectedUserAccount: [] //selected account by user

        }

    }



    //Check if accounts has been updated by redux thunk and update state
    componentDidUpdate(prevProps) {

        if (prevProps.accounts !== this.props.accounts) {
            this.Format_Account_List(this.props.accounts);
        }
     }


     //take the JSON data and work with it :-)   
     Format_Account_List(json_data){

        let a_users_list = []; //create user array
        for(let i = 0; i < json_data.length; i++) {

            let data = JSON.parse(json_data[i]);
            let s_username = <option key={i} value={data.s_username}>{data.s_username}</option>;
            a_users_list.push(s_username); //object
        }

        this.setState({formattedUserAccounts: a_users_list}); //state for drop down list (html formatted)

    }

     changeAccount() {

         //do some account change checks here
      }

      render() {


        return (
             <Form >
                <Form.Group >
                    <Form.Control onChange={e => this.setState( {selectedUserAccount : e.target.value})} as="select">
                        {this.state.formattedUserAccounts}
                    </Form.Control>
                </Form.Group>
                <Button variant="info" size="lg" onClick={this.changeAccount} block>Select</Button>
            </Form>
          );


         }    
 }

 const mapStateToProps = state => ({
      accounts: state.accountSelection.accounts, //accounts from redux store
 });


  export default connect(mapStateToProps)(SwitchAccount);

0

यदि आप रिएक्ट हुक का उपयोग कर रहे हैं, तो आप बस React.useEffect का उपयोग करके एक कार्रवाई भेज सकते हैं

React.useEffect(props.dispatchOnAuthListener, []);

मैं रजिस्टर onAuthStateChangedश्रोता के लिए इस पैटर्न का उपयोग करता हूं

function App(props) {
  const [user, setUser] = React.useState(props.authUser);
  React.useEffect(() => setUser(props.authUser), [props.authUser]);
  React.useEffect(props.dispatchOnAuthListener, []);
  return <>{user.loading ? "Loading.." :"Hello! User"}<>;
}

const mapStateToProps = (state) => {
  return {
    authUser: state.authentication,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    dispatchOnAuthListener: () => dispatch(registerOnAuthListener()),
  };
};

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

-1

का उपयोग करना: अपोलो क्लाइंट 2.0, रिएक्ट-राउटर v4, रिएक्ट 16 (फाइबर)

उत्तर चयनित पुराने प्रतिक्रिया रूटर v3 का उपयोग करें। ऐप के लिए वैश्विक सेटिंग्स को लोड करने के लिए मुझे 'प्रेषण' करने की आवश्यकता थी। ट्रिक कंपोनेंटविलीपेड का उपयोग कर रहा है, हालांकि उदाहरण अपोलो क्लाइंट का उपयोग कर रहा है, और समाधान नहीं लाने के बराबर है। आपको गुलदस्ते की आवश्यकता नहीं है

SettingsLoad.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from "redux";
import {
  graphql,
  compose,
} from 'react-apollo';

import {appSettingsLoad} from './actions/appActions';
import defQls from './defQls';
import {resolvePathObj} from "./utils/helper";
class SettingsLoad extends Component {

  constructor(props) {
    super(props);
  }

  componentWillMount() { // this give infinite loop or no sense if componente will mount or not, because render is called a lot of times

  }

  //componentWillReceiveProps(newProps) { // this give infinite loop
  componentWillUpdate(newProps) {

    const newrecord = resolvePathObj(newProps, 'getOrgSettings.getOrgSettings.record');
    const oldrecord = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    if (newrecord === oldrecord) {
      // when oldrecord (undefined) !== newrecord (string), means ql is loaded, and this will happens
      //  one time, rest of time:
      //     oldrecord (undefined) == newrecord (undefined)  // nothing loaded
      //     oldrecord (string) == newrecord (string)   // ql loaded and present in props
      return false;
    }
    if (typeof newrecord ==='undefined') {
      return false;
    }
    // here will executed one time
    setTimeout(() => {
      this.props.appSettingsLoad( JSON.parse(this.props.getOrgSettings.getOrgSettings.record));
    }, 1000);

  }
  componentDidMount() {
    //console.log('did mount this props', this.props);

  }

  render() {
    const record = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    return record
      ? this.props.children
      : (<p>...</p>);
  }
}

const withGraphql = compose(

  graphql(defQls.loadTable, {
    name: 'loadTable',
    options: props => {
      const optionsValues = {  };
      optionsValues.fetchPolicy = 'network-only';
      return optionsValues ;
    },
  }),
)(SettingsLoad);


const mapStateToProps = (state, ownProps) => {
  return {
    myState: state,
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators ({appSettingsLoad, dispatch }, dispatch );  // to set this.props.dispatch
};

const ComponentFull = connect(
  mapStateToProps ,
  mapDispatchToProps,
)(withGraphql);

export default ComponentFull;

App.js

class App extends Component<Props> {
  render() {

    return (
        <ApolloProvider client={client}>
          <Provider store={store} >
            <SettingsLoad>
              <BrowserRouter>
            <Switch>
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/myaccount"
                component={MyAccount}
                title="form.myAccount"
              />
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/dashboard"
                component={Dashboard}
                title="menu.dashboard"
              />

2
यह कोड अधूरा है और सवाल के लिए अप्रासंगिक भागों के लिए ट्रिमिंग की आवश्यकता है।
नावीस गोल्डन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.