useEffect is not updating PayPal client id with axios request [duplicate] - javascript

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last month.
Sometimes it seems the most simpliest stuff is not working. I am doing a GET request to my API to get the paypal_client_id, but it's not updating in time.
import { useState } from 'react';
import axios from 'axios';
import { useEffect } from 'react';
const PayPalButton = ({ total, onPaymentSuccess, onPaymentError, disabled }) => {
const [paypalClient, setPayPalClient] = useState('test-id');
useEffect(() => {
const paypalkey = async () => {
const { data: clientId } = await axios.get('/api/config/paypal');
setPayPalClient(clientId);
console.log(paypalClient);
};
paypalkey();
}, []);
return (
<PayPalScriptProvider options={{ 'client-id': paypalClient }}>
<PayPalButtons
disabled={disabled}
forceReRender={[total()]}
createOrder={(data, actions) => {
return actions.order.create({
purchase_units: [
{
amount: {
value: total(),
},
},
],
});
}}
onApprove={(data, actions) => {
return actions.order.capture().then((details) => {
onPaymentSuccess(data);
});
}}
onError={(err) => {
onPaymentError();
}}
/>
</PayPalScriptProvider>
);
};
export default PayPalButton;
I thought my useEffect hook would take care to provide the paypal_client_id in time, but it doesn't work.
the API is working, but the request paypal does to authenticate the button is doing it with 'test-id', that proves that my key is not arriving in time.
Any help is much appreciated.

The useEffect triggers at the first render so it's "normal" that the client-id is not there at the "right time".
You should have a loading in your render to prevent this.
Like :
const [paypalClient, setPayPalClient] = useState('');
return (
{!paypalClient ? <p>Loading...</p> :
(<PayPalScriptProvider options={{ 'client-id': paypalClient }}>
<PayPalButtons
disabled={disabled}
forceReRender={[total()]}
createOrder={(data, actions) => {
return actions.order.create({
purchase_units: [
{
amount: {
value: total(),
},
},
],
});
}}
onApprove={(data, actions) => {
return actions.order.capture().then((details) => {
onPaymentSuccess(data);
});
}}
onError={(err) => {
onPaymentError();
}}
/>
</PayPalScriptProvider>)}
);
Or you can simply store your client-id in your environment variables

Related

Redux Toolkit: How to make a GET request after POST or PUT request is successful?

