Thunk + Redux (in React Native) : Can't get action to work - redux-thunk

I'm brand new to this stack. I've seen quite a few other questions about this and have read the Thunk documentation but I can't stitch this together.
When I run the code below I get the error "Actions must be plain objects, use custom middleware for async actions" which is exactly the problem I'm trying to solve with Thunk.
My action looks like this:
src/actions/recipes.js
// this calls the API
function fetchApiGetRecipes() {
return fetch('https://mywebsite.com/endpoint/', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + idToken
}
}).then((json) => {
dispatch({
type: 'RECIPES_REPLACE',
data: json
})
});
}
// this is passed into my container to use to refresh the recipe list
export function getRecipes() {
if (Firebase === null) return () => new Promise(resolve => resolve());
if (Firebase.auth().currentUser !== null) {
Firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// console.log(idToken);
return dispatch => new Promise(resolve => fetchApiGetRecipes(idToken) )
}).catch(function(error) {
// Handle error
});
} else {
console.log("Null user");
}
}
What is the correct syntax to use Thunk here and fix the error I'm getting when the app starts up?
EDIT: I create the store like this:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage'; // default: localStorage if web, AsyncStorage if react-native
import thunk from 'redux-thunk';
import reducers from '../reducers';
// Redux Persist config
const config = {
key: 'root',
storage,
blacklist: ['status'],
};
const reducer = persistCombineReducers(config, reducers);
const middleware = [thunk];
const configureStore = () => {
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(applyMiddleware(...middleware)),
);
const persistor = persistStore(
store,
null,
() => { store.getState(); },
);
return { persistor, store };
};
export default configureStore;

Your getRecipes function doesn't return a function in the if (Firebase.auth().currentUser !== null) clause.
You need to return a function where you are just doing
Firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// console.log(idToken);
return dispatch => new Promise(resolve => fetchApiGetRecipes(idToken) )
}).catch(function(error) {
// Handle error
});
The dispatch function (I assume is the intended one to return) is being returned in the then clause of the promise. That doesn't return the dispatch function to the outer method getRecipies. Hence the error

probably you forgot pass dispatch to func args?
// you use dispatch in this func
function fetchApiGetRecipes() {...}
// you forget pass
return dispatch => new Promise(resolve => fetchApiGetRecipes(idToken) )

Related

Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render

import { setCookies, removeCookies } from "cookies-next";
import { useRouter } from "next/router";
import { useEffect } from "react";
const { URL } = process.env;
export const getServerSideProps = async (context) => {
const userAuthToken = context.req.cookies["authToken"];
const data = {
authToken: userAuthToken,
};
const requestJSON = JSON.stringify(data);
const response = await fetch(URL + "api/userFetch", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: requestJSON,
});
const responseData = await response.json();
return {
props: { datas: responseData },
};
};
const Home = ({ datas }) => {
const router = useRouter();
if (datas[0].error == true) {
useEffect(() => {
setTimeout(() => {
router.push("/");
}, 3000);
}, []);
removeCookies("authToken");
return <h1>Something Went Wrong</h1>;
} else {
return <h1>Welcome To Home{datas[0].error}</h1>;
}
};
export default Home;
This code is running fine on development server but when I try to build this code in production I get this error **
./pages/Home.js
28:5 Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?**
I tried everything I could but can't fix the error
Just do exactly what the error is telling you. Move the useEffect call out of the conditional block. You can still conditionally perform the operation within the hook. For example:
const Home = ({ datas }) => {
const router = useRouter();
useEffect(() => {
if (datas[0].error == true) {
setTimeout(() => {
router.push("/");
}, 3000);
}
}, []);
if (datas[0].error == true) {
removeCookies("authToken");
return <h1>Something Went Wrong</h1>;
} else {
return <h1>Welcome To Home{datas[0].error}</h1>;
}
};
Specifically, as the error states, the same hooks must always be called on every render. (I don't know enough under the hood of React to describe why that's the case, it just seems necessary for stability/consistency/etc.) But the operation being performed by the hook in this case can still be effectively a no-op if the intended condition is not met.

Execute function from object property

i have a little problem with my code.
My expectation is, when i get the object reqHeaders to the function getQueryData
i want that the property UUID will execute the function createToken().
currently, when i running the program, it happens only at the init( first time ).
const createToken = () => {
// some logics...
return `${token}`;
};
const reqHeaders = {
UUID: **createToken()**,
"Content-Type": "application/json;charset=UTF-8"
};
const getQueryData = query => {
return axiosInstance
.post("/someAddress", { selectQuery: query }, { headers: **reqHeaders** })
.then(response => {
// some logs...
return response.data;
})
.catch(error => {
//some logs....
return error;
});
}
};
module.exports = getQueryData;
thank you,
Raz.
The solution is to move the object creation inside the body of the function.
const createToken = () => {
// some logics...
return `${token}`;
};
const getQueryData = query => {
return axiosInstance
.post("/someAddress", { selectQuery: query }, { headers: {
UUID: **createToken()**,
"Content-Type": "application/json;charset=UTF-8"
} })
.then(response => {
// some logs...
return response.data;
})
.catch(error => {
//some logs....
return error;
});
}
};
module.exports = getQueryData;
Now each time you run the function new req headers will be created and hence the UUID. What you did previously created an object once and then use the same object for each function call.

