programing

redux 형식의 Ajax 호출과 같은 비동기 소스를 기반으로 초기 값을 설정하는 방법

bestprogram 2023. 8. 20. 12:17

redux 형식의 Ajax 호출과 같은 비동기 소스를 기반으로 초기 값을 설정하는 방법

공식 페이지와 GitHub redux-form 이슈에는 initialValues로 작업하는 방법에 대한 예가 두 가지 이상 있지만 비동기 소스에 대응하여 initialValues를 설정하는 방법을 설명하는 데 초점을 맞춘 예가 하나도 없습니다.

제가 염두에 두고 있는 주요 사례는 사용자가 이미 존재하는 엔티티를 편집하는 단순한 CRUD 애플리케이션과 같은 것입니다.뷰를 처음 열고 redux-form 구성요소를 마운트한 경우 구성요소가 렌더링되기 전에 initialValues를 설정해야 합니다.이 예에서 구성 요소를 처음 마운트하고 렌더링할 때 필요에 따라 데이터가 로드된다고 가정해 보겠습니다.이 예에서는 하드 코딩된 값 또는 중복 제거 저장소 상태를 기반으로 초기 값을 설정하는 방법을 보여주지만 XHR 호출 또는 가져오기와 같은 비동기식을 기반으로 초기 값을 설정하는 방법에 초점을 맞출 수 없습니다.

기본적인 것을 놓치고 있는 것 같은데 올바른 방향을 알려주세요.

참조:

편집: ReduxForm 문서에서 업데이트된 솔루션

이것은 현재 ReduxForm의 최신 버전에 문서화되어 있으며 이전 답변보다 훨씬 단순합니다.

중요한 것은connect양식 구성요소를 ReduxForm으로 장식합니다.그러면 액세스할 수 있습니다.initialValues구성 요소의 다른 지지대와 마찬가지로 지지대를 사용할 수 있습니다.

// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
InitializeFromStateForm = reduxForm({
  form: 'initializeFromState'
})(InitializeFromStateForm)

// now set initialValues using data from your store state
InitializeFromStateForm = connect(
  state => ({
    initialValues: state.account.data 
  })
)(InitializeFromStateForm)

저는 redux-form reducer plugin 방식을 사용하여 이를 달성했습니다.

다음 데모에서는 비동기 데이터를 가져오고 응답으로 사용자 양식을 미리 채우는 방법을 보여 줍니다.

const RECEIVE_USER = 'RECEIVE_USER';

// once you've received data from api dispatch action
const receiveUser = (user) => {
    return {
       type: RECEIVE_USER,
       payload: { user }
    }
}

// here is your async request to retrieve user data
const fetchUser = (id) => dispatch => {
   return fetch('http://getuser.api')
            .then(response => response.json())
            .then(json => receiveUser(json));
}

그런 다음 루트 리듀서에 다음을 포함합니다.redux-formreducer 반환된 가져온 데이터로 폼 값을 재정의하는 reducer 플러그인을 포함할 수 있습니다.

const formPluginReducer = {
   form: formReducer.plugin({
      // this would be the name of the form you're trying to populate
      user: (state, action) => {
         switch (action.type) {
             case RECEIVE_USER:
                return {
                  ...state,
                  values: {
                      ...state.values,
                      ...action.payload.user
                  }
               }
            default:
               return state;
         }
      }
   })
};

const rootReducer = combineReducers({
   ...formPluginReducer,
   ...yourOtherReducers
});

마지막으로 새로운 폼리듀서를 앱의 다른 리듀서와 결합하는 것을 포함합니다.

참고 다음은 가져온 사용자 개체의 키가 사용자 양식의 필드 이름과 일치한다고 가정합니다.그렇지 않은 경우 매핑할 데이터 필드에 대해 추가 단계를 수행해야 합니다.

기본적으로 initialValues를 통해 폼 구성요소를 한 번만 초기화할 수 있습니다.양식 구성요소를 새 "pristen" 값으로 다시 초기화하는 두 가지 방법이 있습니다.

enableReinitializeprop 또는 reduxForm() 구성 매개 변수 집합을 true로 전달하여 초기 값 속성이 변경될 때마다 폼이 새 "prinsta" 값으로 다시 초기화되도록 합니다.다시 초기화할 때 더티 폼 값을 유지하려면 keepDirtyOnReinitialize를 true로 설정할 수 있습니다.기본적으로 양식을 다시 초기화하면 모든 더티 값이 "pristen" 값으로 바뀝니다.