I made a simple To Do list application to learn Redux Toolkit and after getting it working without a backend, decided to add a simple Flask server to practice working with Redux Toolkit's createAsyncThunk
I was successfully able to create 3 different async thunks for when a user fetches their to-dos, posts a new to-do, or puts a to-do. Here is my toDoSlice:
import { createSlice, current, createAsyncThunk } from '#reduxjs/toolkit';
import axiosPrivate from '../../api/axios';
const initialState = {
toDos: []
}
export const getToDos = createAsyncThunk('toDo/getToDos', async (user) => {
const response = await axiosPrivate.get(`/to-dos/${user?.user?.firebase_uid}`, { headers: { 'Authorization': user?.idToken }, withCredentials: true });
return response.data
})
export const addToDo = createAsyncThunk('toDo/addToDo', async ({ user, newToDo }) => {
const response = await axiosPrivate.post('/to-dos/new', { userId: user?.user?.id, firebaseUid: user?.user?.firebase_uid, task: newToDo?.task }, { headers: { 'Authorization': user?.idToken }, withCredentials: true });
const data = response.data
return {data, user}
})
export const updateToDo = createAsyncThunk('toDo/updateToDo', async ({ user, toDoId, updateAttributes }) => {
const response = await axiosPrivate.put('/to-dos/update', { userId: user?.user?.id, firebaseUid: user?.user?.firebase_uid, toDoId: toDoId, updateAttributes}, { headers: { 'Authorization': user?.idToken }, withCredentials: true })
const data = response.data
return {data, user}
})
export const toDosSlice = createSlice({
name: 'toDos',
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(getToDos.fulfilled, (state, action) => {
state.toDos = action.payload?.toDos
})
.addCase(addToDo.fulfilled, (action) => {
getToDos(action?.payload?.user); // This does not change anything
})
.addCase(updateToDo.fulfilled, (action) => {
getToDos(action?.payload?.user); // This does not change anything
})
}
})
export const { deleteToDo } = toDosSlice.actions;
export const selectToDos = (state) => state.toDos;
export default toDosSlice.reducer;
The problem I am having, is that after a user edits their toDo by marking it complete, I am unsure of where and how to properly fetch the to-dos from the backend. I know that I could technically set the state of the toDo using the redux state and validating if the POST or PUT was successfully, although would like to learn how it is properly done with a GET request thereafter.
My ToDoList component where users can DELETE or PUT their ToDos is as follows:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Stack from '#mui/material/Stack';
import Divider from '#mui/material/Divider';
import Box from '#mui/material/Box';
import Grid from '#mui/material/Grid';
import List from '#mui/material/List';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import IconButton from '#mui/material/IconButton';
import DeleteIcon from '#mui/icons-material/Delete';
import ListItemIcon from '#mui/material/ListItemIcon';
import ListItemText from '#mui/material/ListItemText';
import Checkbox from '#mui/material/Checkbox';
import Typography from '#mui/material/Typography';
import { getToDos, updateToDo, selectToDos } from '../toDoSlice';
import useAuth from '../../../hooks/useAuth';
function ToDoList() {
const dispatch = useDispatch();
const { user } = useAuth();
const toDos = useSelector(selectToDos);
useEffect(() => {
dispatch(getToDos(user));
}, [dispatch, user])
const handleChange = (toDoId, updateAttributes) => {
dispatch(updateToDo({ user, toDoId, updateAttributes }));
// dispatch(getToDos(user)); // This sometimes causes the GET request to occur before the PUT request, which I don't understand
}
return (
<Stack spacing={2}>
<Divider sx={{ marginTop: 5 }} />
<Grid xs={4} item>
{toDos?.toDos?.length ?
<Box>
<List>
{toDos?.toDos?.map((toDo) => (
<ListItem
key={toDo?.id}
secondaryAction={
<IconButton
edge="end"
onClick={() => handleChange(toDo?.id, { 'deleted': !toDo?.deleted })}
>
<DeleteIcon />
</IconButton>
}
>
<ListItemButton onClick={() => handleChange(toDo?.id, { 'completed': !toDo?.completed })}>
<ListItemIcon>
<Checkbox
name={toDo.task}
checked={toDo.completed}
edge='start'
/>
</ListItemIcon>
<ListItemText
primary={toDo.task}
sx={{ textDecoration: toDo.completed ? 'line-through' : null }}
primaryTypographyProps={{
style: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}}
/>
</ListItemButton>
</ListItem>
))}
</List>
</Box>
: <Typography align='center' variant="h6" component="div" mt={2}>No To-Dos</Typography>
}
</Grid >
</Stack >
)
}
export default ToDoList
How do I perform a GET request after the POST or PUT operations? Where should I then put the dispatch(getToDos(user))? The comments in my code show the results of the methods I've already tried
After reading through the Redux-Toolkit docs again and looking through other SO posts, I learned of the proper way to perform asyncThunk calls in series.
Instead of performing them in my slice, I moved the calls to my component and used Promises to execute them. In order to catch the error from the request and have access to it inside the component, you have to use Toolkit's rejectWithValue parameter inside the asyncThunk.
Here's an example:
export const loginUser = createAsyncThunk('user/loginUser', async (loginData, { rejectWithValue }) => {
try {
const response = await axios.post('/login', loginData);
return response.data
} catch (err) {
if (!err.response) {
throw err
}
return rejectWithValue(err.response.data)
}
})
By adding { rejectWithValue } to the parameter and return rejectWithValue(err.response.data), you can access the response in the component, like this:
const handleSubmit = () => {
if (loginData?.email && loginData?.password) {
dispatch(loginUser(loginData))
.unwrap()
.then(() => {
// perform further asyncThunk dispatches here
})
.catch(err => {
console.log(err) // the return rejectWithValue(err.response.data) is sent here
});
}
}

