I'm building my first React front end and see a number of conventions for messages sent via the Dispatcher. e.g.
{
type: ActionTypes.RECEIVE_RAW_MESSAGES,
rawMessages: rawMessages
}
https://github.com/facebook/flux/blob/master/examples/flux-chat/js/actions/ChatServerActionCreators.js#L21
and
{
source: 'VIEW_ACTION',
action: action
}
http://facebook.github.io/react/blog/2014/09/24/testing-flux-applications.html#putting-it-all-together
What is the best message format to use & why?
The short answer is, it probably doesn't really matter—as long as your stores look for the right data. I always use the following format:
{
type: 'ACTION_TYPE', // usually defined by a constant
payload: { ... } // a payload of JSON serializable types
}
If your app needs to distinguish between actions that are initiated by the user and actions that come from the server or some other source, you may considering adding a source key; I personally use separate action types or data within the payload for this purpose.
I always make payload an object (never a raw value) so that data can be added easily without changing receiving sites. For example, instead of
dispatch({type: ACTION_TYPE, payload: id})
I would recommend
dispatch({type: ACTION_TYPE, payload: {id: id}})
Of course, some of this may be dictated by which flux implementation (if any) that you use. The Facebook dispatcher is very agnostic (you can send pretty much anything you want), but some implementations require specific keys (like type, etc).
Flux Standard Action is a project to standardize Flux actions.
In short, an action must have a type, and may have a error, payload, or meta.
Examples
{
type: 'ADD_TODO',
payload: {
text: 'Do something.'
}
}
For an error:
{
type: 'ADD_TODO',
payload: new Error(),
error: true
}
The meta field is indented for "any extra information that is not part of the payload". I haven't seen this used, so I'd try to stick to payload.
Related
How do I validate my API response when storing it as an object. For example:
const { data } = await axios.get("/form");
How do I make sure this data conforms to:
interface Form {
title: string;
questions: Question[]
}
Typescript does not perform any type of validations out of the box like java or other strong typed languages.
Things like
const { data } = await axios.get<Form>("/form");
does not guarantee that the response data is of Form type. It is just used for static typing checks (no errors/validation in runtime). For having a guarantee I would recommend the implementation of a guard function (https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) with some JSON schema validation. For example, you can use
https://www.npmjs.com/package/ajv or https://www.npmjs.com/package/joi package to do that.
Use:
const { data } = await axios.get<Form>("/form");
If you want to make sure that the incoming data is indeed compatible with Form interface, you have to do it in runtime (thus can't be done with TS). Checking runtime data is compatible with a type isn't always straightforward or easy (especially with nested data). io-ts is one of the libraries that does it.
I am trying to make a request to get all the events of a user, then get the detail of this events in a list. I don't find a right solution to do that.
Database
Actions index
So at the moment, I only get the user's travel, but not the detail of each event that the user have.
Thank you for your help
You'll need to do another on() or once() for each event inside your current callback, and load the additional data. This process is known as a client-side join. Then within the inner loop you can dispatch the results, either on each load or when all are loaded.
Code (untested, so there may be typos):
usersRef.child(uid).child("events").on("value", snapshot => {
var promises = []
snapshot.forEach(eventSnapshot => {
promises.push(eventsRef.child(eventSnapshot.key).once("value"));
})
Promise.all(promises).then(eventSnapshots => {
// eventSnapshots contains the details of all events
return eventSnapshot.map(eventSnapshot => eventSnapshot.val());
}).then(events => {
dispatch({ type: FETCH_EVENTS, payload: events });
});
})
Alternatively you can duplicate the minimal data for each event under /users/$uid/events/$eventid`. This duplicates data, but prevents the need for client-side joins. This type of read-vs-write trade-off is very common when using NoSQL databases. For strategies for the duplicated data up to date, see How to write denormalized data in Firebase.
I have written a backend for my app on .net, that provides me CRUD operations.
For example, the response for POST action is whole new item object:
{"Id":"7575c661-a40b-4161-b535-bd332edccc71","Text":"as","CreatedAt":"2018-09-13T15:29:52.7128732+00:00","LastChange":"2018-09-13T15:29:52.7128732+00:00"}
Now I need to use this Id in my frontend (to rewrite a temporary id I made on frontend until I receive confirmation that the upload was successful).
The problem comes with typescript when I type my thunk action "as I think it's right":
export const uploadItem = (fetch: (text: string) => Promise<Item>) =>
(generateId: () => ItemId) =>
(text: string) =>
async (dispatch: Dispatch<ThunkAction<IAction, IAppState, void>>): Promise<IAction> => {
const id = generateId();
try {
dispatch(addItem(id, text));
const itemWithOfficialId = await fetch(text);
dispatch(synchronizeItemId(id, itemWithOfficialId.id));
return dispatch(setAsSynchronized(itemWithOfficialId.id));
} catch {
return dispatch(requestFailedForItem(id, errorMessageTypes.UPLOAD, 'Failed to upload. '));
}
};
The problem I see is on the first line:
fetch: (text: string) => Promise<Item>
that forces me to do:
itemWithOfficialId.id
because my Item has only property id and I need to extract Id.
But typescript won't let me change it to itemWithOfficialId.Id.
I know I could make a new Item model, that would consist of properties returned from the server and use it like Promise<ItemFromServer>, but it feels a bit cheesy to me and I wanted to know if there was another way how to convert these without creating a new representation of the same object?
If you have two objects with different sets of properties, even if the property names differ only in case, I don't think you can get around declaring two different types. However, there are tools such as Typewriter that you may be able to use to generate the ItemFromServer declaration automatically from your server code.
I have just switched over from studying Java to studying Javascript and I am trying to learn React and Redux right now. I am having some confusion with how some objects seem to be created in this tutorial I have been following on YouTube. This tut has so far just covered logging in and signing into a webpage.
api.js
import axios from "axios";
export default {
user: {
login: credentials =>
axios.post("/api/auth", { credentials }).then(res => res.data.user),
signup: user =>
axios.post("/api/users", { user }).then(res => res.data.user),
confirm: token =>
axios.post("/api/auth/confirmation", { token }).then(res => res.data.user)
}
};
This is what I am understanding from this code currently:
axios is imported so that various HTTP requests can be made. A user object is created that contains an array of 3 objects (functions?) : login, signup and confirm. In login object, an arrow function is used to pass credentials as the parameter into the axios object's post function. A promise is then called where the response data is passed into ??? which returns a res.data.user object.
I don't understand what the res.data.user object is, why is it described in such form with the dot separation instead of just something like responseDataObject?
This next piece of code is the 'action' part of the app (Redux) where the api.js is imported. Here 2 of the functions are exported into a new function which is called depending on the state of the user.... I don't think the other parts are relevant to talk about because I understand that programming logic. What I don't understand is how and why the res.data.object is returned like that. Where is the data part coming from? Is that arbitrarily described?
I am clearing missing something here, I've been watching some videos to try to understand what's going on but I think it might be a simple reason best described here.
There are a few terminologies you are using or understanding incorrectly. I think it is important to understand them before dive too deep into React.
First, Object:
An object has key/value pairs.
{
fruit: "apple"
}
that contains an array of 3 objects (functions?)
This would be incorrect. Since array's syntax is []. What you were describing is element or member or key.
Next part is about the promise in Javascript. You have this code. axios.post("/api/auth", { credentials }).then(res => res.data.user), This is basically saying once you call the api with that url, some data will come back(we stored in res). But the res is an object in this case.
If you console log res, you will get an object like this:
{
...
key1: "something",
key2: "something else",
data: {
...
user: { //some info about the user}
...
},
...
}
What you want is the data inside of res, more specifically you want the user's value. So to access it, you want to use res.data.user.
Hopefully, this answers some of your questions, but there is a lot to talk about. You should read more on js's object.
https://www.w3schools.com/js/js_objects.asp
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Welcome to JS world :)
A user object is created that contains an array of 3 objects
(functions?)
Yep it's just 3 simple functions
A promise is then called where the response data is passed into ???
which returns a res.data.user object.
Promise is resolved (then function called) when server response status 2** and inside then res is just simple object witch is come from server (status, headers, response body), here is docs describing what res contains: https://www.npmjs.com/package/axios#response-schema
So inside res.data - client assumed that server responds with user object which contains some user data (example server returns json {user: {id: 1, name: 'Hary'}})
I don't understand what the res.data.user object is, why is it
described in such form with the dot separation instead of just
something like responseDataObject?
You just accessing object properties (example res = {data: {user: {id: 1, name: 'Hary'}}})
So this code .then(res => res.data.user) just an simple helper to return user and not whole response from server, here how you can use it user.login({login: 'cool_user', password: 'qwerty'}).then(user => console.log(user.id)) so if I assumed that sever retunrs this data you got into console user id after success login
One thing not covered in the other posts is this syntax:
export default {
// ...
};
This exports an object from the api.js file. This object is not assigned to a variable or given a name here. Instead, you do that when you import:
import api from 'api.js';
Now in the file with this import, the name api refers to the object exported from api.js. So you can do things like api.user.login(credentials) to call one of the functions defined iniside this object.
I'm just starting to use flux (with redux for now) and am wondering how relationships are supposed to be handled.
For an example we can use Trello that has boards with columns that contains cards.
One approach would be to have one store/reducer for boards and have all the data in it there but that means some very fat stores since they would have to contain all the actions for columns and cards as well.
Another approach i've seen is separating nested resources into for example BoardStore, ColumnStore and CardStore and use their ids as reference.
Here's an example of where I am a bit confused: you could have an action creator called addCard that does a request to the server to create a card with all the data. If you are doing optimistic update, you would have created a card object in one of your store before but you can't know the id it will have until you get back the request.
So in short:
Firing addCard
addCard does a request, in the meantime you return an action of type ADD_CARD_TEMP
you get the request and return an action of type ADD_CARD where the store/reducer changes the id.
Is there a recommended way to deal with this case? Nested store/reducers look a bit silly to me but otherwise you end up with very complex stores so it looks like a compromise really.
Yes, using ids across multiple stores much like a relational database is the way to do it right.
In your example, let's say you want to optimistically put a new card in a particular column, and that a card can only be in one column (one column to many cards).
The cards in your CardStore might look like this:
_cards: {
'CARD_1': {
id: 'CARD_1',
columnID: 'COLUMN_3',
title: 'Go to sleep',
text: 'Be healthy and go to sleep on time.',
},
'CARD_2': {
id: 'CARD_2',
columnID: 'COLUMN_3',
title: 'Eat green vegetables',
text: 'They taste better with onions.',
},
}
Note that I can refer to a card by the id, and I can also retrieve the id within the object. This allows me to have methods like getCard(id) and also be able to retrieve the id of a particular card within the view layer. Thus I can have a method deleteCard(id) that is called in response to an action, because I know the id in the view.
Within the card store, you would have getCardsByColumn(columnID), which would be a simple map over the card objects, and this would produce an array of cards that you could use to render the contents of the column.
Regarding the mechanics of optimistic updates, and how the use of ids affects it:
You can use a client-side id that is established within the same closure that will handle the XHR response, and clear the client-side id when the response comes back as successful, or instead roll back on error. The closure allows you to hold on to the client-side id until the response comes back.
Many people will create a WebAPIUtils module that will contain all the methods related to the closure retaining the client-side id and the request/response. The action creator (or the store) can call this WebAPIUtils module to initiate the request.
So you have three actions:
initiate request
handle success
handle response
In response to the action that initiates the request, your store receives the client-side id and creates the record.
In response to success/error, your store again receives the client-side id and either modifies the record to be a confirmed record with a real id, or instead rolls back the record. You would also want to create a good UX around that error, like letting your user try again.
Example code:
// Within MyAppActions
cardAdded: function(columnID, title, text) {
var clientID = this.createUUID();
MyDispatcher.dispatch({
type: MyAppActions.types.CARD_ADDED,
id: clientID,
columnID: columnID,
title: title,
text: text,
});
WebAPIUtils.getRequestFunction(clientID, "http://example.com", {
columnID: columnID,
title: title,
text: text,
})();
},
// Within WebAPIUtils
getRequestFunction: function(clientID, uri, data) {
var xhrOptions = {
uri: uri,
data: data,
success: function(response) {
MyAppActions.requestSucceeded(clientID, response);
},
error: function(error) {
MyAppActions.requestErrored(clientID, error);
},
};
return function() {
post(xhrOptions);
};
},
// Within CardStore
switch (action.type) {
case MyAppActions.types.CARD_ADDED:
this._cards[action.id] = {
id: action.id,
title: action.title,
text: action.text,
columnID: action.columnID,
});
this._emitChange();
break;
case MyAppActions.types.REQUEST_SUCCEEDED:
var tempCard = this._cards[action.clientID];
this._cards[action.id] = {
id: action.id,
columnID: tempCard.columnID,
title: tempCard.title,
text: tempCard.text,
});
delete this._cards[action.clientID];
break;
case MyAppActions.types.REQUEST_ERRORED:
// ...
}
Please don't get too caught up on the details of the names and the specifics of this implementation (there are probably typos or other errors). This is just example code to explain the pattern.