I’m building a Django-React application (Django API on its standalone server on port 8000 and React front-end on its standalone server on port 3000) using axios to fetch data from the Django server and Redux to dispatch the data. So, I created the redux action like this:
// GET TRANSACTIONS: /action/transactions.js
import axios from 'axios';
import { GET_TRANSACTIONS } from './types';
export const getTransactions = () => (dispatch) => {
axios.get('http://127.0.0.1:8000/api/transaction')
.then((res) => {
dispatch({
type: GET_TRANSACTIONS,
payload: res.data,
});
}).catch((err) => console.log(err));
};
And in the the React view to get the data from redux store I have this:
// React Views :
import React, {Component,} from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getTransactions } from '../../../../actions/transactions';
import './transfert.css';
const initialState = {
transactions: [],
};
const propTypes = {
transactions: PropTypes.array.isRequired,
};
class Transfert extends Component {
componentDidMount() {
getTransactions();
}
render() {
return (
<div className="table">
</div>
);
}
}
const mapStateToProps = (state) => ({
transactions: state.transactions.transactions,
});
export default connect(mapStateToProps, { getTransactions })(Transfert);
The problem is that I’m not getting the data from the Django server in the reduxdevtools. It displays nothing, only says: type(pin):"##INIT".
Whereas I should be getting all the transactions from there.
Checking the Django server, I found that the Django console says:
code 400, message Bad request version
('\x9bú|$Lª\x01Þù\x83!ów·È\x803<Æ\x00"ZZ\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x00')
[27/Jan/2022 15:56:59] You're accessing the development server over
HTTPS, but it only supports HTTP.
And I thought of setting the SECURE_SSL_REDIRECT to false in the Django setting. So, I set it there like this:
SECURE_SSL_REDIRECT=False SESSION_COOKIE_SECURE=False CSRF_COOKIE_SECURE=False
But to not avail, I’m still not hitting the right point. All my console logging rendered no result, and I’m even more confused, not knowing whether the problem is in the axios call: axios.get('http://127.0.0.1:8000/api/transaction')
Or from the getTransaction() whitin the React Transaction.js (Rect View). Or is there anything with the
const mapStateToProps = (state) => ({
transactions: state.transactions.transactions,
});
Or with the export default connect(mapStateToProps, { getTransactions })(Transfert);
NOTE: In the transaction.jsx view I earlier called:
componentDidMount() {
this.props.getTransactions();
}
With the this.props but the error says: “Must use destructuring props assignment”
That is why I changed it to current one:
componentDidMount() {
getTransactions();
}
Any help is very much appreciated. Here I’m seriously stuck.
After struggling with this issue throughout this couple of weeks, I found the answer. First of all the “Cors header” mentioned by #Derek S, which I ignored in the beginning was mandatory. But after installing and configuring it, the issue was persisting because of some other misunderstandings which I got to have a good grasp of now.
I was taking example on a tutorial in which several redux folders were created for each of the redux steps: Reducer folder – actions folder– and a store.js file separated. So the main redux action inside my react component was the getTransaction() function declared as:
import axios from 'axios';
import { GET_TRANSACTIONS } from './types';
export const getTransactions = () => (dispatch) => {
axios.get('http://127.0.0.1:8000/api/transaction')
.then((res) => {
dispatch({
type: GET_TRANSACTIONS,
payload: res.data,
});
}).catch((err) => console.log(err));
};
};
The insanity in this is that I was using redux inside a class based
component and the getTransactions() function was not triggered, hence
nothing was dispatched to the redux store.
That was the issue number one. So instead of creation the
getTransaction() inside a separated redux actions folder, I threw it’s
content straight inside the mapDispatchToProps of the target class
componentlike this:
const mapDispatchToProps = () => (dispatch) => {
axios.get('http://127.0.0.1:8000/api/transaction')
.then((res) => {
dispatch({
type: GET_TRANSACTIONS,
payload: res.data,
});
console.log(res.data);
}).catch((err) => console.log(err));
};
And afterward instead of
export default connect(mapStateToProps, { getTransactions })(Transfert);
I rather did
export default connect(mapStateToProps, mapDispatchToProps)(Transfert);
And the redux devtools beautifully displayed the transactions grabbed from API: result is
Another misunderstanding was to be following other people’s
suggestions to configure the cors header for Django, and they were
suggesting to add const cors = require("cors"); and to have:
export const getTransactions = () => (dispatch) => {
axios({
method: 'get',
url: ‘http://127.0.0.1:8000/api/transaction’
withCredentials: false,
content-type: text/plain
params: {
access_token: SECRET_TOKEN,
},
});
};
But this was just another huge pain in the ass, since cors header should only be configured in the backend (the server side) in my case the django API, and not inside the axios' call. So, I got rid of all those suggestions from the react frontend app and kept the component as simple as possible:
import React, {Component,} from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import { GET_TRANSACTIONS } from '../../../../actions/types';
import PropTypes from 'prop-types';
import './transfert.css';
const initialState = {
transactions: [],
};
const propTypes = {
transactions: PropTypes.array.isRequired,
};
class Transfert extends Component {
render() {
return (
<div className="table">
</div>
);
}
}
const mapStateToProps = (state) => ({
transactions: state.transactions.transactions,
});
const mapDispatchToProps = () => (dispatch) => {
axios.get('http://127.0.0.1:8000/api/transaction')
.then((res) => {
dispatch({
type: GET_TRANSACTIONS,
payload: res.data,
});
console.log(res.data);
}).catch((err) => console.log(err));
};
const connectToStore = connect(mapStateToProps, mapDispatchToProps);
const ConnectedComponent = connectToStore(Transfert);
export default connect(mapStateToProps, mapDispatchToProps)(Transfert);
And now it works fine as the API is able to forward the data:
To me this looks like a problem with your Django server. The error indicates that you are trying to use https to access the api which only supports http, but according to your code you are using http.
Open the debugger in your browser, what does the network traffic look like when the call is made? Is any error returned? Does a prefetch (OPTION) request occur and return OK?
If it is rejected before the actual request is called then there's a configuration issue somewhere. Likely CORS or https instead http.
Related
I'm doing an API call to my server using Redux, but I'm unsure what the "best practices" are for doing so.
When simply using React, one common way of doing API calls is that you create a folder called for example hooks, where you create your own custom hooks to make the API calls that you want.
However, when it comes to using Redux with React, it is not really clear to me what the best method of structuring your client and API calls are, in order to make it more readable and easy to scale in the future.
Questions
So, what are the best practises for making API calls with Redux in React to increase readability and scaleability?
Second question, what are the best practises to use API calls in a functional component in React with React Redux?
Here below are the current structure of a project, where I make a simple GET request using Redux to the server, which respons with the JSON Placeholder /post.
Starting off with the file structure, to give a better understanding of where everything is located.
Files related to the API call
index.js (root)
import App from "./App";
import reducers from "./reducers";
ReactDOM.render(
<Provider store={createStore(reducers, applyMiddleware(thunk))}>
<App />
</Provider>,
document.querySelector("#root")
);
index.js (reducers)
import postReducer from "./postReducer";
export default combineReducers({
posts: postReducer,
});
postReducer.js (reducers)
import _ from "lodash";
import { GET_POSTS } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "id") };
default:
return state;
}
}
export default postReducer;
requestPosts.js (apis)
import axios from "axios";
const API_URL = "http://localhost:8000";
async function httpGetAllPosts() {
const response = await axios.get(`${API_URL}/posts`);
return response.data;
}
export { httpGetAllPosts};
types.js (actions)
export const GET_POSTS = "GET_POSTS";
index.js (actions)
import { httpGetAllPosts } from "../apis/requestPosts";
import { GET_POSTS } from "./types";
const getAllPosts = () => async (dispatch) => {
const response = await httpGetAllPosts();
dispatch({ type: GET_POSTS, payload: response });
};
export { getAllPosts };
Moving on to the second question. What are the best practises to use API calls in a functional component in React with React Redux?
Here is how I used it in a functional component. Is this the ideal way of doing it? I think the useEffect looks a bit weird, when all it does is call a function and has a dependancy related to that function.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getAllPosts } from "../actions";
import { Card } from "react-bootstrap";
const Posts = ({ getAllPosts, posts }) => {
useEffect(() => {
getAllPosts();
}, [getAllPosts]);
return (
<div>
{posts.map((post) => {
return (
<Card style={{ width: "45rem", marginBottom: "1rem" }} key={post.id}>
<Card.Body>
<Card.Title>{post.title}</Card.Title>
<Card.Text>{post.body}</Card.Text>
</Card.Body>
</Card>
);
})}
</div>
);
};
const mapStateToProps = (state, ownProps) => {
return { posts: Object.values(state.posts) };
};
export default connect(mapStateToProps, { getAllPosts })(Posts);
In next.js We can request to api in getServerSideProps and pass data to page through props like this :
method 1: (next default approach)
const Page: NextPage = ({page}) => {
return <div>{JSON.stringify(data)}</div>;
};
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
I work in large scale project So I want to manage all server and client request only using thunk and then store it to redux and use in page like this:
method 2: (with next-redux-wrapper)
import React from 'react';
import {NextPage} from 'next';
import {useSelector} from 'react-redux';
import {wrapper, State, fetchData } from '../store';
const Page: NextPage = () => {
const {data} = useSelector(state => state);
return <div>{JSON.stringify(data)}</div>;
};
export const getServerSideProps = wrapper.getServerSideProps(store => ({req, res, ...etc}) => {
store.dispatch(fetchData());
});
export default Page;
I want to know which is better approach ?
Is it anti-pattern to use second approach to store all pre-rendered data and client side state of app in redux?
I am using React Testing Library to test some features of my website and I am facing issues with the initialization of the Redux store while running tests. The following is the initialState:
const initialState = {
isAuth: false,
loading: localStorage.getItem("authToken") ? true : false,
error: false,
user: {
username: "",
},
};
I have set up the tests with the following code to wrap everything around the Provider:
import { FC, ReactElement } from "react";
import { render, RenderOptions } from "#testing-library/react";
import { Provider } from "react-redux";
import { store } from "../store/store";
const AllTheProviders: FC = ({ children }) => {
return <Provider store={store}>{children}</Provider>;
};
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, "wrapper">
) => render(ui, { wrapper: AllTheProviders, ...options });
export * from "#testing-library/react";
export { customRender as render };
Then, in the actual test I use beforeAll(() => window.localStorage.setItem("authToken", "mockToken") to set the token in localStorage. Based on the value of the loading state the Login component should be rendered on my website, but I'm always getting false as the value of loading.
import { render, screen, waitFor } from "../utils/test-utils";
import App from "../App";
beforeAll(() => window.localStorage.setItem("authToken", "MockAuthToken"));
test("Login page not rendered if a valid auth token is present", async () => {
render(<App />);
//this is to check that the Login component is not rendered
await waitFor(() => expect(screen.queryByText("Sign in")).toBeNull());
await waitFor(() => expect(screen.getByRole("navigation")).toBeDefined(), {
timeout: 5000,
});
});
Is this happening because the Redux store is created before the setItem function execution during the tests? While in the browser the token is already there when I enter the website and so the intended behavior is not resembled by the test.
That's correct. We have an open issue asking us to add a "lazy initialization function" overload for initialState in createSlice:
https://github.com/reduxjs/redux-toolkit/issues/1024
I was actually working on trying to implement this last night and have a PR up, but there's some concerns about changes in the semantics of behavior:
https://github.com/reduxjs/redux-toolkit/pull/1662
I'll look at this some more tonight and see what we can come up with.
I am using redux with redux-thunk middleware. The function in question makes a GET request to an API and upon response (.then()) dispatches the res to my redux store via an action.
For some reason when I pass dispatch to the parent function the function never runs. When I remove dispatch the parent function does run...(???) I have multiple other components within the same app that follow this exact same pattern successfully. For some reason this particular component is behaving in this strange way although i've triple checked and the boilerplate is all the same.
Here is my store.jsx:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from '../reducers/root_reducer'
const configureStore = (preloadedState = {}) =>
createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk, logger)
);
export default configureStore;
my actions my_team_actions.js:
import * as APIUtil from '../util/api/my_team_api_util';
export const RECEIVE_ORG_SURVEY = "RECEIVE_ORG_SURVEY"
export const receiveOrgSurvey = survey => ({
type: RECEIVE_ORG_SURVEY,
survey
});
export const getOrganizationSurvey = () => dispatch => {
debugger
APIUtil.getOrgSurvey()
.then((res) => {
debugger
dispatch(receiveOrgSurvey(res))
})
.catch(err => console.log(err))
}
my API call my_team_api_util.js:
import axios from 'axios';
export const getOrgSurvey = () => {
return axios.get(`/api/mongo/organizations/test`)
}
component container my_team_container.jsx:
import { connect } from 'react-redux';
import MyTeam from './my_team';
import { getOrganizationSurvey } from '../../actions/my_team_actions';
const mSTP = state => {
return {
user: state.session.user,
};
};
const mDTP = dispatch => {
return {
getSurvey: () => getOrganizationSurvey(),
};
};
export default connect(mSTP, mDTP)(MyTeam);
component my_team.jsx:
import React from 'react';
class MyTeam extends React.Component {
constructor(props) {
super(props)
this.createTeam = this.createTeam.bind(this);
}
createTeam() {
this.props.getSurvey();
}
render() {
return (
<div className="my-team-frame frame">
<div className="my-team-container">
<div className="contact-data-container">
<div className="contact-data-header header">Contact a Data Scientist</div>
</div>
<div className="myteam" onClick={this.createTeam}>BUTTON</div>
</div>
</div>
)
}
}
export default MyTeam;
On the client side the my_team component renders fine and when I click the button which calls the function which will eventually dispatch my action it only seems to run when dispatch is NOT included in getOrganizationSurvey() in my_team_actions.js i.e. I hit both debuggers (and the second one with a correct res object). When dispatch is included (as shown in the snippet above) I don't hit either debuggers nor are any errors thrown.
I'm really scratching my head on this, any input is appreciated!
Thanks,
Ara
God I am a moron... XD
I said I triple checked... I should have checked 4 times! The issue was in my components container my_team_container.jsx I simply forgot to pass dispatch in the map dispatch to props object!
I fixed it by adding dispatch to the getSurvey callback...
my_team_container.jsx
import { connect } from 'react-redux';
import MyTeam from './my_team';
import { getOrganizationSurvey } from '../../actions/my_team_actions';
const mSTP = state => {
return {
user: state.session.user,
};
};
const mDTP = dispatch => {
return {
getSurvey: () => dispatch(getOrganizationSurvey()),
};
};
export default connect(mSTP, mDTP)(MyTeam);
it's funny how you can spend 2 hours on a problem, think it's hopeless and then as soon as you ask for help take another look at it and the solution just stares right back at you 😂
I started learning React about 3 or 4 months ago. Initially went through Codecademy's course, and have since polished up a lot with other courses and tutorials, the main of which used Axios for get requests without really explaining Axios. I decided to go back to the "Ravenous" yelp clone project at Codecademy, the instructions of which are for class components, and thought it would be a nice challenge to complete that same project but with functional components. Everything was perfect until it was time to wire up the Yelp api, and given my lack of knowledge with axios and requests in general, I can't pinpoint what I'm doing wrong. I'm almost certain that I'm doing something wrong in Yelp.js, but I can't be sure because I'm getting a type error that "fetchRestaurants" is not a function in App.js, no matter which way I call it. I think it involves Promises, and I tried using async/await on searchYelp, but this part is all new to me.
Here's Yelp.js:
import axios from "axios";
const KEY = "RwNYGeKdhbfM1bo6O8X04nLNR7sKjIXyAQ-Fo-nz-UdNHtSKDAmHZCc9MSiMAVmNhwKrfcukZ0qlHF1TdVIRSrgak3-oHVNmGD5JPR4ysxhfGd1hgOllt83H1i44X3Yx";
const fetchRestaurants = (term, location, sortBy) => { const corsApiUrl = "https://cors-anywhere.herokuapp.com/"; return () => {
axios
.get(
`${corsApiUrl}https://api.yelp.com/v3/businesses/search?categories=restaurants&location=${location}&term=${term}&sort_by=${sortBy}`,
{
headers: {
Authorization: `Bearer ${KEY}`,
},
}
)
.then(res => res.data.businesses)
.catch(error => console.log(error.response)); }; };
export default fetchRestaurants;
And here's App.js:
import React, { useState } from "react";
import BusinessList from "./BusinessList";
import SearchBar from "./SearchBar";
import Yelp from "./Yelp";
import "./App.css";
const App = props => {
const [businesses, setBusinesses] = useState([]);
const searchYelp = (term, location, sortBy) => {
const businessList = Yelp.fetchRestaurants(term, location, sortBy);
setBusinesses(businessList);
};
return (
<div className="App">
<h1>Ravenous</h1>
<SearchBar searchYelp={searchYelp} />
<BusinessList businesses={businesses} />
</div>
);
};
export default App;
Apologies in advance if this was supposed to be posted somewhere else. Noob probs.
you did not save the response anywhere. .then(res => res.data.businesses) here the response should've been saved somewhere, either a varibale or state. also your function responseble for the axios request doesn't return anything.
It's not an issue with axios itself, but how you use it in your React application.
Yelp.js: fetchRestaurants() should return a proper value (promise in this case) :
const fetchRestaurants = (term, location, sortBy) => {
return axios
.get(url, options)
.then(res => res.data.businesses)
.catch(...)
}
App.js: All API calls should be done using useEffect hook:
useEffect(() => {
Yelp.fetchRestaurants(term, location, sortBy)
.then(businessList => setBusinesses(businessList));
}, [term, location, sortBy]);