INITIALIZE 작업을 발송합니다(redux-form에서 제공하는 작업 작성자 사용).

http://redux-form.com/6.1.1/examples/initializeFromState/ 에서 참조.▁:

WillMount() 구성 요소에 대한 디스패치를 실행하고 상태를 loading으로 설정할 수 있습니다.

로드하는 동안 예를 들어 요청이 값으로 반환될 때만 스피너를 렌더링하고 상태를 업데이트한 다음 값으로 폼을 다시 렌더링하시겠습니까?

다음은 비동기 소스를 기반으로 초기 값을 설정하는 방법에 대한 최소 작업 예제입니다.
초기화 작업 작성자를 사용합니다.

initialValues의 모든 값을 정의할 수 없습니다. 그렇지 않으면 무한 루프가 발생합니다.

// import { Field, reduxForm, change, initialize } from 'redux-form';

async someAsyncMethod() {
  // fetch data from server
  await this.props.getProducts(),

  // this allows to get current values of props after promises and benefits code readability
  const { products } = this.props;

  const initialValues = { productsField: products };

  // set values as pristine to be able to detect changes
  this.props.dispatch(initialize(
    'myForm',
    initialValues,
  ));
}

이 방법이 최선의 해결책은 아닐 수도 있지만, 제 요구사항에 충분히 부합합니다.

  • 진입 시 API에 대한 AJAX 요청
  • 요청이 충족되거나 서버 오류를 표시할 때 데이터로 양식을 초기화합니다.
  • 폼을 재설정해도 초기 시드 데이터로 재설정됩니다.
  • 양식을 다른 용도로 재사용할 수 있습니다(예: 단순 if 문이 초기 값 설정을 무시할 수 있음).게시물 추가 및 편집 게시물 추가 또는 주석 추가 및 주석 편집 등
  • 종료 시 Redux 양식에서 데이터가 제거됩니다(블로그 구성 요소에서 새 데이터를 다시 렌더링하므로 Redux에 저장할 이유가 없음).

양식.jsx:

import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { browserHistory, Link } from 'react-router';

import { editPost, fetchPost } from '../../actions/BlogActions.jsx';
import NotFound from '../../components/presentational/notfound/NotFound.jsx';
import RenderAlert from '../../components/presentational/app/RenderAlert.jsx';   
import Spinner from '../../components/presentational/loaders/Spinner.jsx'; 

// form validation checks
const validate = (values) => {
  const errors = {}
  if (!values.title) {
    errors.title = 'Required';
  }

  if (!values.image) {
    errors.image = 'Required';
  }

  if (!values.description) {
    errors.description = 'Required';
  } else if  (values.description.length > 10000) {
    errors.description = 'Error! Must be 10,000 characters or less!';
  }

  return errors;
}