TestCafe Triggering Test By POST Request In Express

I had a question that doesn't seem to be answered anywhere.
I am running tests from within my Express.js api. I set up a page that has a button and a field to enter a keyword intended to be used during a testcafe test. My endpoint I set up is /testcafe. But after sending a post request to /testcafe, there is a long delay while test runs and so my question is what is the best next step besides hanging?
Also, can my post request body, which contains the keyword, be directly used in a test like this? Keep in mind it's this pattern:
frontend -> POST request -> Express server -> /testcafe endpoint - test
My problem is after it reaches test, I currently have it attempting to call fetch from within the request logger. Is this right?
import { ClientFunction, Selector } from 'testcafe';
import { RequestLogger, RequestHook } from 'testcafe';
import zlib from 'zlib';
import fetch from 'isomorphic-unfetch';
const url = 'https://www.mysitetesturl.com/page';
class MyRequestHook extends RequestHook {
constructor (requestFilterRules, responseEventConfigureOpts) {
super(requestFilterRules, responseEventConfigureOpts);
}
onRequest (e) {
console.log('in onRequest!')
console.log('========================')
console.log('Request Body')
let buf = e._requestContext.reqBody
console.log(buf.toLocaleString())
}
onResponse (e) {
let buf = Buffer(e.body)
let unzippedBody = Buffer(zlib.gunzipSync(buf))
let payload = unzippedBody.toLocaleString()
fetch('http://myapiipaddress/api/testcafe',
method: 'PUT',
body: JSON.stringify(payload)
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
console.log(doc)
}
})
}
}
const myRequestHook = new MyRequestHook({
url: url,
method:'get'},
{
includeHeaders: true,
includeBody: true
}
);
fetch('http://myapiipaddress/api/testcafe',
method: 'GET'
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
fixture`myfixture`
.page(doc.url)
.requestHooks(myRequestHook);
test(`mytest`, async t => {
const inputField = Selector('input');
await t
await t
.wait(5000)
.typeText(inputField, doc.text)
.wait(5000)
}
);
}
})
According to your scheme, you need to organize your code in a different way:
const createTestCafe = require('testcafe');
....
// Choose the necessary body parser for express application
// https://github.com/expressjs/body-parser
app.use(bodyParser.urlencoded({ extended: true }));
...
app.post('/', function (req, res) {
createTestCafe('localhost', 1337, 1338, void 0, true)
.then(testcafe => {
const runner = testcafe.createRunner();
return runner
.src('/tests')
.browsers('chrome')
.run();
})
.then(failedCount => {
testcafe.close();
res.end();
});
})

calling ramda compose in nodejs class

I have the following method skipLoggingThisRequest in a node js class which I am trying to test. The method is supposed to return either true or false, based on the path in request, using ramda compose to get to that value. However in my tests, no matter what path I set in the request object, my skipLoggingThisRequest always returns true.
What am I missing here?
my class:
import { compose, filter, join, toPairs, map, prop, flip, contains, test, append } from 'ramda'
import { create, env } from 'sanctuary'
import { isEmpty, flattenDeep } from 'lodash'
import chalk from 'chalk'
import log from 'menna'
class MyClass {
constructor (headerList) {
this.headerWhiteList = flattenDeep(append(headerList, []));
}
static getBody (req) {
return (!isEmpty(req.body) ? JSON.stringify(req.body) : '');
}
static S () {
return create({ checkTypes: false, env });
}
static isInList () {
return flip(contains);
}
static isInWhitelist () {
return compose(this.isInList(this.headerWhiteList), this.S.maybeToNullable, this.S.head);
}
static parseHeaders () {
return (req) => compose(join(','), map(join(':')), filter(this.isInWhitelist), toPairs, prop('headers'));
}
skipLoggingThisRequest () {
return (req) => compose(test(/^.*(swagger|docs|health).*$/), prop('path'))
}
logger (req, res, next) {
if (this.skipLoggingThisRequest(req)) {
console.log('Skipping')
return next();
}
const primaryText = chalk.inverse(`${req.ip} ${req.method} ${req.originalUrl}`);
const secondaryText = chalk.gray(`${this.parseHeaders(req)} ${this.getBody(req)}`);
log.info(`${primaryText} ${secondaryText}`);
return next();
}
}
export default MyClass
My tests:
import sinon from 'sinon';
import MyClass from '../lib/MyClass';
describe('MyClass', () => {
const headerList = ['request-header-1', 'request-header-2'];
const request = {
'headers': {
'request-header-1': 'yabadaba',
'request-header-2': 'dooooooo'
},
'ip': 'shalalam',
'method': 'GET',
'originalUrl': 'http://myOriginalUrl.com',
'body': ''
};
const response = {};
const nextStub = sinon.stub();
describe('Logs request', () => {
const myInstance = new MyClass(headerList);
const skipLogSpy = sinon.spy(myInstance, 'skipLoggingThisRequest');
request.path = '/my/special/path';
myInstance.logger(request, response, nextStub);
sinon.assert.called(nextStub);
});
});
this.skipLoggingThisRequest(req) returns a function ((req) => compose(test(/^.*(swagger|docs|health).*$/), prop('path'))).
It does not return a boolean. However, since functions are truthy, your if statement always executes.
What you most likely want to do is this.skipLoggingThisRequest()(req). You get the function and then apply a request to it.
Demonstration of what's going on:
const testFunction = () => (test) => test === "Hello!";
console.log(testFunction);
console.log(testFunction());
console.log(testFunction()("Hello!"));
console.log(testFunction()("Goodbye!"));
if (testFunction) {
console.log("testFunction is truthy.");
}
if (testFunction()) {
console.log("testFunction() is truthy.");
}
if (testFunction()("Hello!")) {
console.log('testFunction()("Hello!") is truthy.');
}
if (!testFunction()("Goodbye!")) {
console.log('testFunction()("Goodbye!") is falsey.');
}