React & Axios: Unable to render array values in UI but I can console.log them

I am trying to render an array of data on the UI. So far, I have been able to console.log the array but have not been able to display it on a table.
I am currently using Axios to retrieve the data from the back-end and am trying to render the data in Bootstrap Table. My initial issue was that I needed to assign each child in the list a unique key prop. Now that this error is gone, I still cannot see the data in the UI.
Invite table component (InviteTable.js):
import React, { useState, useEffect } from 'react';
import Axios from 'axios';
import BootstrapTable from 'react-bootstrap-table-next';
import PaginationFactory from 'react-bootstrap-table2-paginator';
import ToolkitProvider, {Search} from 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit';
import Spinner from 'react-bootstrap/Spinner';
const InviteTable = () => {
const [invites, setInvites] = useState([]);
const [loading, setLoading] = useState(false);
const { SearchBar } = Search;
const url = "http://localhost:5000/api/get";
//Define columns
const columns = [
{ dataField: "Id", text: "Id", headerStyle: () => {return { width: "10%" };} },
{ dataField: "Code", text: "Invite Code", headerStyle: () => {return { width: "10%" };} },
{ dataField: "Recipient", text: "Recipient Email", headerStyle: () => {return { width: "35%" };} },
{ dataField: "Created", text: "Date Sent", headerStyle: () => {return { width: "35%" };} },
];
//GET and set data
useEffect(() => {
Axios.get(url).then(result => {
setInvites(result.data);
console.log(result.data);
setLoading(true);
})
.catch(function(error) {
console.log(error);
});
}, []);
return (
<div>
{loading ? (
<ToolkitProvider keyField="Id" data={invites} columns={columns} search>
{(props) => (
<div>
<SearchBar {...props.searchProps} />
<BootstrapTable
{...props.baseProps}
pagination={PaginationFactory()}
/>
</div>
)}
</ToolkitProvider>
) : (
<Spinner animation="border" />
)}
</div>
)
};
export { InviteTable }
Console:
Output of console.log
I have resolved this issue. The problem lay in the server file that was running the query to return the invites.
I am quite new to this so am still in the process of learning as I go.
Initially I was sending the entire result to the front end which was causing issues:
request.query('select * from Invites;', (err, result) => {
// console log error if there is one
if (err) console.log(err);
// send records as a response
res.send(result);
});
To resolve this, I specified that the recordset should be sent to the front end:
request.query('select * from Invites;', (err, result) => {
// console log error if there is one
if (err) console.log(err);
// send records as a response
res.send(result.recordset);
});
Here is the solution, first check your data if it's in the object then convert it into an array. and make sure your columns' dataField name same as your database parameters
const [post, setPost] = useState([]);
then(response => {
const data = response.data;
const arr = Object.entries(data); //convert object into array
arr.forEach(([key, value]) => {
setPost(oldArray => [...oldArray, value]); // Assign Data toTable
});
})
<BootstrapTable keyField='id' data={post} columns={columns} pagination={paginationFactory()}/>

Making an axios get request and using React useState but when logging the data it still shows null

