React useState hook with callback - reactjs

I'm having a hard time converting the following methods with setState
sendMsgToMe = () => {
const { senderIdString, senderId } = this.props; //eslint-disable-line
const { messages, counter } = this.state;
let myNumber;
this.setState({
btnSendMeIsDisabled: true,
});
axios
.get(`${appConfig.URL_REST}user`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
})
.then((response) => {
myNumber = response.data.phoneNumber;
this.setState(
{
messageToSend: [
{
from: senderIdString ? senderIdString.number : senderId,
to: myNumber,
message: messages[counter],
},
],
},
this.sendMsgAxios
);
});
};
sendMsgToRecipients = () => {
const { recipientsNmbs, senderIdString, senderId } = this.props;
const { messages, counter } = this.state;
this.setState({
btnSendIsDisabled: true,
});
const msgToSend = recipientsNmbs.map((item) => {
return {
from: senderIdString ? senderIdString.number : senderId,
to: item,
message: messages[counter],
};
});
this.setState(
{
messageToSend: msgToSend,
},
this.sendMsgAxios
);
};
into functions. I saw examples with calling it from useEffect with dependency array but they were more plain.
The main issue in this piece of code
this.setState(
{
messageToSend: [
{
from: senderIdString ? senderIdString.number : senderId,
to: myNumber,
message: messages[counter],
},
],
},
this.sendMsgAxios
);
My questions is how exactly should I call it in useEffect or is there any better approach?

You could use a new state callSendMsgAxios that will trigger sendMsgAxios when sendMsgToMe or sendMsgToRecipients are executed, your component will look like:
...
// useReducer is better in this case
const [state, setState] = useState({
messages: '',
counter: '',
btnSendMeIsDisabled: false,
messageToSend: [{
from: '',
to: '',
message: ''
}],
callSendMsgAxios: false,
...
});
useEffect(() => {
if(callSendMsgAxios) {
sendMsgAxios();
setState(prevState => ({ ...prevState, callSendMsgAxios: false }) );
}
}, [callSendMsgAxios]);
...
const sendMsgToMe = () => {
const { senderIdString, senderId } = props;
const { messages, counter } = state;
let myNumber;
setState(prevState => ({
...prevState,
btnSendMeIsDisabled: true,
}));
axios
.get(`${appConfig.URL_REST}user`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
})
.then((response) => {
myNumber = response.data.phoneNumber;
setState(prevState => (
{
...prevState,
callSendMsgAxios: true,
messageToSend: [
{
from: senderIdString ? senderIdString.number : senderId,
to: myNumber,
message: messages[counter],
},
],
}
));
});
};
...

The code you put in useEffect, is executed, based on the dependencies you send to it.
So first ask yourself,
What should I execute? (Becomes the body of your useEffect)
Does it depend on anything before determining if it should execute or not. (Becomes the dependency)
useEffect(() => {
this.sendMsgAxios(a, b c); // a, b, c could be still thats contained in your state
}, [a, b, c]) // clearly since a, b, c are ones which the effect depends on, you need to pass them in the dependencies array
I'm hoping this will lead to what you want to achieve.

Related

React does not rerender on updated state of nested array

I have an array of objects like so:
const [categories, setCategories] = React.useState([
{
id: 1,
title: 'Top Picks',
subTitle: "Today's hottest stuff",
images: [],
searchQuery: 'shoes',
},
...]);
Which I update with values in useEffect once like so:
React.useEffect(() => {
const newCategories = categories.map(category => {
fetch(`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`, {
headers: {
'Authorization': apiKey,
},
}).then(r => {
r.json().then(convertedJson => {
category.images = convertedJson.photos;
});
});
return category;
});
setCategories(newCategories);
}, []);
however the child components here never rerender and I cannot figure out why. My understanding is that .map creates a new array anyhow, so the spread syntax isn't necessary in setCategories() but regardless it does not work.
{categories.map((category, i) => (
<CategorySlider {...category} key={i}/>
))}
There's a few issues but the primary issue I see is you're returning the category before the fetch can complete - so even when those fetch calls inside your map complete, you already returned the category below before the fetch completes.
Try using the .finally() block:
React.useEffect(() => {
const newCategories = categories.map(category => {
const c = {...category}; // <--- good practice
fetch(`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`, {
headers: {
'Authorization': apiKey,
},
}).then(r => {
r.json().then(convertedJson => {
category.images = convertedJson.photos;
});
}).catch((err) => {
console.error(err);
}).finally(() => {
return category;
});
});
setCategories(newCategories);
}, []);
Thanks! Using setState before the promises resolved was indeed the problem. The solution looks like this now:
React.useEffect(() => {
async function fetchImages() {
const promises = categories.map(async category => {
const response = await fetch(
`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`,
{
headers: {
Authorization: apiKey,
},
}
);
const convertedJson = await response.json();
category.images = convertedJson.photos;
return category;
});
setCategories(await Promise.all(promises));
}
fetchImages();
}, []);

Update Values of Multiple Array in Redux