// renders input fields
const renderInputField = ({ input, label, type, meta: { touched, error } }) => (
  <div>
    <label>{label}</label>
    <div>
      <input {...input} className="form-details complete-expand" placeholder={label} type={type}/>
      {touched && error && <div className="error-handlers "><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
    </div>
  </div>
)

// renders a text area field
const renderAreaField = ({ textarea, input, label, type, meta: { touched, error } }) => (
  <div>
    <label>{label}</label>
    <div>
      <textarea {...input} className="form-details complete-expand" placeholder={label} type={type}/>
      {touched && error && <div className="error-handlers"><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
    </div>
  </div>
)

class BlogPostForm extends Component {   
  constructor() {
    super();

    this.state = {
      isLoaded: false,
      requestTimeout: false,
    };
  }

  componentDidMount() {
    if (this.props.location.query.postId) {
      // sets a 5 second server timeout
      this.timeout = setInterval(this.timer.bind(this), 5000);
      // AJAX request to API 
      fetchPost(this.props.location.query.postId).then((res) => {
        // if data returned, seed Redux form
        if (res.foundPost) this.initializeForm(res.foundPost);
        // if data present, set isLoaded to true, otherwise set a server error
        this.setState({
          isLoaded: (res.foundPost) ? true : false,
          serverError: (res.err) ? res.err : ''
        });
      });
    }
  }

  componentWillUnmount() {
    this.clearTimeout();
  }

  timer() {
    this.setState({ requestTimeout: true });
    this.clearTimeout();
  }

  clearTimeout() {
    clearInterval(this.timeout);
  }

  // initialize Redux form from API supplied data
  initializeForm(foundPost) {

    const initData = {
      id: foundPost._id,
      title: foundPost.title,
      image: foundPost.image,
      imgtitle: foundPost.imgtitle,
      description: foundPost.description
    }

    this.props.initialize(initData);
  }

  // onSubmit => take Redux form props and send back to server
  handleFormSubmit(formProps) {
    editPost(formProps).then((res) => {
      if (res.err) {
        this.setState({
          serverError: res.err
        });
      } else {
        browserHistory.push(/blog);
      }
    });
  }

  renderServerError() {
    const { serverError } = this.state;
    // if form submission returns a server error, display the error
    if (serverError) return <RenderAlert errorMessage={serverError} />
  }

  render() {
    const { handleSubmit, pristine, reset, submitting, fields: { title, image, imgtitle, description } } = this.props;
    const { isLoaded, requestTimeout, serverError } = this.state;

    // if data hasn't returned from AJAX request, then render a spinner 
    if (this.props.location.query.postId && !isLoaded) {
      // if AJAX request returns an error or request has timed out, show NotFound component
      if (serverError || requestTimeout) return <NotFound />

      return <Spinner />
     }

    // if above conditions are met, clear the timeout, otherwise it'll cause the component to re-render on timer's setState function
    this.clearTimeout();

    return (
      <div className="col-sm-12">
        <div className="form-container">
          <h1>Edit Form</h1>
          <hr />
          <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
            <Field name="title" type="text" component={renderInputField} label="Post Title" />
            <Field name="image" type="text" component={renderInputField} label="Image URL" />
            <Field name="imgtitle" component={renderInputField} label="Image Description" />
            <Field name="description" component={renderAreaField} label="Description" />
            <div>
              <button type="submit" className="btn btn-primary partial-expand rounded" disabled={submitting}>Submit</button>
              <button type="button" className="btn btn-danger partial-expand rounded f-r" disabled={ pristine || submitting } onClick={ reset }>Clear Values</button>
            </div>
          </form>
         { this.renderServerError() }
        </div>
      </div>
    )
  }
}

BlogPostForm = reduxForm({
  form: 'BlogPostForm',
  validate,
  fields: ['name', 'image', 'imgtitle', 'description']
})(BlogPostForm);


export default BlogPostForm = connect(BlogPostForm);

BlogActions.jsx:

import * as app from 'axios';

const ROOT_URL = 'http://localhost:3001';

// submits Redux form data to server
export const editPost = ({ id, title, image, imgtitle, description, navTitle }) => {
 return app.put(`${ROOT_URL}/post/edit/${id}?userId=${config.user}`, { id, title, image, imgtitle, description, navTitle }, config)
 .then(response => {
   return { success: response.data.message }
  })
  .catch(({ response }) => {
    if(response.data.deniedAccess) {
      return { err: response.data.deniedAccess }
    } else {
      return { err: response.data.err }
    }
  });
}

// fetches a single post from the server for front-end editing     
export const fetchPost = (id) => {
  return app.get(`${ROOT_URL}/posts/${id}`)
  .then(response => {
     return { foundPost: response.data.post}
   })
   .catch(({ response }) => {
     return { err: response.data.err };
   });
}    

RenderAlert.jsx:

import React, { Component } from 'react';

const RenderAlert = (props) => {   
    const displayMessage = () => {
      const { errorMessage } = props;

      if (errorMessage) {
        return (
          <div className="callout-alert">
            <p>
              <i className="fa fa-exclamation-triangle" aria-hidden="true"/>
              <strong>Error! </strong> { errorMessage }
            </p>
          </div>
        );
      }
    }

    return (
      <div>
        { displayMessage() }
      </div>
    );
  }


export default RenderAlert;

Reducers.jsx

import { routerReducer as routing } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import { combineReducers } from 'redux';  

const rootReducer = combineReducers({
  form: formReducer,
  routing
});

export default rootReducer;

사용:

UpdateUserForm = reduxForm({
  enableReinitialize: true,
  destroyOnUnmount: false,
  form: 'update_user_form' // a unique identifier for this form
})(UpdateUserForm);

UpdateUserForm = connect(
  (state) => ({
    initialValues: state.userManagment.userSingle
  })
)(UpdateUserForm);
export default UpdateUserForm;

언급URL : https://stackoverflow.com/questions/34271910/how-to-set-initialvalues-based-on-async-source-such-as-an-ajax-call-with-redux-f