How/where to convert dates from the server in a redux app? - javascript

I'm starting to study redux now. I used the real-word example as a starting point, using normalizr and reselect to handle data.
Now, I need to understand where is the best place to convert dates coming from the server into js Date objects. Since normalizr already takes care of "some Schemas" I thought it could do that too, but I did not find it there.
Where should I convert these dates? My assumption is that I have to keep those dates already converted on the store. Is that right?

Normalizr can do this - in the current version (v3.3.0) you can achieve it like this:
import { schema } from 'normalizr';
const ReleaseSchema = new schema.Entity('releases', {}, {
processStrategy: (obj, parent, key) => {
return {
...obj,
createdAt: new Date(obj.createdAt),
updatedAt: new Date(obj.updatedAt),
};
},
});

I added a third parameter to callApi (inside api middleware):
function callApi(endpoint, schema, conversionFromServer) {
const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint
return fetch(fullUrl)
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
return Promise.reject(json)
}
const camelizedJson = camelizeKeys(json)
const nextPageUrl = getNextPageUrl(response)
let convJson = camelizedJson;
if(conversionFromServer) {
convJson = conversionFromServer(convJson);
}
return Object.assign({},
normalize(convJson, schema),
{ nextPageUrl }
)
})
}
and now I can call it like this:
return {
[CALL_API]: {
types: [ TIMESLOTS_REQUEST, TIMESLOTS_SUCCESS, TIMESLOTS_FAILURE ],
endpoint: `conference/${conferenceId}/timeslots`,
schema: Schemas.TIMESLOT_ARRAY,
conversionFromServer: (data) => {
data.timeslots.forEach((t)=>{
t.startTime=php2js.date(t.startTime)
t.endTime=php2js.date(t.endTime)
});
return data;
}
}
}
This way I keep this conversion "as close to the server" as I can.

Related

React SWR - how to know that updating (mutating) is running?