I'm updating an array and I wanted to update the productCode based on the given newCode response. This is by clicking the 'CREATE ALL PRODUCTS' button.
I'm thinking that the problem is on the reducer. It's currently not updating the productCode and newProductCode
Tip: productIndex is the key to finding it
Click Here: CODESANDBOX
Action
export const createAllProducts = (products) => async (dispatch) => {
try {
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_REQUEST
});
const responses = [
{
config: null,
data: {
newCode: "NEW_AA"
},
headers: null
},
{
config: null,
data: {
newCode: "NEW_FF"
},
headers: null
},
{
config: null,
data: {
newCode: "NEW_GG"
},
headers: null
}
];
const finalResponses = responses.map((product, index) => ({
newProductCode: product.data.newCode,
productCode: product.data.newCode,
productIndex: products[index].productIndex
}));
console.log(finalResponses);
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_SUCCESS,
payload: finalResponses
});
} catch (error) {
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_FAILURE
});
}
};
Reducer
case appConstants.CREATE_ALL_PRODUCTS_SUCCESS:
const updatedProducts = state.products.map((product, index) => {
const found = action.payload.find((el) => el.productIndex === index);
return found
? {
...updatedProducts,
productCode: found.productCode,
newProductCode: found.newProductCode
}
: product;
});
return {
...state,
isCreatingAllProducts: false,
products: updatedProducts
};
The issue is with the reducer
case appConstants.CREATE_ALL_PRODUCTS_SUCCESS:
return {
...state,
products: state.products.map((product, index) => {
const found = action.payload.find((el) => el.productIndex === index);
console.log(found);
return found
? {
...product,
productCode: found.productCode,
newProductCode: found.newProductCode
}
: product;
})
};
You used reduce methods with the initial value state, which is the actually old state.
Consider this example:
const state = { history: null }
const payload = [ 'hello', 'equal' ]
//your current reducer
const newState = payload.reduce((acc, cur) => { acc[cur] = cur; return acc } , state)
//the state reference point to the same obj, then redux will not trigger re-render
console.log(newState === state) // true

Accessing the values from Promise inside useEffect hook

const useOnfidoFetch = (URL) => {
useEffect(() => {
const appToken = axios.get('http://localhost:5000/post_stuff')
.then((response) => response.data.data.data.json_data)
.then((json_data) => {
const id = json_data.applicant_id;
const token = json_data.onfido_sdk_token;
return {id, token};
});
if (appToken) {
console.log('this is working!');
OnfidoSDK.init({
// the JWT token you generated above
token: null,
containerId: "root",
steps: [
{
type: 'welcome',
options: {
title: 'Open your new bank account',
},
},
'document'
],
onComplete: function (data) {
console.log('everything is complete');
axios.post('https://third/party/api/v2/server-api/anonymous_invoke?aid=onfido_webapp', {
params: {
applicant_id: appToken.applicant_id
}
});
}
});
}
}, [URL]);
}
export default function() {
const URL = `${transmitAPI}/anonymous_invoke?aid=onfido_webapp`;
const result = useOnfidoFetch(URL, {});
return (
<div id={onfidoContainerId} />
);
}
I have refactored this dozens of times already, I am getting back some values from the appToken Promise, but I need to provide the token value from that Promise to that token property inside of Onfido.init({}) and I need to provide the id to the applicant_id property and I continue to get undefined.
If you need the token for something else as well, then i would suggest storing it in useState, and triggering OnfidoSDK.init when the value of that state changes.
Like this:
const useOnfidoFetch = (URL) => {
const [token, setToken] = useState();
useEffect(() => {
axios.get('http://localhost:5000/post_stuff')
.then((response) => response.data.data.data.json_data)
.then((json_data) => {
const token = json_data.onfido_sdk_token;
setToken(token);
})
}, [URL])
useEffect(() => {
if (!token) return;
OnfidoSDK.init({
// the JWT token you generated above
token,
containerId: "root",
steps: [
{
type: 'welcome',
options: {
title: 'Open your new bank account',
},
},
'document'
],
onComplete: function (data) {
console.log('everything is complete');
axios.post('https://third/party/api/v2/server-api/anonymous_invoke?aid=onfido_webapp', {
params: {
applicant_id: appToken.applicant_id
}
});
}
});
}, [token]);
}
Move the entire if(appToken){ ... } inside the body of the second .then((json_data) => { ... })
Something like this:
const useOnfidoFetch = (URL) => {
useEffect(() => {
const appToken = axios.get('http://localhost:5000/post_stuff')
.then((response) => response.data.data.data.json_data)
.then((json_data) => {
const id = json_data.applicant_id;
const token = json_data.onfido_sdk_token;
// Here. This code will be executed after the values are available for id and token
if (appToken) {
console.log('this is working!');
OnfidoSDK.init({
// the JWT token you generated above
token: null,
containerId: "root",
steps: [
{
type: 'welcome',
options: {
title: 'Open your new bank account',
},
},
'document'
],
onComplete: function (data) {
console.log('everything is complete');
axios.post('https://third/party/api/v2/server-api/anonymous_invoke?aid=onfido_webapp', {
params: {
applicant_id: appToken.applicant_id
}
});
}
});
}
return {id, token};
});
// In here the promise is not yet finished executing, so `id` and `token` are not yet available
}, [URL]);
};
export default function() {
const URL = `${transmitAPI}/anonymous_invoke?aid=onfido_webapp`;
const result = useOnfidoFetch(URL, {});
return (
<div id={onfidoContainerId} />
);
}
For better readability, you could also move the if(appToken){ ... } block inside a separate function that takes id, token as arguments, which you can call from inside the promise.then block.

