I wrote a hook that calls apollo useQuery. It's pretty simple:
useDecider:
import { useState } from 'react';
import { useQuery, gql } from '#apollo/client';
export const GET_DECIDER = gql`
query GetDecider($name: [String]!) {
deciders(names: $name) {
decision
name
value
}
}
`;
export const useDecider = name => {
const [enabled, setEnabled] = useState(false);
useQuery(GET_DECIDER, {
variables: {
name
},
onCompleted: data => {
const decision = data?.deciders[0]?.decision;
setEnabled(decision);
},
onError: error => {
return error;
}
});
return {
enabled
};
};
I'm trying to test it now and the MockedProvider is not returning the expected data:
import React from 'react';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import { MockedProvider } from '#apollo/client/testing';
import { useDecider, GET_DECIDER } from './useDecider';
const getMock = (value = false, decider = '') => [
{
request: {
query: GET_DECIDER,
variables: {
name: decider
}
},
result: () => {
console.log('APOLLO RESULT');
return {
data: {
deciders: [
{
decision: value,
name: decider,
value: 10
}
]
}
};
}
}
];
const FakeComponent = ({ decider }) => {
const { enabled } = useDecider(decider);
return <div>{enabled ? 'isEnabled' : 'isDisabled'}</div>;
};
const WrappedComponent = ({ decider, value }) => (
<MockedProvider mocks={getMock(value, decider)} addTypename={false}>
<FakeComponent decider={decider} />
</MockedProvider>
);
describe('useDecider', () => {
it('when decider returns true', () => {
// should return true
render(<WrappedComponent decider="fake_decider" value={true} />);
screen.debug();
const result = screen.getByText('isEnabled');
expect(result).toBeInTheDocument();
});
});
I simplified your hook implementation and put together a working example:
import { useQuery, gql } from "#apollo/client";
export const GET_DECIDER = gql`
query GetDecider($name: [String]!) {
deciders(names: $name) {
decision
name
value
}
}
`;
export const useDecider = (name) => {
const { data } = useQuery(GET_DECIDER, { variables: { name } });
return { enabled: data?.deciders[0]?.decision || false };
};
Note that in the test I also updated your getBy to an await findBy:
describe("useDecider", () => {
it("when decider returns true", async () => {
// should return true
render(<WrappedComponent decider="fake_decider" value={true} />);
screen.debug();
const result = await screen.findByText("isEnabled");
expect(result).toBeInTheDocument();
});
});
This is because you need to wait for your API call to complete before the data will be on the page, hence you would not expect the data to be there on the first render.
From https://www.apollographql.com/docs/react/development-testing/testing/#testing-the-success-state
To test how your component is rendered after its query completes, you
can await a zero-millisecond timeout before performing your checks.
This delays the checks until the next "tick" of the event loop, which
gives MockedProvider an opportunity to populate the mocked result
try adding before your expect call
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
Related
I have a situation where there were created 4 custom hooks all very similar to each other.
Those use similar queries and mutations. The scope of all 4 is to manage an API called StudyConfiguration. I need to make it merged in one custom hook and in an efficient way.
The 4 custom hooks are called
useConfiguration,
useSetConfiguration,
useStudyConfigurationOverride,
useUnSetConfiguration
I tried to emerge as follows and below it, you will see the 4 custom hooks along with details of their usage. The goal is to have all 4 in one in the right way.
This is the new custom hook I did by merging the 4 in one but I'm not convinced that could be the right way
import { gql, useMutation, useQuery } from '#apollo/client';
import { useCallback } from 'react';
const GET = gql`
query WEB_useConfiguration($name: String!, $scope: StudyConfigurationScope) {
studyConfiguration(name: $name, filter: $scope) {
name
value
configurationOverrideChain {
value
scope
}
}
}
`;
const GET_OVERRIDE = gql`
query WEB_useConfigurationOverride($name: String!, $scope: StudyConfigurationScope) {
studyConfiguration(name: $name, filter: $scope) {
configurationOverrideChain {
value
scope
}
}
}
`;
const SET = gql`
mutation WEB_setConfiguration($input: setStudyConfiguration!) {
setStudyConfiguration(input: $input) {
name
value
}
}
`;
const UNSET = gql`
mutation WEB_unSetConfiguration($input: unsetStudyConfiguration!) {
unsetStudyConfiguration(input: $input) {
name
value
}
}
`;
const useConfiguration = ({ name, scope, defaultValue = null }) => {
const { data } = useQuery(GET, {
variables: { name, scope },
fetchPolicy: 'network-only',
});
const value = data?.studyConfiguration?.value;
if (!value) {
return defaultValue;
}
try {
return JSON.parse(value);
} catch {
return value;
}
};
const useStudyConfigurationOverride = ({ name, scope }) => {
const { data } = useQuery(GET_OVERRIDE, {
variables: { name, scope },
fetchPolicy: 'network-only',
});
const value = data?.studyConfiguration?.configurationOverrideChain;
if(!value) return []
return value;
};
const useSetConfiguration = input => {
const [setStudyConfiguration] = useMutation(SET);
const executeSetConfiguration = useCallback(() => {
return setStudyConfiguration({ variables: { input: { ...input } } });
}, [input, setStudyConfiguration]);
return [executeSetConfiguration];
};
const useUnSetConfiguration = input => {
const [unSetStudyConfiguration] = useMutation(UNSET);
const executeUnSetConfiguration = useCallback(() => {
return unSetStudyConfiguration({ variables: { input: { ...input } } });
}, [input, unSetStudyConfiguration]);
return [executeUnSetConfiguration];
};
export { useConfiguration, useSetConfiguration, useUnSetConfiguration, useStudyConfigurationOverride };
Now the single hooks with an example of how they are used
useConfiguration
This custom hook is for getting the Study configuration from the API and returns a value. The reason we have a try/catch is that depending on what config is requested the value can be a string or a boolean. So if the parse fails we return the value directly.
import { gql, useQuery } from '#apollo/client';
const QUERY = gql`
query WEB_useConfiguration($name: String!, $scope: StudyConfigurationScope) {
studyConfiguration(name: $name, filter: $scope) {
name
value
}
}
`;
export const useConfiguration = ({ name, scope, defaultValue = null }) => {
const { data } = useQuery(QUERY, {
variables: { name, scope },
fetchPolicy: 'network-only',
});
const value = data?.studyConfiguration?.value;
if (!value) {
return defaultValue;
}
try {
return JSON.parse(value);
} catch {
return value;
}
};
The above is used as an example
const emailSender = useConfiguration({
name: 'messaging.email.sender.address',
scope: { studyId },
});
In this case, we get back a string like value = email#email.com
useStudyConfigurationOverride
This custom hook is actually almost the same as the above one but we getting the second part of the result of the same query. It is giving back configurationOverrideChain which is an array of tracked changes for the configuration
import { gql, useQuery } from '#apollo/client';
const QUERY = gql`
query WEB_useConfigurationOverride($name: String!, $scope: StudyConfigurationScope) {
studyConfiguration(name: $name, filter: $scope) {
name
configurationOverrideChain {
value
scope
}
}
}
`;
export const useStudyConfigurationOverride = ({ name, scope }) => {
const { data } = useQuery(QUERY, {
variables: { name, scope },
fetchPolicy: 'network-only',
});
const value = data?.studyConfiguration?.configurationOverrideChain;
if(!value) return []
return value;
};
An example of usage
const studyOverrides = useStudyConfigurationOverride({
name: 'messaging.email.sender.address',
scope: { studyId },
});
results in an array of the overrides as
[
{
"value": "global#test.com",
"scope": "GLOBAL"
},
{
"value": "noreply#test.com",
"scope": "STUDY"
},
{
"value": "hello#app.trialbee.com",
"scope": "DEFAULT"
}
]
useSetConfiguration
This custom hooks set a new configuration
import { gql, useMutation } from '#apollo/client';
import { useCallback } from 'react';
const SET_STUDY_CONFIGURATION = gql`
mutation WEB_setConfiguration($input: setStudyConfiguration!) {
setStudyConfiguration(input: $input) {
name
value
}
}
`;
export const useSetConfiguration = input => {
const [setStudyConfiguration] = useMutation(SET_STUDY_CONFIGURATION);
const executeSetConfiguration = useCallback(() => {
return setStudyConfiguration({ variables: { input: { ...input } } });
}, [input, setStudyConfiguration]);
return [executeSetConfiguration];
};
Example of usage
const [setNoReplyStudyEmail] = useSetConfiguration({
name: 'messaging.email.sender.address',
value: noReplyEmail,
scope: { studyId },
});
useUnsetConfiguration
This custom hook is for remove the configuration setted like from above mutation
import { gql, useMutation } from '#apollo/client';
import { useCallback } from 'react';
const UNSET_STUDY_CONFIGURATION = gql`
mutation WEB_unSetConfiguration($input: unsetStudyConfiguration!) {
unsetStudyConfiguration(input: $input) {
name
value
}
}
`;
export const useUnSetConfiguration = input => {
const [unSetStudyConfiguration] = useMutation(UNSET_STUDY_CONFIGURATION);
const executeUnSetConfiguration = useCallback(() => {
return unSetStudyConfiguration({ variables: { input: { ...input } } });
}, [input, unSetStudyConfiguration]);
return [executeUnSetConfiguration];
};
Example
const [setSmsMessagingDefault] = useUnSetConfiguration({
name: 'messaging.recruitment.sms.enable',
scope: { studyId },
});
As extra details this is a component using this implementation
import {
useConfiguration,
useSetConfiguration,
useStudyConfigurationOverride,
useUnSetConfiguration
} from '#lib/hooks/useConfiguration';
import { FormControlLabel, FormGroup, Switch, Typography } from '#mui/material';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
const StudyConfiguration = ({ studyId }) => {
const intl = useIntl();
// !TODO: make one smart hook/s
// Tracking the study scope/value
const studyOverrides = useStudyConfigurationOverride({
name: 'messaging.email.sender.address',
scope: { studyId },
});
console.log('studyOverrides: ', studyOverrides);
// Getting the study config
const smsEnabled = useConfiguration({
name: 'messaging.recruitment.sms.enable',
scope: { studyId },
defaultValue: false,
});
console.log('smsEnabled: ', smsEnabled);
const emailSender = useConfiguration({
name: 'messaging.email.sender.address',
scope: { studyId },
});
console.log('emailSender: ', emailSender);
const [studyConfOverride, setStudyConfOverride] = useState(studyOverrides);
const [valueEmailReply, setValueEmailReply] = useState(emailSender);
const [valueSmsConf, setValueSmsConf] = useState(smsEnabled);
useEffect(() => {
if (studyConfOverride.length !== studyOverrides.length) {
setStudyConfOverride(studyOverrides);
}
}, [studyOverrides]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (valueEmailReply !== emailSender) {
setValueEmailReply(emailSender);
}
}, [emailSender]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (valueSmsConf !== smsEnabled) {
setValueSmsConf(smsEnabled);
}
}, [smsEnabled]); // eslint-disable-line react-hooks/exhaustive-deps
// Building the default reply email based on 'SCOPE'
// !TODO: study overrides sort in study service (TBD)
let defaultEmail;
if (studyOverrides.find(o => o.scope === 'GLOBAL')) {
const { value } = studyOverrides.find(o => o.scope === 'GLOBAL');
defaultEmail = value;
} else if (studyOverrides.find(o => o.scope === 'DEFAULT')) {
const { value } = studyOverrides.find(o => o.scope === 'DEFAULT');
defaultEmail = value;
}
// Extracting the email domain from default email and used to make a 'noreply#domain.xxx'
const emailDomain = defaultEmail?.substring(defaultEmail.indexOf('#'));
const noReplyEmail = `noreply${emailDomain}`;
// Set study config
const [setNoReplyStudyEmail] = useSetConfiguration({
name: 'messaging.email.sender.address',
value: noReplyEmail,
scope: { studyId },
});
const [setSmsMessagingDisable] = useSetConfiguration({
name: 'messaging.recruitment.sms.enable',
value: 'false',
scope: { studyId },
});
// unSet study config
const [setDefaultStudyEmail] = useUnSetConfiguration({
name: 'messaging.email.sender.address',
scope: { studyId },
});
const [setSmsMessagingDefault] = useUnSetConfiguration({
name: 'messaging.recruitment.sms.enable',
scope: { studyId },
});
const handleReplyEmailChange = async event => {
setValueEmailReply(event.target.checked ? defaultEmail : noReplyEmail);
event.target.checked
? await setDefaultStudyEmail()
: await setNoReplyStudyEmail();
};
const handleSmsConf = async event => {
setValueSmsConf(event.target.checked);
event.target.checked
? await setSmsMessagingDefault()
: await setSmsMessagingDisable();
};
const isEmailEnabled = valueEmailReply === defaultEmail;
return (
<FormGroup>
<FormControlLabel
control={
<Switch
data-testid="email-reply"
checked={isEmailEnabled}
onChange={handleReplyEmailChange}
/>
}
label={
<Typography color="textPrimary">
{intl.formatMessage(
{
defaultMessage:
'Allow candidates to reply to emails (send from {replyEmailTxt} instead of {noReplyTxt})',
},
{ replyEmailTxt: defaultEmail, noReplyTxt: noReplyEmail },
)}
</Typography>
}
/>
<FormControlLabel
control={
<Switch
data-testid="sms-enable"
checked={valueSmsConf}
onChange={handleSmsConf}
/>
}
label={
<Typography color="textPrimary">
{intl.formatMessage({
defaultMessage: `SMS messaging`,
})}
</Typography>
}
/>
</FormGroup>
);
};
export default StudyConfiguration;
Answering my own question in case of someone will have similar issues.
After hours I found the following solution merging all the custom hooks into one hook.
The solution
import { gql, useMutation, useQuery } from '#apollo/client';
import { useCallback } from 'react';
const GET = gql`
query WEB_useConfiguration($name: String!, $scope: StudyConfigurationScope) {
studyConfiguration(name: $name, filter: $scope) {
name
value
configurationOverrideChain {
value
scope
}
}
}
`;
const SET = gql`
mutation WEB_setConfiguration($input: setStudyConfiguration!) {
setStudyConfiguration(input: $input) {
name
value
}
}
`;
const UNSET = gql`
mutation WEB_unsetConfiguration($input: unsetStudyConfiguration!) {
unsetStudyConfiguration(input: $input) {
name
value
}
}
`;
const useGetConfiguration = ({ name, scope, defaultValue }) => {
const { data } = useQuery(GET, {
variables: { name, scope },
fetchPolicy: 'network-only',
});
const value = data?.studyConfiguration?.value;
const overrideChain = data?.studyConfiguration?.configurationOverrideChain;
const studySettings = {};
if (!value) studySettings.value = defaultValue;
else {
try {
studySettings.value = JSON.parse(value);
} catch {
studySettings.value = value;
}
}
if (!overrideChain) studySettings.overrideChain = [];
else {
studySettings.overrideChain = overrideChain;
}
return studySettings;
};
const useSetConfiguration = ({ name, scope }) => {
const [setStudyConfiguration] = useMutation(SET);
const executeSetConfiguration = useCallback(
(value) =>
setStudyConfiguration({ variables: { input: { name, scope, value } } }),
[name, scope, setStudyConfiguration],
);
return executeSetConfiguration;
};
const useUnsetConfiguration = input => {
const [unsetStudyConfiguration] = useMutation(UNSET);
const executeUnsetConfiguration = useCallback(
() => unsetStudyConfiguration({ variables: { input: { ...input } } }),
[input, unsetStudyConfiguration],
);
return executeUnsetConfiguration;
};
export const useConfiguration = ({ name, scope, defaultValue = null }) => {
// GET
const studySettings = useGetConfiguration({ name, scope, defaultValue });
// SET
const setStudyConfiguration = useSetConfiguration({ name, scope });
// UNSET
const unsetStudyConfiguration = useUnsetConfiguration({ name, scope });
return [studySettings, setStudyConfiguration, unsetStudyConfiguration];
};
and an example of how it is used in a possible component
const [emailSettings, setSenderEmail, unsetSenderEmail] = useConfiguration({
name: 'messaging.email.sender.address',
scope: { studyId },
});
I'm getting a "Unhandled Rejection (Error): Too many re-renders. React limits the number of renders to prevent an infinite loop." message for the following code. Not sure what is causing this issue.
I think it's because I'm calling the setNewNotifications(combineLikesCommentsNotifications) within the users.map loop. But if I move setNewNotifications(combineLikesCommentsNotifications) outside of the loop, it can no longer read likeNewNotifications / commentNewNotifications. What is the best approach to this?
Code below, for context, users returns:
const users = [
{
handle: "BEAR6",
posts: undefined,
uid: "ckB4dhBkWfXIfI6M7npIPvhWYwq1"
},
{
handle: "BEAR5",
posts: [
{
comment: false,
handle: "BEAR5",
key: "-Mmx7w7cTl-x2yGMi9uS",
like: {
Mn4QEBNhiPOUJPBCwWO: {
like_notification: false,
postId: "-Mmx7w7cTl-x2yGMi9uS",
postUserId: "rFomhOCGJFV8OcvwDGH6v9pIXIE3",
uid: "ckB4dhBkWfXIfI6M7npIPvhWYwq1",
userLikeHandle: "BEAR6"
}},
post_date: 1635260810805,
title: "hello"
},
{
comment: false,
comments_text: {0: {
comment_date: 1635399828675,
comment_notification: false,
commenter_comment: "hi1",
commenter_handle: "BEAR6",
commenter_uid: "ckB4dhBkWfXIfI6M7npIPvhWYwq1",
key: "-Mn4QF1zT5O_pLRPqi8q"
}},
handle: "BEAR5",
key: "-MmxOs0qmFiU9gpspEPb",
like: {
Mn4QDCOrObhcefvFhwP: {
like_notification: false,
postId: "-MmxOs0qmFiU9gpspEPb",
postUserId: "rFomhOCGJFV8OcvwDGH6v9pIXIE3",
uid: "ckB4dhBkWfXIfI6M7npIPvhWYwq1",
userLikeHandle: "BEAR6"},
Mn4QKEk95YG73qkFsWc: {
postId: "-MmxOs0qmFiU9gpspEPb",
postUserId: "rFomhOCGJFV8OcvwDGH6v9pIXIE3",
uid: "rFomhOCGJFV8OcvwDGH6v9pIXIE3",
userLikeHandle: "BEAR5"
}},
post_date: 1635265250442,
title: "hi"
}
],
uid: "rFomhOCGJFV8OcvwDGH6v9pIXIE3"
}
]
Code
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
export default function Notifications() {
const [newNotifications, setNewNotifications] = useState('')
const users = useSelector(state => state.users)
return users.map((post) => {
if(post.posts){
return post.posts.map((postContent) => {
const likes = postContent.like ? Object.values(postContent.like) : null
const comments = postContent.comments_text ? Object.values(postContent.comments_text) : null
const likeNewNotifications = likes ? likes.filter(post => {
return post.like_notification === false
} ) : null
const commentNewNotifications = comments ? comments.filter(post => {
return post.comment_notification === false
} ) : null
const combineLikesCommentsNotifications = likeNewNotifications.concat(commentNewNotifications)
setNewNotifications(combineLikesCommentsNotifications)
}
)
}
return (
<div>
<p>
{newNotifications}
</p>
</div>
);
}
)
}
There are multiple errors. But lets face it step by step.
I'll copy and paste your code, but with extra comments, to let you know where I'm referencing:
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
export default function Notifications() {
const [newNotifications, setNewNotifications] = useState('')
const users = useSelector(state => state.users)
// Step 0: I guess the error is because this users.map is running everytime (with any update in the component. So, when you set a new state, it'll render again. So, you have to do this, probably 2 times: on mount and after one update.
// Step 1: You're using users.map but it returns a new array. My recommendation would be: use users.forEach instead.
return users.map((post) => {
if(post.posts){
return post.posts.map((postContent) => {
const likes = postContent.like ? Object.values(postContent.like) : null
const comments = postContent.comments_text ? Object.values(postContent.comments_text) : null
const likeNewNotifications = likes ? likes.filter(post => {
return post.like_notification === false
} ) : null
const commentNewNotifications = comments ? comments.filter(post => {
return post.comment_notification === false
} ) : null
const combineLikesCommentsNotifications = likeNewNotifications.concat(commentNewNotifications)
setNewNotifications(combineLikesCommentsNotifications)
}
)
}
return (
<div>
<p>
{newNotifications}
</p>
</div>
);
}
)
}
(Read Step 0 and Step 1 as comments in the code)
Also, about:
But if I move setNewNotifications(combineLikesCommentsNotifications) outside of the loop, it can no longer read likeNewNotifications / commentNewNotifications. What is the best approach to this?
You can do
Step 3: To be able to do that, you can use let, set one variable in the parent of the loop and update the value inside the loop (or if you have an array can push even if it's const). it'd be like:
function foo() {
const users = [{}, {}, {}, {}];
const usersWithEvenId = [];
users.forEach(user => {
if (user.id % 2 === 0) {
usersWithEvenId.push(user)
}
})
}
Taking in consideration these 3 steps the resulted code would be like:
import React, { useState, useEffect} from 'react';
import { useSelector, useDispatch } from 'react-redux';
export default function Notifications() {
const [newNotifications, setNewNotifications] = useState('');
const users = useSelector(state => state.users);
// Function to get new posts
const getNewPosts = () => {
const notifications = [];
users.forEach((user) => {
if (user.posts) {
posts.forEach((post) => {
// Your logic;
notifications.push(newNotifications)
})
}
});
setNewNotifications(notifications);
};
// Run to get newPosts on mount (but also in any other moment)
useEffect(() => {
getNewPosts();
}, [])
return (
<div>
<p>
{newNotifications}
</p>
</div>
);
}
Maybe you can write the code like this:
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
export default function Notifications() {
const users = useSelector((state) => state.users);
const combineLikesCommentsNotifications = users.map((post) => {
if (post.posts) {
return post.posts.map((postContent) => {
const likes = postContent.like ? Object.values(postContent.like) : null;
const comments = postContent.comments_text
? Object.values(postContent.comments_text)
: null;
const likeNewNotifications = likes
? likes.filter((post) => {
return post.like_notification === false;
})
: null;
const commentNewNotifications = comments
? comments.filter((post) => {
return post.comment_notification === false;
})
: null;
const combineLikesCommentsNotifications = likeNewNotifications.concat(
commentNewNotifications
);
setNewNotifications(combineLikesCommentsNotifications);
});
}else{
return [];
}
})
const [newNotifications, setNewNotifications] = useState(combineLikesCommentsNotifications);
return (
<div>
<p>{newNotifications}</p>
</div>
); ;
}
I have this code for my context provider, I have my wrapped in component but still when I try to use it in a child using either useProductState or useProductDispatch, it returns undefined (throws err);
import React from "react";
import productsReducer from "./productsReducer";
const ProductsStateContext = React.createContext();
const ProductsDispatchContext = React.createContext();
const initialState = {
restaurantTitle: "",
restaurantId: "VljSa5Eakepw9QkTAUOW",
productsCollection: "",
categories: [],
defaultCategory: "",
isLoading: true,
};
function ProductsProvider({ children }) {
const [state, dispatch] = React.useReducer(productsReducer, initialState);
return (
<ProductsStateContext.Provider value={state}>
<ProductsDispatchContext.Provider value={dispatch}>
{children}
</ProductsDispatchContext.Provider>
</ProductsStateContext.Provider>
);
}
function useProductsState() {
const context = React.useContext(ProductsStateContext);
if (context === undefined) {
throw new Error("useProductsState must be used within a ProductsProvider");
}
return context;
}
function useProductsDispatch() {
const context = React.useContext(ProductsDispatchContext);
if (context === undefined) {
throw new Error(
"useProductsDispatch must be used within a ProductsProvider"
);
}
return context;
}
export { ProductsProvider, useProductsState, useProductsDispatch };
Can somebody explain how this works, I'm trying to access state and dispatch into a functional component that is a child of .
UPDATE:
I've got this as an action for my reducer
case "FETCH_RESTAURANT_DATA": {
return fetchRestaurantData(state, action.payload);
}
Function body looks like this:
const fetchRestaurantData = (state, value) => {
let newState = state;
return axios
.post(api.routes.restaurant, { restaurantId: state.restaurantId })
.then((res) => {
newState.restaurantTitle = res.data.restaurantTitle;
res.data.categories.forEach(
(category) =>
(newState.categories[category] = {
loaded: false,
props: [],
})
);
newState.defaultCategory = res.data.categories[0];
newState.productsCollection = res.data.productsCollection;
newState.isLoading = false;
return axios.post(api.routes.category, {
productsCollection: res.data.productsCollection,
categoryId: newState.defaultCategory,
});
})
.then((res) => {
newState.categories[newState.defaultCategory].props =
res.data[newState.defaultCategory];
newState.categories[newState.defaultCategory].loaded = true;
console.log(newState);
return newState;
});
};
What i think is going on, I think in reducer it does not wait for my response and update context state with an undefined value which then triggers my error.
I have tried to make a middle async function that awaits for fetchRestaurantData() response but it is still updating before getting a response
You should wait for the response in fetchRestaurantData:
const fetchRestaurantData = async (state, value) => { // add async keyword to the function
let newState = state;
return await axios // here add await
.post(api.routes.restaurant, { restaurantId: state.restaurantId })
.then((res) => {
newState.restaurantTitle = res.data.restaurantTitle;
res.data.categories.forEach(
(category) =>
(newState.categories[category] = {
loaded: false,
props: [],
})
);
newState.defaultCategory = res.data.categories[0];
newState.productsCollection = res.data.productsCollection;
newState.isLoading = false;
return axios.post(api.routes.category, {
productsCollection: res.data.productsCollection,
categoryId: newState.defaultCategory,
});
})
.then((res) => {
newState.categories[newState.defaultCategory].props =
res.data[newState.defaultCategory];
newState.categories[newState.defaultCategory].loaded = true;
console.log(newState);
return newState;
});
};
More information about the async functions
I'm using React/Redux in this code and I'm trying to pass the correct prop by action. My intention is to change converter name on click modal button. But when I debbug, console server shows me the same action with no alteration clicking on confirm button.
My action in file actions:
export const saveOrUpdateConverter = converter => {
return {
converter,
type: CONVERTER.SAVE_OR_UPDATE_CONVERTER
};
};
The function I'm using to do that:
export const saveOrUpdateConverter = (converter, type) => {
const url = `${BASE_URL}/SaveOrUpdateConverter`;
let converterWithoutId = {
...converter,
Id: 0
};
return makeRequest(
{
method: "post",
url,
data: type === "edit" ? converter : converterWithoutId
},
(data, dispatch) => {
// if we are adding a new converter, we need to remove it from newConverters
if (type === "add") {
dispatch(actions.removeFromNewConverters(converter));
}
dispatch(actions.saveOrUpdateConverter(data));
},
true
);
};
The file where I'm calling the function
const handleSaveUpdateConverter = async () => {
let type = "edit";
return await props.saveOrUpdateConverter(converter, type);
};
Component receiving function by prop:
<AddOrEditConverterModal
converter={converter}
show={showEditConverterModal}
onCloseModal={() => setShowEditConverterModal(false)}
saveOrUpdateConverter={(converter, propsType) =>
handleSaveUpdateConverter(converter, propsType)
}
type={"edit"}
/>
I finally call the props saveOrUpdateConverter in other file:
const updateConverter = async () => {
if (converter.IntervalToSavePayload < 5) {
props.requestError(
true,
props.intl.formatMessage({
id: "modal.base.converter.interval.save.pyload.error"
})
);
return;
}
await props.saveOrUpdateConverter(converter, props.type);
debugger
props.onCloseModal();
};
Connect function to use saveOrUpdateConverter :
import { connect } from "react-redux";
import { saveOrUpdateConverter } from "Features/Devices/Converters/actions";
import ConverterPage from "./ConverterPage";
const mapStateToProps = state => ({
activeConverters: state.converter.activeConverters,
activeInstruments: state.instrument.activeInstruments
});
export default connect(mapStateToProps, {saveOrUpdateConverter})(ConverterPage);
Im wondering how I can call a method from outside of a React Functional Component. I wrote the function GetUsedLockers() which gets all the used lockers and returns amount. Now I want to call this function from another another component (OrgLocker.tsx) and display the data from the getUsedLockers() function there.
OrgLockerTables.tsx
const OrgLockerTables: React.FC = () => {
const lockerCall = 'lockers';
const [lockerData, setLockerData] = useState({
id: 0,
guid: "",
is_currently_claimable: false
}[""]);
useEffect(() => {
componentConsole().then((res) => {
setLockerData(res);
})
// eslint-disable-next-line
}, []);
if (!lockerData) return (<div>Loading...</div>);
//function to get all used lockers
function getUsedLockers() {
let amount = 0;
for (let i = 0; i < lockerData.length; i++) {
if (!lockerData.is_currently_claimable) {
amount++;
}
}
console.log('log from getusedlockers, amount: ', amount)
return (amount)
}
// function to get JSON data from the API
function componentConsole(): Promise<any> {
return new Promise<any>((resolve, reject) => {
http.getRequest('/' + lockerCall).then((res) => {
let data = res.data.data;
console.log('data:', data);
resolve(res.data.data);
}).catch((error) => {
console.log(error);
reject();
});
})
}
}
OrgLocker.tsx
import OrgLockerTables from '../tables/orgLockerTables';
const OrgLockers: React.FC = () => {
let lockerTable = new OrgLockerTables();
return (
<div className="main-div-org">
<p>Used</p>
<p>{lockerTable.getUsedLockers()}</p>
</div>
);
}
export default OrgLockers;
When trying to make a call to OrgLockerTables and storing it in the lockerTable let it gives the following error:
Expected 1-2 arguments, but got 0.ts(2554)
Any help would be greatly appreciated!
I've restructured everything making it more understandable, I hope you don't mind according to what I think you want the comment above.
locker-model.ts - The type for the particular data being called back is found
export type Locker = {
id: number;
guid: string;
isCurrentlyClaimable: boolean;
}
locker-business.ts - Where all the business logic is carried out, from the call for data to the calculation based on it
import { Locker } from "./locker-models";
const lockerCall = 'lockers';
const mockedData: Locker[] = [{
id: 0,
guid: "sample",
isCurrentlyClaimable: false,
},
{
id: 1,
guid: "sample2",
isCurrentlyClaimable: true,
},
{
id: 2,
guid: "sample3",
isCurrentlyClaimable: true,
}]
// Mocked function from your backend (componentConsole where you use lockerCall variable)
export const getLockersData = (): Promise<Locker[]> => Promise.resolve(mockedData);
export const getAmount = (lockers: Locker[]): number => {
let amount = 0;
!!lockers ?
lockers.filter(({isCurrentlyClaimable}) => { if(isCurrentlyClaimable) amount++ })
: 0;
return amount;
};
index.tsx - Here are both components that make the call to get the data and render the result you're looking for
import React, { Component } from 'react';
import { Locker } from './locker-models';
import { getLockersData, getAmount } from './locker-business';
import './style.css';
type OrgLockersProps = {
amount: number;
}
const OrgLockers: React.FC<OrgLockersProps> = ({ amount }) => {
return (
<div className="main-div-org">
<p>Lockers used:</p>
<p>{amount}</p>
</div>
);
}
type OrgLockerTableProps = {};
const OrgLockerTable : React.FC<OrgLockerTableProps> = props => {
const [lockerData, setLockerData] = React.useState<Locker[]>([]);
React.useEffect(() => {
getLockersData().then(response => setLockerData(response));
}, []);
const amount = getAmount(lockerData);
return (
<div>
<OrgLockers amount={amount} />
</div>
);
};
You can see the example here
You can create new .js file like Helpers.js and define export function with parameter it like that
export function getUsedLockers(lockerData) {
let amount = 0;
//Check your loop it can be like that
for (let i = 0; i < lockerData.length; i++) {
if (!lockerData[i].is_currently_claimable) {
amount++;
}
}
console.log('log from getusedlockers, amount: ', amount)
return (amount)
}
Then import it where do you want to use.
import {getUsedLockers} from "../Helpers";
And use it like that:
const amount = getUsedLockers(data);