Im mostly using SWR to get data, however I have a situation that I need to update data. The problem is, I need an indicator that this request is ongoing, something like isLoading flag. In the docs there's a suggestion to use
const isLoading = !data && !error;
But of course when updating (mutating) the data still exists so this flag is always false. The same with isValidating flag:
const { isValidating } = useSWR(...);
This flag does NOT change when mutation is ongoing but only when its done and GET request has started.
Question
Is there a way to know if my PUT is loading? Note: I dont want to use any fields in state because it won't be shared just like SWR data is. Maybe Im doing something wrong with my SWR code?
const fetcher = (url, payload) => axios.post(url, payload).then((res) => res);
// ^^^^^ its POST but it only fetches data
const updater = (url, payload) => axios.put(url, payload).then((res) => res);
// ^^^^^ this one UPDATES the data
const useHook = () => {
const { data, error, mutate, isValidating } = useSWR([getURL, payload], fetcher);
const { mutate: update } = useSWRConfig();
const updateData = () => {
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
return {
data,
updateData,
isValidating, // true only when fetching data
isLoading: !data && !error, // true only when fetching data
}
Edit: for any other who reading this and facing the same issue... didnt find any solution for it so switched to react-query. Bye SWR
const { mutate: update } = useSWRConfig();
const updateData = () => {
// this will return promise
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
By using react-toastify npm module to show the user status.
// first wrap your app with: import { ToastContainer } from "react-toastify";
import { toast } from "react-toastify";
const promise=update(getURL, updater(putURL, payload))
await toast.promise(promise, {
pending: "Mutating data",
success: "muttation is successfull",
error: "Mutation failed",
});
const markSourceMiddleware = (useSWRNext) => (key, fetcher, config) => {
const nextFetcher = (...params) =>
fetcher(...params).then((response) => ({
source: "query",
response,
}));
const swr = useSWRNext(key, nextFetcher, config);
return swr;
};
const useHook = () => {
const {
data: { source, response },
mutate,
} = useSWR(key, fetcher, { use: [markSourceMiddleware] });
const update = mutate(
updateRequest().then((res) => ({
source: "update",
response,
})),
{
optimisticData: {
source: "update",
response,
},
}
);
return {
update,
updating: source === "update",
};
};
Hmm based on that:
https://swr.vercel.app/docs/conditional-fetching
It should work that the "is loading" state is when your updater is evaluates to "falsy" value.
REMAINDER! I don't know react swr just looked into docs - to much time at the end of the weekend :D
At least I hope I'll start discussion :D

Get specific value from array of objects [{"key":"value"}, {"key":"value"}]

I'm trying to interact with the API of processmaker.
I have made a simple form to authenticate and get the authorization token, which is needed to interact with the rest of the API.
I am able to use the token to output a json response of created projects after login. The response is an array of objects.
I need to get the prj_uid for an api request so I want to extract these, but I've had little luck using map.
How can I iterate over the response and get prj_name and prj_uid for each of the objects in the array?
import React, { useState, useEffect } from "react";
//import ResponsiveEmbed from "react-responsive-embed";
const Tasks = ({ loggedIn }) => {
const [hasError, setErrors] = useState(false);
const [projects, setProjects] = useState([]);
const url = "http://127.0.0.1:8080/api/1.0/workflow/project";
useEffect(() => {
let access_token = sessionStorage.getItem("access_token");
async function fetchData() {
const response = await fetch(url, {
method: "GET",
withCredentials: true,
timeout: 1000,
headers: {
Authorization: "Bearer " + access_token
}
});
response
.json()
.then(response => setProjects(response))
.catch(err => setErrors(err));
}
fetchData();
}, [loggedIn]);
console.log(JSON.stringify(loggedIn) + " logged in, displaying projects");
console.log(projects + " projects");
if (!loggedIn) {
return <h1>Error</h1>;
} else {
return (
<>
<p>Login success!</p>
<h2>Projects:</h2>
<span>{JSON.stringify(projects)}</span>
<div>Has error: {JSON.stringify(hasError)}</div>
</>
);
}
};
export default Tasks;
Stringified Response:
[
{
"prj_uid":"1755373775d5279d1a10f40013775485",
"prj_name":"BPMN Process",
"prj_description":"This is a processmaker BPMN Project",
"prj_category":"8084532045d5161470c0de9018488984",
"prj_type":"bpmn",
"prj_create_date":"2019-08-13 08:50:25",
"prj_update_date":"2019-08-13 09:04:16",
"prj_status":"ACTIVE"
},
{
"prj_uid":"7459038845d529f685d84d5067570882",
"prj_name":"Purchase Request",
"prj_description":"",
"prj_category":"2284311685392d2e70f52e7010691725",
"prj_type":"bpmn",
"prj_create_date":"2019-08-13 11:30:48",
"prj_update_date":"2019-08-13 12:20:05",
"prj_status":"ACTIVE"
}
]
Array.map() is your answer- you had it right.
its as simple as:
let mappedObject = result.map( el => ({ prj_name, prj_uid }) );
el is every element in the array, and we construct the new array with an object containing only prj_name and prj_uid. Because el alraeady has those properties with those names, we do not need to write { prj_name: el.prj_name } when we construct the new object, it is implied and will do the trick with only the property names there.
mappedObject will now hold an array of objects consists only of the asked properties.
You might wanna read more about map to understand it better- Array.map()
If loggedIn is the json object, then you can do this:
const uidNameArr = loggedIn.map((item) => { // returns an array of arrays with the values you want.
return [item.prj_uid, item.prj_name]
})
uidNameArr.forEach(([uid,name]) => {
console.log(`${name} has a uid of ${uid}`)
})

json and data returned from api's

I'm new to coding and just paying around, but i have what I think is really simple problem, but just don't have the necessary vocabulary to express it properly. Here is my code, its for currency conversion.
app.get('/currency', (request, response) => {
const from_currency = request.query.from;
const to_currency = request.query.to;
const amount = request.query.amount;
const exchange = `${from_currency}_${to_currency}`
//https://free.currencyconverterapi.com/api/v5/convert?q=USD_PHP&compact=y
const currencyUrl = `https://free.currencyconverterapi.com/api/v5/convert?q=${from_currency}_${to_currency}&compact=y`;
const requestOptions = {
uri: currencyUrl,
json: true
};
requestPromise(requestOptions)
.then((data) => {
//const responseData = getCurrentCurrencyJSON(data);
response.json(data); //responseData
})
.catch((error) => {
console.log(error.message);
response.json({
messages: [{
text: 'Sorry there was an error!'
}]
});
});
});
This returns, from the API, {"USD_GBP":{"val":0.73897}}, which is fine and I can return the value if i write response.json(data.USD_GBP.val); however I want to be able to vary the currencies im converting to and from, so how do I make the USD_GBP in the response.json dynamic?
thanks if anyone can help.
If the api is always going to return an object with than single attribute, you can do something like this:
res = {"USD_GBP":{"val":0.73897}};
res[Object.keys(res)[0]].val
Not the nicest, but it works.

Create a nested return model with Knex.js

I'm using Knex.js to query a MySQL database in a Hapi.js route. The following code works but requires a nested query:
{
path: '/recipes',
method: 'GET',
handler: (req, res) => {
const getOperation = Knex.from('recipes')
// .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
.select()
.orderBy('rating', 'desc')
.limit(10)
.then((recipes) => {
if (!recipes || recipes.length === 0) {
res({
error: true,
errMessage: 'no recipes found'
});
}
const recipeGuids = recipes.map(recipe => recipe.guid);
recipes.forEach(r => r.ingredients = []);
const getOperation2 = Knex.from('ingredients')
.whereIn('recipe', recipeGuids)
.select()
.then((ingredients) => {
recipes.forEach(r => {
ingredients.forEach(i => {
if (i.recipe === r.guid) {
r.ingredients.push(i);
}
});
});
res({
count: recipes.length,
data: recipes
});
});
});
}
}
Is there a way to create a return model with Knex.js that has nested objects that match the parent's id/guid so that I don't have nested promises?
Short answer: No.
With Knex, you can retrieve data the same as with SQL, which is record based, not object based, so the closest that you could come would be to use a join to allow doing just a single select to retrieve a single array having elements: recipes, guids, ingredients. This would repeat the recipe & guid for each ingredient, which you avoid by using nested objects. (See the answer below by #Fazal for an example of this.)
As another alternative, you could store the ingredients as a 'blob' field in the recipe table, but I don't believe that MySQL would allow you to create an Array field, so when retrieving the data, you would have to do a transform of the field into the array. And transform it from the Array before updating it into the table. Like: storableData = JSON.stringify(arrayData) and arrayData = JSON.parse(storableData)
There are a few other things that I would suggest to help you improve the code though. (Yeah, I know, not really the question here):
Separate the routing functionality from data handling.
Also, separate data manipulation functionality from retrieval.
Use throw & .catch for creating and handling unsuccessful responses.
The separation of routing, data retrieval, data manipulation makes testing, debugging, and future comprehension easier as each function has a more atomic purpose.
Throwing/.catching unsuccessful process conditions makes it much simpler to have more comprehensive error processing by allowing you to put (most of the time) a single .catch in your router response handling (Hapi.js may even do this .catch for you???).
Also, see the other .catch and .on('query-error' that I added for logging errors. You may have a different logging mechanism you want to use rather than the console. I use Winston. And note that .on('query-error' is NOT a .catch. There will still be an Error() that is thrown, and must be handled somewhere, this will just give you good info about the failure close to the source.
(Sorry, the below code is untested)
path: '/recipes',
method: 'GET',
handler: (req, res) => {
return getRecipeNIngredients()
.then((recipes) => {
res({
count: recipes.length,
data: recipes
});
})
.catch((ex) => {
res({
error: true,
errMessage: ex.message
});
});
};
function getRecipeNIngredients() {
let recipes = null;
return getRecipes()
.then((recipeList) => {
recipes = recipeList;
const recipeGuids = recipes.map(recipe => recipe.guid);
recipes.forEach(r => r.ingredients = []);
return getIngredients(recipeGuids);
})
.then((ingredients) => {
recipes.forEach(r => {
ingredients.forEach(i => {
if (i.recipe === r.guid) {
r.ingredients.push(i);
}
});
});
return recipes;
})
.catch((ex) => {
console.log(".getRecipeNIngredients ERROR ex:",ex); // log and rethrow error.
throw ex;
});
};
function getRecipes() {
return Knex.from('recipes')
// .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
.select()
.orderBy('rating', 'desc')
.limit(10)
.on('query-error', function(ex, obj) {
console.log("KNEX getRecipes query-error ex:", ex, "obj:", obj);
})
.then((recipes) => {
if (!recipes || recipes.length === 0) {
throw new Error('no recipes found')
}
})
};
function getIngredients(recipeGuids) {
return Knex.from('ingredients')
.whereIn('recipe', recipeGuids)
.select()
.on('query-error', function(ex, obj) {
console.log("KNEX getIngredients query-error ex:", ex, "obj:", obj);
})
};
I hope this is Useful!
Gary.
I created a library that return nested object even it has types for typescript
Nested Knex
import * as n from 'nested-knex';
n.array(
n.type({
id: n.number("recipe.id", { id: true }),
title: n.string("recipe.title"),
ingredients: n.array(
n.type({
id: n.number("ingredients.id", { id: true }),
title: n.string("ingredients.title")
})
)
})
)
.withQuery(
knex
.from("recipes")
.innerJoin("ingredients", "recipes.guid", "ingredients.recipe")
.select()
.orderBy("rating", "desc")
.limit(10)
)
.then(recipes => {});
so recipes even have types
You can easily avoid nest query. Just use subquery as-
knex.select('*')
.from(function () {
this.select('*').from('recipes').limit(10).as('recipes'); // limit here
})
.leftJoin('ingredients', 'ingredients.recipe_id', 'recipes.guid')
.then((rec) => {
console.log(rec);
})
see.. just few lines of code.

Express.js response converts snake_case keys to camelCase automatically

I'm working on a small project at work and we have an Express.js based node application running that sends a json response that has keys in snake_case format. We have another node application that consumes this service but the response object keys are accessed in camelCase format here. I'd like to know what happens in the background to make this work.
This is the code in the REST API
app.get('/api/customer/:id', (req, res) => {
const data = {
"arr": [{
"my_key": "609968029"
}]
}
res.send(data);
});
This is how it is consumed in the other node application
getData = (id) => {
const options = {
url: `api/customer/${id}`
};
return httpClient.get(options)
.then(data => {
const arr = data.arr.map(arrEntry => {
return {
myKey: arrEntry.myKey
};
});
return {
arr
};
});
};
Here myKey correctly has the data from the REST API but I'm not sure how my_key is converted to myKey for it work.
Turns out we have used humps library to resolve the response object from keys snake-case to camelCase.
I found this code in the lib call
const humps = require('humps');
...
axios(optionsObj)
.then(response => {
resolve(humps.camelizeKeys(response.data));
})
.catch(err => {
reject(err);
});
lodash can do this
_.camelCase('Foo Bar');
// => 'fooBar'

Categories