How to set loading true in graphql query.?

I am using graphQl in this i want to set loading = true for 1 second to show loader after that it will reset by response how will i do that
i am using below code right now,
const loadData = graphql(initialData, {
options: ({ params: { Id }, authData: { userPermissions } }) => ({
variables: {
Id,
Settings: hasPermissions(userPermissions, [USER_PERMISSIONS.du]),
},
fetchPolicy: APOLLO_FETCH_POLICIES.NETWORK_ONLY,
errorPolicy: APOLLO_ERROR_POLICIES.ALL,
notifyOnNetworkStatusChange: true,
}),
// skip: props => props.loading = true,
props: ({ data }) => {
const { error, loading, refetch, networkStatus, buy, timeZones, manager } = data;
return {
error:error,
loading: networkStatus === 1 && !loading ? true : loading,
networkStatus,
onReload: refetch,
timeZones,
manager: get(manager, 'itUsers', []),
};
},
});
Any help is appreciated.
Well, you can use custom fetch. Something like this might work:
const customFetch = (url, {delay, ...opts}) => {
return Promise.all([
fetch(url, opts),
new Promise(resolve => setTimeout(resolve, delay || 0)),
]).then(([res, _]) => res)
}
const uploadLink = createUploadLink({
uri,
fetch: customFetch,
})
const client = new ApolloClient({
cache,
link: uploadLink,
})
//////////////////////////////////////////////
// Then you can add delay option via context
//////////////////////////////////////////////
const loadData = graphql(initialData, {
options: ({ params: { Id }, authData: { userPermissions } }) => ({
variables: {
Id,
Settings: hasPermissions(userPermissions, [USER_PERMISSIONS.du]),
},
fetchPolicy: APOLLO_FETCH_POLICIES.NETWORK_ONLY,
errorPolicy: APOLLO_ERROR_POLICIES.ALL,
notifyOnNetworkStatusChange: true,
///////////////////////////////////////////
// add context with delay
context: {
fetchOptions: {delay: 1000},
///////////////////////////////////////////
},
}),
// skip: props => props.loading = true,
props: ({ data }) => {
const { error, loading, refetch, networkStatus, buy, timeZones, manager } = data;
return {
error:error,
loading: networkStatus === 1 && !loading ? true : loading,
networkStatus,
onReload: refetch,
timeZones,
manager: get(manager, 'itUsers', []),
};
},
});
I have not tested it.

useState does not support a second callBack, what could be the easy fix?

This is my useEffect
useEffect(() => {
let pageId =
props.initialState.content[props.location.pathname.replace(/\/+?$/, "/")]
.Id;
if (props.initialState.currentContent.Url !== props.location.
setCurrentContent({ currentContent: { Name: "", Content: "" } }, () => {
fetch(`/umbraco/surface/rendercontent/byid/${pageId}`, {
credentials: "same-origin"
})
.then(response => {
if (response.ok) {
return response.json();
}
return Promise.reject(response);
})
.then(result => {
setCurrentContent({
currentContent: { Name: result.Name, Content: result.Content }
});
});
});
}
}, []);
I have tried things like useCallback/useMemo but yet no luck, I'm sure this is a simple fix but I must be missing the bigger picture, thanks in advance.
What you can do is write an effect that checks if the currentContent state is changed and empty and takes the necessary action. You would however need to ignore the initial render. Also unline setState in class components you don't pass on the state value as object instead just pass the updated state
const ContentPage = props => {
const [currentContent, setCurrentContent] = useState({
Name: props.initialState.currentContent.Name,
Content: props.initialState.currentContent.Content
});
const initialRender = useRef(true);
useEffect(() => {
let pageId =
props.initialState.content[props.location.pathname.replace(/\/+?$/,
"/")]
.Id;
if (
initialRender.current &&
currentContent.Name == "" &&
currentContent.Content == ""
) {
initialRender.current = false;
fetch(`/umbraco/surface/rendercontent/byid/${pageId}`, {
credentials: "same-origin"
})
.then(response => {
if (response.ok) {
return response.json();
}
return Promise.reject(response);
})
.then(result => {
setCurrentContent({ Name: result.Name, Content: result.Content });
});
}
}, [currentContent]);
useEffect(() => {
if (props.initialState.currentContent.Url !== props.location) {
setCurrentContent({ Name: "", Content: "" });
}
}, []);
...
};
export default ContentPage;

Resources