Call server-side function from ReactJS component

I'm trying to implement a payments system in my ReactJS app that requires server-side code.
I have several questions:
How do you connect a ReactJS app so it can communicate with server-side code?
How would you set up a function in the server-side code?
How would you call that function from a component in a ReactJS app?
For reference, I'm trying to integrate Stripe subscriptions. They give server-side code examples for Node, PHP, etc.
FYI: I am not trying to set up server-side rendering. When you search for server-side code in reference to ReactJS, that's just about all that comes up.
EDIT: I'm particularly interested in a NodeJS solution. I'm also using Webpack.
Just in case, it is helpful to you... I have a React UI that triggers video processing on a Django backend (I mainly use GraphQL through Apollo Client to trigger my server side functions and REST framework when file transfers are involved).
Is REST an option for you?
The middleware I use for file transfers for example:
const SERVER_URL = process.env.SERVER_URL;
const fileTransferApi = (payload) => {
const { authenticated, token, endpoint, body, contentType, method } = payload;
let config = {};
if (authenticated) {
if (token) {
config = {
method,
headers: {
'Content-Type': contentType,
Authorization: `Bearer ${token}`
},
body
};
} else {
throw new Error('No token saved!');
}
}
return fetch(`${SERVER_URL}/api/rest/v1/${endpoint}`, config)
.then((response) =>
response.text().then((text) => ({ text, response }))
).then(({ text, response }) => {
if (!response.ok) {
return Promise.reject(text);
}
return text;
}).catch((err) => console.log(err));
};
export const FILE_TRANSFER_API = Symbol('FILE_TRANSFER_API');
export default () => (next) => (action) => {
const fileTransferApiAction = action[FILE_TRANSFER_API];
if (typeof fileTransferApiAction === 'undefined') {
return next(action);
}
const { payload, types } = fileTransferApiAction;
const [, successType, errorType] = types;
return fileTransferApi(payload).then(
(response) =>
next({
type: successType,
payload: {
text: response,
message: 'ok'
}
}),
(error) => next({
type: errorType,
payload: {
error: error.message || 'There was an error.'
}
})
);
};
My store (I use Redux):
import { createStore, compose, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import ReduxThunk from 'redux-thunk';
import ApolloClientSingleton from '../network/apollo-client-singleton';
import fileTransferApi from '../middlewares/fileTransferApi';
import reducer from './reducers';
export default class Store {
constructor(history, initialState = {}) {
this.data = createStore(
reducer,
initialState,
compose(
applyMiddleware(
fileTransferApi,
ReduxThunk.withExtraArgument(ApolloClientSingleton),
routerMiddleware(history),
ApolloClientSingleton.middleware()
),
typeof window === 'object' && typeof window.devToolsExtension !== 'undefined'
? window.devToolsExtension() : (f) => f
)
);
}
}
In my actions:
export const windowsDownload = (authenticated, token) => ({
[FILE_TRANSFER_API]: {
types: [WINDOW_DOWNLOAD_REQUEST, WINDOW_DOWNLOAD_SUCCESS, WINDOW_DOWNLOAD_FAILURE],
payload: {
endpoint: 'file_transfer/download/windows',
contentType: 'text/csv',
method: 'get',
body: null,
authenticated,
token
}
}
});
This REST setup enables me to send requests (POST video, GET csv...) from my React UI to my Django server. Can't you set up some REST calls between your app and your server?

Resources