I have axios making a get request to fetch a request a list of vessels and their information,
i am trying to use redux slice, and populate the data using dispute, however the state is always empty dispute not having any errors
the vesselSlice :
import { createSlice } from "#reduxjs/toolkit";
import { api } from "../components/pages/screens/HomeScreen";
const vesselSlice = createSlice({
name: "vessels",
initialState: {
vessels: [],
},
reducers: {
getVessels: (state, action) => {
state.vessels = action.payload;
},
},
});
export const vesselReducer = vesselSlice.reducer;
const { getVessels } = vesselSlice.actions;
export const fetchVessels = () => async (dispatch) => {
try {
await api
.get("/vessels")
.then((response) => dispatch(getVessels(response.data)));
} catch (e) {
return console.error(e.message);
}
};
the HomeScreen.js :
import React, { useEffect } from "react";
import VesselCard from "../../VesselCard";
import axios from "axios";
import { useDispatch, useSelector } from "react-redux";
import { fetchVessels } from "../../../features/vesselSlice";
export const api = () => {
axios
.get("http://127.0.0.1:8000/api/vessels/info")
.then((data) => console.log(data.data))
.catch((error) => console.log(error));
};
function HomeScreen() {
const { vessels, isLoading } = useSelector((state) => state.vessels);
const dispatch = useDispatch();
// This part:
useEffect(() => {
fetchVessels(dispatch);
}, [dispatch]);
return (
<div>
Fleet vessels :
<div className="fleet-vessels-info">
{vessels.map((vessel) => (
<VesselCard vessel={vessel} />
))}
</div>
</div>
);
}
export default HomeScreen;
You receive dispatch this way. It is not being received from any middleware.
export const fetchVessels = async (dispatch) =>{}
If you want to continue using your approach, call function this way
useEffect(() => {
fetchVessels()(dispatch);
}, [dispatch]);
Api function error
export const api = async () => {
try {
const res = await axios.get("http://127.0.0.1:8000/api/vessels/info");
return res.data;
} catch (error) {
console.log(error);
}
};
export const fetchVessels = () => async (dispatch) => {
try {
await api()
.then((data) => dispatch(getVessels(data)));
} catch (e) {
return console.error(e.message);
}
};
Related
I have a problem when I want to log in to the login by entering the email and password. What happens is that when I enter with the correct email and correct password, the animation appears but it stays cycled, and if I refresh the page and try again, now it lets me enter into the application
Here's my login form code:
import axios from "axios";
import { useRef, useState } from "react";
import { storeToken } from "../utils/authServices";
import { useNavigate } from "react-router-dom";
import { useLoading } from "../context/hooks/useLoading";
import { LoginForm } from "../components";
export const Login = () => {
const API_URL = "https://api.app"; //I hide the API for security reasons
const { run } = useLoading();
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const correoRef = useRef("");
const passwordRef = useRef("");
const handleSubmit = async (e) => {
e.preventDefault();
const { value: correo } = correoRef.current;
const { value: password } = passwordRef.current;
await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
})
.then((response) => {
storeToken(response.data.token);
run();
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
})
.catch((err) => {
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
});
};
return (
<LoginForm
correoRef={correoRef}
passwordRef={passwordRef}
handleSubmit={handleSubmit}
error={error}
errorMessage={errorMessage}
/>
);
};
import { createContext, useReducer, useContext } from "react";
const initialState = {
loading: false,
alerts: [],
};
const reducers = (state, action) => {
switch (action.type) {
case "LOADING_RUN":
return {
...state,
loading: true,
};
case "LOADING_STOP":
return {
...state,
loading: false,
};
default:
return { ...state };
}
};
const AppContext = createContext();
const AppContextProvider = (props) => {
const [state, dispatch] = useReducer(reducers, initialState);
return <AppContext.Provider value={{ state, dispatch }} {...props} />;
};
const useAppContext = () => useContext(AppContext);
export { AppContextProvider, useAppContext };
import { useMemo } from "react";
import { useAppContext } from "../AppContext";
export const useLoading = () => {
const { dispatch } = useAppContext();
const loading = useMemo(
() => ({
run: () => dispatch({ type: "LOADING_RUN" }),
stop: () => dispatch({ type: "LOADING_STOP" }),
}),
[dispatch]
);
return loading;
};
import jwt_decode from "jwt-decode";
export const storeToken = (token) => {
localStorage.setItem("token", token);
};
export const getToken = (decode = false) => {
const token = localStorage.getItem("token");
if (decode) {
const decoded = jwt_decode(token);
return decoded;
}
return token;
};
export const logout = () => {
localStorage.removeItem("token");
};
How can I log in without refreshing the page?
There's two problems here. One is you're using await with a .then .catch block. Pick one or the other. You're also never calling the stop() dispatch when your async call is complete which appears to be responsible for removing the loader.
Instead of:
const { run } = useLoading();
Use:
const { run, stop } = useLoading();
Then change this:
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
To this:
setTimeout(() => {
navigate("/nueva-solicitud");
stop();
}, 1000);
Although I would just recommend writing the entire promise like this:
try {
run();
const response = await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
});
storeToken(response.data.token);
navigate("/nueva-solicitud");
stop();
} catch (err) {
stop();
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
}
I have this custom http hook with abort when you try to go to a different page (I saw it in a tutorial but I am not truly sure I need it). When I fetch data with it and useEffect(), I have this error on the backend but the request is executed and everything is as planned. My question is, how to improve my code so it does not throw this error and do I need this functionality with abortController() ?
http-hook.ts
import { useCallback, useRef, useEffect } from "react";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { selectError, showError } from "src/redux/error";
import { selectLoading, startLoading, stopLoading } from "src/redux/loading";
export const useHttpClient = () => {
const dispatch = useDispatch();
const error = useSelector(selectError);
const loading = useSelector(selectLoading);
const activeHttpRequests: any = useRef([]);
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
dispatch(startLoading());
const httpAbortCtrl = new AbortController();
activeHttpRequests.current.push(httpAbortCtrl);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: httpAbortCtrl.signal,
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortCtrl
);
if (!response.ok) {
throw new Error(responseData.message);
}
dispatch(stopLoading());
return responseData;
} catch (err) {
dispatch(showError(err.message));
dispatch(stopLoading());
throw err;
}
},
[]
);
useEffect(() => {
return () => {
activeHttpRequests.current.forEach((abortCtrl: any) => abortCtrl.abort());
};
}, []);
return { loading, error, sendRequest };
};
UserInfo.tsx
import React, { Fragment, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useHttpClient } from "src/hooks/http-hook";
import classes from "./UserInfo.module.css";
const UserInfo = () => {
const { sendRequest } = useHttpClient();
const [currentUser, setCurrentUser] = useState<any>();
const userId = useParams<any>().userId;
useEffect(() => {
const fetchCurrentUser = async () => {
try {
const responseData = await sendRequest(
`http://localhost:5000/api/user/${userId}`
);
setCurrentUser(responseData.user);
console.log("currentUser ", currentUser);
} catch (err) {
console.log(err);
}
};
fetchCurrentUser();
}, [sendRequest ,userId]);
return currentUser ? (
<Fragment>
<div className={classes.cover} />
<div className={classes.user_info}>
<img
alt="user_img"
src={`http://localhost:5000/${currentUser.image}`}
className={classes.user_img}
/>
<div className={classes.text}>
<p>
Name: {currentUser.name} {currentUser.surname}
</p>
<p>Email: {currentUser.email}</p>
<p>Age: {currentUser.age}</p>
</div>
</div>{" "}
</Fragment>
) : (
<p>No current user</p>
);
};
export default UserInfo;
Backend
getCurrentUser.ts controller
const getCurrentUser = async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const userId = req.params.userId;
let user;
try {
user = await User.findById(userId);
} catch (err) {
const error = new HttpError("Could not fetch user", 500);
return next(error);
}
res.json({ user: user.toObject({ getters: true }) });
};
I am experiencing the below error when I introduce useQueryClient? Any ideas why this may be?
I am trying to invalidateQueries for a queryKey onSuccess of the useUpdateEmployee hook.
bundle.js:1427 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
Component
import { useFetchEmployee, useUpdateEmployee } from '../Users/Usershooks';
const User = () => {
const userData = {
name: 'test'
};
const { data } = useFetchEmployee(userID);
const { mutate } = useUpdateEmployee(userID, userData);
const saveChangesOnClick = () => {
mutate();
};
return (
<div>
...
</div>
);
};
export default User;
HookFile
import axios from 'axios';
import { useMutation, useQuery, useQueryClient } from 'react-query';
const queryClient = useQueryClient();
export const useFetchEmployers = () => useQuery(['fetchEmployers'], () => axios.get('https://jsonplaceholder.typicode.com/users')
.then(response => response.data));
export const useFetchEmployee = (userID: any) => useQuery(['fetchEmployers', userID], () => axios.get(`https://jsonplaceholder.typicode.com/users/${userID}`)
.then(response => response.data));
export const useUpdateEmployee = (userID: any, userData: any) => useMutation(
() => axios.put(`https://jsonplaceholder.typicode.com/users/${userID}`, userData)
.then(response => response.data),
{
onSuccess: () => {
console.log("success");
queryClient.invalidateQueries(['fetchEmployers']);
}
}
);
useQueryClient is a hook, it has to be initialized in a React component or in a custom hook. Just move it inside the useUpdateEmployee.
export const useUpdateEmployee = (userID: any, userData: any) => {
const queryClient = useQueryClient();
return useMutation(
...,
onSuccess: () => {
queryClient.invalidateQueries(['fetchEmployers']);
}
);
};
Im trying to implement debouncing with my own pure js function for calling the action in action file with axios POST request.
The below code in the input box component
import React, { useState, useCallback } from 'react'
import { searchDrug } from '../../actions/drug-interaction'
function CustomInputSearch(props) {
const { handleSelectInput } = props
const apiCall = (value) => {
searchDrug(value)
}
const debounce = (apiFunc, delay) => {
let inDebounce
return function () {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => apiFunc.apply(context, args), delay)
}
}
const optimizedVersion = debounce(apiCall, 500)
const handleChange = (e) => {
optimizedVersion(e.target.value)
}
return (
<div>
<input
className='form-control'
placeholder='Search Drug...'
onKeyUp={handleChange}
/>
</div>
)
}
export default CustomInputSearch
Ignore the unnececssary imports.
The below code is the action file.
export const searchDrug = (drug) => {
const params = {
"start": drug,
"limit": 100
}
let axiosConfig = {
headers: {
// 'Access-Control-Allow-Origin': '*'
}
}
return (dispatch) => {
dispatch({ type: 'DRUG_LIST_NOTIFY', payload: { drugListLoading: true } })
axios.post(`${API_URL}/drug/autocomplete`, params, axiosConfig)
.then((response) => {
dispatch({
type: 'DRUG_LIST',
payload: { response: response.data, drugListLoading: false }
})
})
.catch((error) => {
dispatch({ type: 'DRUG_LIST_NOTIFY', payload: { drugListLoading: false } })
if (error.response && error.response.status === 401) {
window.open('/?src=auth-error', '_self')
}
});
};
}
But im not seeing any request going in network tab in browser.Im also composedWithDevtools in redux store.Thanks in advance
It is because your searchDrug action must be came from dispatch instead of calling it directly.
import React, { useState, useCallback } from 'react'
import { useDispatch } from 'react-redux'
import { searchDrug } from '../../actions/drug-interaction'
function CustomInputSearch(props) {
const { handleSelectInput } = props
const dispatch = useDispatch()
const apiCall = (value) => {
dispatch(searchDrug(value))
}
...
I am trying to fetch some data from a firebase collection, however I cannot seem to get it to work properly.
My code is as follows:-
component:-
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { fetchPlayers } from '../../redux/players/players.actions';
class Players extends Component {
componentDidMount() {
this.props.fetchPlayers();
}
render() {
const { players } = this.props;
// const playerItems = players.map(player => (
// <div key={player.id}>
// <h3>{player.name}</h3>
// </div>
// ));
return (
<div>
<h1>PLAYERS</h1>
{/* {playerItems} */}
</div>
)
}
}
const mapStateToProps = state => ({
players: state.players.items,
newPlayer: state.players.item
});
export default connect(mapStateToProps, {fetchPlayers})(Players);
My actions file looks like this :-
import { FETCH_PLAYERS, NEW_PLAYER } from "./players.types";
import { getPlayers, addPlayer } from '../../db/firebase/players.firebase';
// this syntax below will return the dispatch (to the reducer)
export const fetchPlayers = () => dispatch => {
const players = getPlayers();
dispatch({
type: FETCH_PLAYERS,
payload: players
});
};
export const createPlayer = playerToAdd => dispatch => {
addPlayer(playerToAdd);
dispatch({
type: NEW_PLAYER,
payload: playerToAdd
});
};
And my db function is as follows :-
import firebase from "../../config/firebase";
const db = firebase.firestore();
export const getPlayers = () => {
const players = fetchData();
return players;
}
export const addPlayer = (playerToAdd) => {
db.collection("players").add({ ...playerToAdd });
}
const fetchData = async () => {
let playerArr = [];
await db.collection("players")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
playerArr = playerArr.concat(doc.data());
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
return playerArr;
}
My problem is that in my component, the players remains as a promise, and the fetchData() db call is being returned after.
How can I await the array to be populate first (in the fetchData()) and then pass this to my component through the actions?
Thanks for your help, very much appreciated!
I have managed to fix it in this way, not sure if this is the correct way though, still fairly new to Redux:-
component:-
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { fetchPlayers } from '../../redux/players/players.actions';
class Players extends Component {
componentDidMount() {
this.props.fetchPlayers();
}
render() {
const {players} = this.props;
let playerItems = ''
if (players) {
playerItems = players.map(player => (
<div key={player.id}>
<h3>{player.name}</h3>
</div>
));
}
return (
<div>
<h1>PLAYERS</h1>
{playerItems}
</div>
)
}
}
const mapStateToProps = state => ({
players: state.players.items,
newPlayer: state.players.item
});
export default connect(mapStateToProps, {fetchPlayers})(Players);
actions:-
import { FETCH_PLAYERS, NEW_PLAYER } from "./players.types";
import { getPlayers, addPlayer } from '../../db/firebase/players.firebase';
// this syntax below will return the dispatch (to the reducer)
export const fetchPlayers = () => async dispatch => {
const players = await getPlayers();
dispatch({
type: FETCH_PLAYERS,
payload: players
});
};
export const createPlayer = playerToAdd => dispatch => {
addPlayer(playerToAdd);
dispatch({
type: NEW_PLAYER,
payload: playerToAdd
});
};
firebase:-
import firebase from "../../config/firebase";
const db = firebase.firestore();
export const getPlayers = async () => {
const players = await fetchData();
return players;
}
export const addPlayer = (playerToAdd) => {
db.collection("players").add({ ...playerToAdd });
}
const fetchData = async () => {
let playerArr = [];
await db.collection("players")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
playerArr = playerArr.concat(doc.data());
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
return playerArr;
}
In this way its working however if you have any suggestions, more than welcome to listen to.
Thanks