When I make a request to an API and setting the state to the results from the Axios request it still shows up null. I am using React useState and setting the results from the request and wanting to check to see if its coming through correctly and getting the right data its still resulting into null. The request is correct but when I use .then() to set the state that is the issue I am having.
Below is the component that I am building to make the request called Details.js (first code block) and the child component is the DetailInfo.js file (second code block) that will be displaying the data. What am I missing exactly or could do better when making the request and setting the state correctly display the data?
import React, {useEffect, useState} from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import axios from 'axios';
import { getCookie } from '../utils/util';
import DetailInfo from '../components/DetailInfo';
import DetailImage from '../components/DetailImage';
const Details = () => {
const [ countryData, setCountryData ] = useState(null);
let country;
let queryURL = `https://restcountries.eu/rest/v2/name/`;
useEffect(() => {
country = getCookie('title');
console.log(country);
queryURL += country;
console.log(queryURL);
axios.get(queryURL)
.then((res) => {
console.log(res.data[0])
setCountryData(res.data[0]);
})
.then(() => {
console.log(countryData)
}
);
}, [])
return (
<>
<Container className="details">
<Row>
<Col sm={6}>
<DetailImage />
</Col>
<Col sm={6}>
<DetailInfo
name={countryData.name}
population={countryData.population}
region={countryData.region}
subRegion={countryData.subRegion}
capital={countryData.capital}
topLevelDomain={countryData.topLevelDomain}
currencies={countryData.currencies}
language={countryData.language}
/>
</Col>
</Row>
</Container>
</>
)
}
export default Details;
The child component below......
import React from 'react';
const DetailInfo = (props) => {
const {name, population, region, subRegion, capital, topLevelDomain, currencies, language} = props;
return (
<>detail info{name}{population} {region} {capital} {subRegion} {topLevelDomain} {currencies} {language}</>
)
}
export default DetailInfo;
Ultimately, the problem comes down to not handling the intermediate states of your component.
For components that show remote data, you start out in a "loading" or "pending" state. In this state, you show a message to the user saying that it's loading, show a Spinner (or other throbber), or simply hide the component. Once the data is retrieved, you then update your state with the new data. If it failed, you then update your state with information about the error.
const [ dataInfo, setDataInfo ] = useState(/* default dataInfo: */ {
status: "loading",
data: null,
error: null
});
useEffect(() => {
let unsubscribed = false;
fetchData()
.then((response) => {
if (unsubscribed) return; // unsubscribed? do nothing.
setDataInfo({
status: "fetched",
data: response.data,
error: null
});
})
.catch((err) => {
if (unsubscribed) return; // unsubscribed? do nothing.
console.error('Failed to fetch remote data: ', err);
setDataInfo({
status: "error",
data: null,
error: err
});
});
return () => unsubscribed = true;
}, []);
switch (dataInfo.status) {
case "loading":
return null; // hides component
case "error":
return (
<div class="error">
Failed to retrieve data: {dataInfo.error.message}
</div>
);
}
// render data using dataInfo.data
return (
/* ... */
);
If this looks like a lot of boiler plate, there are useAsyncEffect implementations like #react-hook/async and use-async-effect that handle it for you, reducing the above code to just:
import {useAsyncEffect} from '#react-hook/async'
/* ... */
const {status, error, value} = useAsyncEffect(() => {
return fetchData()
.then((response) => response.data);
}, []);
switch (status) {
case "loading":
return null; // hides component
case "error":
return (
<div class="error">
Failed to retrieve data: {error.message}
</div>
);
}
// render data using value
return (
/* ... */
);
Because state only update when component re-render. So you should put console.log into useEffect to check the new value:
useEffect(() => {
country = getCookie('title');
console.log(country);
queryURL += country;
console.log(queryURL);
axios.get(queryURL).then(res => {
console.log(res.data[0]);
setCountryData(res.data[0]);
});
}, []);
useEffect(() => {
console.log(countryData);
}, [countryData]);
useState does reflecting its change immediately.
I think that it would be probably solved if you set countryData to second argument of useEffect.
useEffect(() => {
country = getCookie('title');
console.log(country);
queryURL += country;
console.log(queryURL);
axios.get(queryURL)
.then((res) => {
console.log(res.data[0])
setCountryData(res.data[0]);
})
.then(() => {
console.log(countryData)
}
);
}, [countryData])
The issue is, as samthecodingman, pointed out, an issue of intermediate data. Your component is being rendered before the data is available, so your child component needs to re-render when its props change. This can be done via optional chaining, an ES6 feature.
import React, { useEffect, useState } from "react";
import DetailInfo from "./DetailInfo";
import { Col, Container, Row } from "react-bootstrap";
import axios from "axios";
const Details = () => {
const [countryData, setCountryData] = useState({});
let country = "USA";
let queryURL = `https://restcountries.eu/rest/v2/name/`;
useEffect(() => {
console.log(country);
queryURL += country;
console.log(queryURL);
axios
.get(queryURL)
.then((res) => {
console.log(res.data[0]);
setCountryData(res.data[0]);
})
.then(() => {
console.log(countryData);
});
}, []);
return (
<Container className="details">
<Row>
<Col sm={6}>
<DetailInfo
name={countryData?.name}
population={countryData?.population}
region={countryData?.region}
subRegion={countryData?.subRegion}
capital={countryData?.capital}
language={countryData?.language}
/>
</Col>
<Col sm={6}></Col>
</Row>
</Container>
);
};
export default Details;
Checkout my Codesandbox here for an example.

