Format date in Redux Reducer before it hits state

对着背影说爱祢 提交于 2020-08-24 06:36:20

问题


how can I format a date from a standard JS date object, which has come from the server and is fed into React via Redux through a REST api, which has an initial output of:

"2016-05-16T13:07:00.000Z"

Into a different format? So it is listed as DD MM YYYY?

Can I do this in the Reducer of Action by manipulating the request result, or the payload:

import { FETCH_BOOKS, FETCH_BOOK } from '../actions';

const INITIAL_STATE = { books: [], book: null };

export default function(state = INITIAL_STATE, action) {
    switch(action.type) {
        case FETCH_BOOK:
            return { ...state, book: action.payload.data };
        case FETCH_BOOKS:
            return { ...state, books: action.payload.data };
    }
    return state;
}

With action:

export function fetchBooks() {
    const request = axios.get('/api/books');

    return {
        type: FETCH_BOOKS,
        payload: request
    }
}

fetchBooks() is called in the componentWillMount() method of the Books React component:

componentWillMount() {
    this.props.fetchBooks();
}

The api returns the following structured JSON:

[{"_id":0,"date":"2016-05-16T13:07:00.000Z","title":"Eloquent JavaScript","description":"Learn JavaScript"}]

Update

Thank you very much Nicole and nrabinowitz - I have implemented the following changes... in the action creators

function transformDateFormat(json) {
    const book = {
        ...json,
        id: json.data._id,
        // date: new Date(moment(json.data.date).format('yyyy-MM-dd'))
        date: new Date(json.data.date)
    }
    console.log(book);
    return book;
}

export function fetchBook(id) {
    const request = axios.get(`/api/books/${id}`)
        .then(transformDateFormat);

    return {
        type: FETCH_BOOK,
        payload: request 
    }
};

I am logging the book object in the transform date function, and it is adding id and date to the root of the object, but the structure of the json actually needs to be book.data.id and book.data.date. I'm not sure how to create the correct structure within the transformDate function to return to the action creator.

This is the book object from the transform date function console log:

Object {data: Object, status: 200, statusText: "OK", headers: Object, config: Object…}
config: Object
data: Object
    __v:0
    _id: "5749f1e0c373602b0e000001"
    date: "2016-05-28T00:00:00.000Z"
    name:"Eloquent JavaScript"
    __proto__:Object
date: Sat May 28 2016 01:00:00 GMT+0100 (BST)
headers: Object
id: "5749f1e0c373602b0e000001"
request: XMLHttpRequest
status: 200
statusText: "OK"
__proto__: Object

My function needs to place the id and date inside the data object. (The date is just standard date format as something is wrong with my implementation of moment (date: new Date(moment(json.data.date).format('yyyy-MM-dd')) returns Invalid date).

Thanks again for all your help - really appreciated.


回答1:


I would do this transformation immediately when the data arrives in the frontend, i.e. in your AJAX request. This way, your frontend application only works with the data as you intend them to be shaped, and your request handler encapsulates and hides the data structures as they are provided from the server and passes the data to the frontend application in the desired shape.

(In Domain-Driven Design, this goes under the name of "Anticorruption Layer").

Regarding the technical solution:

Currently, your event payload contains a promise to the data. You don't show us where you invoke fetchBooks(), i.e. where the promise is executed. That's where you need to do the data transformation. (But personally, I'd rather keep the data transformation inside the fetchBooks function.)

Also, that's the point where you need to send the data to the redux store. To be able to reduce events asynchronously, you need something like redux-thunk.

A solution might look like this (my apologies, I'm not very experienced in using promises, so I'll use a callback here, but maybe you can somehow translate that to a promise):

export function fetchBooks(callback) {
    const request = axios.get('/api/books')
                    .then(function (response) {
                       callback(transformData(response));
                    });
}

transformData is supposed to do the date transformation from backend format to frontend format.

Somewhere else you probably trigger an action that gets emitted in order to fetch the books (let's call it loadBooks for now). That's where you need to do the asynchronous dispatch:

function loadBooks() {
    return (dispatch, getState) => {

        fetchBooks(response => {
            dispatch(receiveBooks(response));
        });
    };
}

function receiveBooks(data) {
    return {
        type: FETCH_BOOKS,
        payload: data
    };
}

This way, you will only dispatch the data to the redux store once it was actually returned from the AJAX call.

To use redux-thunk, you need to inject its middleware into your store like this:

import thunkMiddleware from "redux-thunk";
import { createStore, applyMiddleware } from "redux";

const createStoreWithMiddleware = applyMiddleware(
    thunkMiddleware
)(createStore);

and then pass your reducers function to this store.

Please keep asking if you have any further questions.




回答2:


I agree with @Nicole here - this is best handled in the AJAX client. One pattern I often use here is a DataMapper, something like:

function jsonToBook(json) {
    // do whatever transformations you need here
    var book = {
        ...json,
        id: json._id,
        date: new Date(json.date)
    }
    // return the transformed, app-appropriate object
    return book;
}

Then, in your AJAX client, you can put the transform into a .then call:

const request = axios.get('/api/books')
    .then(jsonToBook);

This still returns a promise, so your Promise middleware should still work as expected. One advantage of this is that the data mapper is really easy to test, but logic in the AJAX client itself is harder.

One other point here is that I'd recommend parsing to a Date object, not a formatted string. This is opinion, but in general I think that formatting concerns are the province of the view (that is, the React components) rather than the store/reducers. Passing a Date instead of a string makes it easier to format the date differently in different parts of the app, for example.

For the actual date formatting, see Moment.js or D3's time-formatting utils.




回答3:


Rather than modifying the response once received, it may be better to address the issue using axios library's transformResponse function as part of the call per the [axios docs][1] and [example][2]:

const ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}.\d{3})Z/;

export function fetchBooks() {
   const request = axios.get('/api/books', {
      transformResponse: axios.defaults.transformResponse.concat(function (data, headers) {
        Object.keys(data).forEach(function (k) {
          if (ISO_8601.test(data[k])) {
            data[k] = new Date(Date.parse(data[k]));
          }
        });
        return data;
      })
    });

  return {
    type: FETCH_BOOKS,
    payload: request
  }
}

If you want the dates to be consistently returned as objects throughout your application then an interceptor. Note that the following example will only update the top-level objects from strings to dates rather than nested objects:

axios.interceptors.response.use(function (response) {
  // Identify data fields returning from server as ISO_8601 formatted dates
  var ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}.\d{3})Z/;

  var data = response.data
  Object.keys(data).forEach(function (k) {
    if (ISO_8601.test(data[k])) {
      data[k] = new Date(Date.parse(data[k]));
    }
  });
  return response;
}, function (error) {
  // Do something with response error
  return Promise.reject(error);
});


来源:https://stackoverflow.com/questions/37253688/format-date-in-redux-reducer-before-it-hits-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!