Why can I console.log a variable but it not be able to use in a filter

I am trying to create a React.js blog so I have an easybase.io database that will hold data for my blog. There is a page where all posts are displayed, and when someone clicks on a post the post id is sent over using the paramas module in the react-router-dom package. When doing console.log(postid) it displays in the console but when passing it through the filter it doesn't work for some reason is there any explanation? The filter is filtering through all the posts selected from the database and only returning the object with the same id. When putting task.id === 1 it returns with the object with an id of 1 however when putting in task.id === postid and postid is equal to 1 in the console then it returns nothing.
All posts code
/* eslint-disable */
import { useEasybase } from "easybase-react";
import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
export default function BlogPosts() {
let history = useHistory();
const { db } = useEasybase();
const [responseData, setResponse] = useState([]);
async function getPosts() {
const ebData = await db("POSTS").return().all();
setResponse(ebData);
}
useEffect(() => {
getPosts();
}, []);
return (
<>
<div>
{responseData.map((val, key) => {
return (
<div
style={{ border: "1px solid black", margin: "1em 1em" }}
key={key}
onClick={() => {
history.push(`/post/${val.id}`);
}}
>
<h3>{val.title}</h3>
<div dangerouslySetInnerHTML={{ __html: val.post_text }} />
</div>
);
})}
</div>
</>
);
}
Open post code
/* eslint-disable */
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useEasybase } from "easybase-react";
export default function BlogPosts() {
//testing for the filter function
const tasks = [
{
taskId: 1,
taskName: "Clean the bathroom",
taskStatus: "Complete",
},
{
taskId: 2,
taskName: "Learn filtering data in React",
taskStatus: "To do",
},
{
taskId: 3,
taskName: "Fix the bug on React project",
taskStatus: "To do",
},
{
taskId: 4,
taskName: "Fix the car",
taskStatus: "Complete",
},
];
const [responseData, setResponse] = useState([]);
const [idState, setIdState] = useState(0);
let { postid } = useParams();
const { db } = useEasybase();
async function getPosts() {
const ebData = await db("POSTS").return().all();
setResponse(ebData);
console.log(ebData);
}
useEffect(() => {
getPosts();
setIdState(postid);
console.log(postid);
}, []);
return (
<>
<div style={{ justifyContent: "center", alignContent: "center" }}>
{tasks
.filter((task) => task.taskStatus === "To do")
.map((task) => (
<li key={task.taskId}>{task.taskName}</li>
))}
{responseData
.filter((task) => task.id === postid)
.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</div>
</>
);
}
The most likely cause is that postid from the route parameters is a string (as after all, the URL it came from is a string) while your task.id is a number as shown in your code.
When comparing '1' === 1, it'll always fail due to the types being different. Either use ==, or better, first convert the route parameter (postid) to a number.

useState Hooks not working with enzyme mount()

The setWishlists hook in this component seems to not run, even though everything before and after it in the promise chain runs. It just doesn't change wishlists. In my test's setup: handleGetWishlists is passed through a jest mock so that it can still be used in the component while allowing jest to spy on it. The implementation is still passed through so that Mock Service Worker an provide the data instead of mocking fetch.
My repo on the relevant branch is here
Relevant section:
//HomePage.js
import React, { useEffect, useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import {
Accordion,
AccordionSummary,
AccordionDetails,
Typography,
FormGroup,
FormControlLabel,
Checkbox,
} from '#material-ui/core/';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
const useStyles = makeStyles(() => ({
homeContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
heading: {},
accordion: {
width: '50%',
},
}));
const HomePage = ({ handleGetWishlists }) => {
const classes = useStyles();
const [wishlists, setWishlists] = useState([]);
useEffect(() => {
handleGetWishlists()
.then((res) => res.json())
.then((data) => {
console.log('DATA BEFORE SET', data); //--> DATA BEFORE SET [...somedata...]
return data;
})
.then(setWishlists) // --> console.error()
.then(() => console.log('WISHLIST AFTER SET', wishlists)); // --> WISHLIST AFTER SET []
console.log('END OF USE EFFECT');
}, []);
return (
<div className={classes.homeContainer}>
{wishlists.map((wishlist, index) => {
return (
<Accordion key={`Accordian${index}`} className={classes.accordion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
>
<Typography className={classes.heading}>
{`${wishlist.name} by ${wishlist.author}`}
</Typography>
</AccordionSummary>
<AccordionDetails>
<FormGroup>
{wishlist.items.map((item, index) => {
return (
<FormControlLabel
key={`WishlistItemCheckbox${index}`}
control={<Checkbox />}
label={item.name}
/>
);
})}
</FormGroup>
</AccordionDetails>
</Accordion>
);
})}
</div>
);
};
export default HomePage;
//HomePage.test.js
import React from 'react';
import { mount } from 'enzyme';
import { handleGetWishlists } from '../../client/client';
import HomePage from './HomePage';
import { Accordion } from '#material-ui/core/';
describe('HomePage', () => {
let component;
const mockHandleGetWishlists = jest.fn();
beforeEach(async () => {
mockHandleGetWishlists.mockImplementation(handleGetWishlists);
component = mount(<HomePage handleGetWishlists={mockHandleGetWishlists} />);
});
afterEach(() => {
mockHandleGetWishlists.mockReset();
});
it('should load wishlists', async () => {
expect(mockHandleGetWishlists).toBeCalled();
mockHandleGetWishlists()
.then((res) => res.json())
.then(console.log);
expect(component.exists(Accordion)).toBeTruthy();
});
});
//client.jsconst
client = {
//...
handleGetWishlists: () =>
fetch('http://localhost:3001/wishlist', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}),
//...
};
module.exports = client;
The variable wishlists will contain the updated value only in the next run of useEffect(), not yet in the same run, so console.log('WISHLIST AFTER SET', wishlists) still shows the old value.
Your useEffect is (intentionally) only called once, so you can not console.log the updated wishlist inside the useEffect. You just never have it there.
However, I would expect your wishlists.map() should correctly use the updated wishlists, assuming that the data returned from handleGetWishlists() is correct.
If you want to console.log the updated wishlists, you need to use a 2nd useEffect(), like this:
useEffect(() => {
console.log( 'WISHLIST AFTER SET', wishlists );
}, [ wishlists ]);
When setWishlist is used as a callback in the promise chain, its context changes from your react component state to that of the Promise itself. Calling setWishlist directly from the handler should set wishlist as expected.
handleGetWishlists()
.then((res) => res.json())
.then((data) => {
console.log('DATA BEFORE SET', data); //--> DATA BEFORE SET [...somedata...]
setWishlist(data);
})

Categories