I'm trying the get the user current location in my app, but even if I can see it when I console.log it it doesn't work.
I'm using an async function in order to retrieve it but I must be doing something wrong and I cannot figure out what the issue is.
ContextState
import React, { useReducer } from "react";
import RestContext from "./restContext";
import RestReducer from "./restReducer";
import Yelp from "../../Util/Yelp";
import { getCurrentPosition } from "../../Util/GeoLocation";
import {
GET_RESTAURANTS,
GET_INFO_RESTAURANT,
CLEAR_SEARCH,
SET_LOADING,
GET_LOCATION,
} from "../../types";
const RestState = (props) => {
const initalState = {
restaurants: [],
restaurant: {},
loading: false,
location: {},
};
const [state, dispatch] = useReducer(RestReducer, initalState);
// Get Restaurants
const getRestaurants = async (text) => {
setLoading();
let restaurants = await Yelp.searchRestaurants(text);
if (restaurants) {
dispatch({ type: GET_RESTAURANTS, payload: restaurants });
} else {
dispatch({ type: GET_RESTAURANTS, payload: [] });
}
};
// Get info Restaurants
const getRestaurantInfo = async (id) => {
setLoading();
let restaurant = await Yelp.searchRestaurantsInfo(id);
if (restaurant) {
dispatch({ type: GET_INFO_RESTAURANT, payload: restaurant });
} else {
dispatch({ type: GET_INFO_RESTAURANT, payload: {} });
}
};
// Clear search
const clearSearch = () => dispatch({ type: CLEAR_SEARCH });
// Set loading
const setLoading = () => dispatch({ type: SET_LOADING });
// Get location
const fetchCoordinates = async () => {
try {
const coords = await getCurrentPosition();
dispatch({ type: GET_LOCATION, payload: coords });
} catch (error) {
// Handle error
console.error(error);
}
}
return (
<RestContext.Provider
value={{
restaurants: state.restaurants,
restaurant: state.restaurant,
loading: state.loading,
getRestaurants,
clearSearch,
getRestaurantInfo,
fetchCoordinates,
}}
>
{props.children}
</RestContext.Provider>
);
};
export default RestState;
It's reducer
import {
GET_RESTAURANTS,
GET_INFO_RESTAURANT,
CLEAR_SEARCH,
SET_LOADING,
GET_LOCATION,
} from "../../types";
export default (state, action) => {
switch (action.type) {
case GET_RESTAURANTS:
return { ...state, restaurants: action.payload, loading: false };
case GET_INFO_RESTAURANT:
return { ...state, restaurant: action.payload, loading: false };
case CLEAR_SEARCH:
return { ...state, restaurants: [], loading: false };
case SET_LOADING:
return {
...state,
loading: true,
};
case GET_LOCATION:
return { ...state, location: action.payload };
default:
return state;
}
};
And the Home page when it's should be used
import React, { Fragment, useEffect, useContext } from "react";
import Search from "../../Components/restaurants/Search";
import Alert from "../../Components/layout/Alert";
import Navbar from "../../Components/layout/Navbar";
import DisplayRestaurants from "../../Components/layout/DisplayRestaurants";
import Footer from "../../Components/layout/Footer";
import { Waypoint } from "react-waypoint";
import RestContext from "../context/restaurant/restContext";
const Home = () => {
const restContext = useContext(RestContext);
useEffect(() => {
restContext.fetchCoordinates();
// eslint-disable-next-line
}, []);
const handleWaypointEnter = () => {
document.querySelector("nav").classList.remove("fixed");
};
const handleWaypointLeave = () => {
document.querySelector("nav").classList.add("fixed");
};
return (
<section className="main-home">
<Fragment>
<Navbar />
<Search />
<Alert />
<Waypoint onEnter={handleWaypointEnter} onLeave={handleWaypointLeave} />
<DisplayRestaurants />
<Footer />
</Fragment>
</section>
);
};
export default Home;
getCurrentPosition
export function getCurrentPosition(options = {}) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
}
coord obj
GeolocationCoordinates {latitude: 52.3555177, longitude: -1.1743196999999999, altitude: null, accuracy: 372529, altitudeAccuracy: null, …}
accuracy: 372529
altitude: null
altitudeAccuracy: null
heading: null
latitude: 52.3555177
longitude: -1.1743196999999999
speed: null
__proto__: GeolocationCoordinates
Thanks for your help
can you try this instead?
it returns a promise so in theory should be able to use .then
getCurrentPosition().then((res) => {
console.log(res) // check what `res` is
dispatch({ type: GET_LOCATION, payload: res.cords });
})
Related
I have a problem with my redux reducer.It doesn't return the expected state after dispatching the getCurrentProfile action, it returns the initial state which is "null" instead of "{}", which is fetched with an ajax request, so when the network return the result the state profile change to the result returned but when it is an error returned it stay null instead of empty object, so that is my code:
enter image description here
profileAcction.js :
import axios from 'axios';
import { GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE} from './types';
//Loading profile
const setProfileLoading = () => {
return {
type: PROFILE_LOADING
}
};
// Clear current profile
export const clearCurrentProfile = () => {
return {
type: CLEAR_CURRENT_PROFILE,
}
}
// Get current profile
export const getCurrentProfile = () => dispatch => {
dispatch(setProfileLoading());
axios.get('/api/profile')
.then(res => dispatch({
type: GET_PROFILE,
payload: res.data
})).catch(error =>
dispatch({
type: GET_PROFILE,
payload: {}
}))
};
profileReducer.js:
import {GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE} from '../actions/types';
const initialState = {
profile: null,
profiles: null,
loading: false
};
const profileReducer = (state=initialState, action) => {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case CLEAR_CURRENT_PROFILE:
return {
...state,
profile: null,
loading: false
}
default:
return state;
}
};
export default profileReducer;
i have a website where some users are localystored and stored in my mongodb, i created some buttons where when clicked they will firstly call the current state of the user to be the same as the on in the db, then after it should update the date value of the current localystored user. Whenever the button to update the date is clicked, for every user that is logged in, only the first user registered in the database is changed. I think the problem is in the backed update call or the Authcontext file but cant find a way to fix it.
File where the change should happen when function updateXMonth is called
import React, { useContext, useEffect } from "react";
import { AuthContext } from "../../context/AuthContext";
import { Navigate } from "react-router-dom";
import useFetch from "../../hooks/useFetch";
import { useState } from "react";
import Footer from "../../components/OutFooter";
import Navbar from "../../components/OutNavbar";
import Sidebar from "../../components/OutSidebar";
import {
ContractContainer,
HeadingContainer,
TypeH1,
ActiveUntil,
MonthlyWrapper,
MonthlyContainer,
MonthNumber,
Navbarback,
Desc,
TypeH2,
Subtitle,
} from "./userinfoElements";
import moment from "moment";
import axios from "axios";
import { Button } from "../../components/ButtonElements";
const Userinfo = () => {
// for nav bars
const [isOpen, setIsOpen] = useState(false);
// set state to true if false
const toggle = () => {
setIsOpen(!isOpen);
};
const { user, dispatch } = useContext(AuthContext);
const { data } = useFetch(`/contracts/${user?.contractType}`);
// useEffect(() => {
// if (user) {
// userUpdate();
// }
// });
let userUpdate = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let date = new Date().toJSON();
if (res.data.details.activeUntil < date) {
dispatch({ type: "CONTRACT_EXPIRED" });
console.log("Users contract has expired");
}
} catch (err) {
console.log(err);
}
};
if (!user) {
return <Navigate to="/" />;
}
let dateFormat = moment(user.activeUntil).format("DD/MMMM/yyyy");
const update1Month = async () => {
try {
let res = await axios.post(`/auth/${user.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(30, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update3Month = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(90, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update6Month = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(180, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update12Month = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(365, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} />
{/* navbar for smaller screens*/}
<Navbar toggle={toggle} />
<Navbarback /> {/* filling for transparent bacground navbar*/}
<>
<ContractContainer>
<TypeH1>
Hello {user.fName} {user.lName}!
</TypeH1>
<HeadingContainer>
<TypeH2>
Your contract type:{" "}
<span style={{ color: "red" }}>{data.contractType}</span>
</TypeH2>
<ActiveUntil>
Subscription active until{" "}
<span style={{ color: "red" }}>{dateFormat}</span>
</ActiveUntil>
</HeadingContainer>
<Subtitle>
Pay right now and get imediate access. The more the cheaper. If you
have a subscription already active, no days will be lost during the
proccess, they will only be added.
</Subtitle>
<MonthlyWrapper>
<MonthlyContainer>
<MonthNumber>1 Month</MonthNumber>
<Button onClick={update1Month}>{data.month1Price}$</Button>
<Desc>Pay right now to add 1 month to your subscription.</Desc>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>3 Months</MonthNumber>
<Button onClick={update3Month}>{data.month3Price}$</Button>
<Desc>Pay right now to add 3 months to your subscription.</Desc>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>6 Months</MonthNumber>
<Button onClick={update6Month}>{data.month6Price}$</Button>
<Desc>Pay right now to add 6 months to your subscription.</Desc>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>12 Months</MonthNumber>
<Button onClick={update12Month}>{data.month12Price}$</Button>
<Desc>Pay right now to add 12 months to your subscription.</Desc>
</MonthlyContainer>
</MonthlyWrapper>
</ContractContainer>
</>
<Footer />
</>
);
};
export default Userinfo;
controller file for /activedate/
import User from "../models/User.js";
export const updateActiveDate = async (req, res, next) => {
try {
await User.updateOne({
$set: { activeUntil: req.body.activeUntil },
});
res.status(200).json("Active date has been updated.");
} catch (err) {
next(err);
}
};
AuthContext file
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
expired: false,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
expired: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
expired: false,
};
case "CONTRACT_EXPIRED":
return {
user: null,
loading: false,
error: null,
expired: true,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
expired: false,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
expired: false,
};
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
//update LStorage when user state updates
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
expired: state.expired,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
I'm newbie to Reactjs. The problem I'm encountered:
When Article page loads in the first time, all is fine and there are 10 articles shown. When I click on the browser back button, and then I go to the Article page for the second time, the article-list will be duplicated (so, it will be 20 articles). If I do so again, it will be 30 articles and so on ..
I want to know, why the result of API call appends for the Redux and not replace? In other word, how can I clean the Redux on page load every time? The expected result is seeing always 10 item (articles) on the page Article when I open it.
Here is a simplified of the element (for navigating to the list of articles) in the main page:
import Pages from "Constants/Pages";
const Component = () => {
const history = useHistory();
const navigateWithToken = (page) => {
history.push(page);
}
};
return (
<div className="d-flex align-items-center flex-column py-1 ">
<div
className="main-footer-btn-article"
onClick={() => navigateWithToken(Pages.Articles)}
></div>
<span className="main-footer-btn-text">Articles</span>
</div>
)
};
export const ArticlesBtn = memo(Component);
Also, here is the Article page:
import { memo, useEffect } from "react";
import { useHistory } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import PostItems from "SharedComponents/PostItems";
import { getAllPosts } from "Redux/Actions";
import Pages from "Constants/Pages";
const Page = () => {
const posts = useSelector((state) => state?.articles?.posts?.items);
const dispatch = useDispatch();
const { push } = useHistory();
useEffect(() => {
dispatch(getAllPosts());
}, []);
const onClickPost = (item) => {
push({
pathname: Pages.SingleArticle,
state: {
postId: item.id,
title: item.subject,
is_saved: item.is_saved,
},
});
};
return (
<div className="full-height overflow-auto">
{
posts?.map((item, index) => {
return (
<PostItems
{...item}
key={item.id}
index={index}
onClickPost={() => onClickPost(item)}
/>
);
})
}
</div>
);
};
export default memo(Page);
Also here is the API call:
const getAllPosts = (page = 1) => {
return async (dispatch: ReduxDispatch) => {
//"posts?for=for_website"
dispatch(toggleLoading(true));
try {
const { data } = await axios({
method: "GET",
url: "posts?for=for_mobile",
params: { page: page },
});
const items = data?.data?.data;
const pagination = {
current_page: data.data.current_page,
last_page: data.data.last_page,
};
dispatch(
dispatchItemToRedux({
type: ReducerTypes.GET_ALL_POSTS,
payload: {
items,
pagination,
},
})
);
} catch (err) {
return Promise.reject(err);
} finally {
dispatch(toggleLoading(false));
}
};
};
Also, here is the reducer:
import ReducerTypes from "Redux/Types/ReducerTypes";
const INITIAL_STATE = {
posts: {
items: [],
pagination: {}
},
singlePost: {
id: null,
subject: null,
caption: null,
deep_link: null,
short_link: null,
total_comments: null,
total_likes: null,
total_views: null,
created: null,
medias: null,
likes: []
},
postComments: []
};
function articleReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ReducerTypes.GET_ALL_POSTS:
return {
...state,
posts: {
items: state.posts.items.concat(action.payload.items),
pagination: action.payload.pagination
}
};
case ReducerTypes.GET_SINGLE_POST:
return {
...state,
singlePost: action.payload
};
case ReducerTypes.GET_POST_COMMENTS:
return {
...state,
postComments: action.payload
};
case ReducerTypes.GET_POST_LIKES:
return {
...state,
singlePost: {
...state.singlePost,
likes: action.payload
}
};
default:
return state;
};
};
export default articleReducer;
case ReducerTypes.GET_ALL_POSTS:
return {
...state,
posts: {
items: action.payload.items,
pagination: action.payload.pagination
}
};
Try omitting that .concat()
I'm trying to check if my store is onboarded or not. for that, I'm making an API call through the redux to check it in the BE and if it's true I'll redirect it to the dashboard. I'm able to get the data successfully from BE, and on success checkIsStoreOnboardedSuccess() is called but in the reducer, the state is not updated with the CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS state in the reducer.
action.js
import * as actionTypes from './index';
import API from '../../api';
export const clearCheckIsStoreOnboarded = () => {
return {
type: actionTypes.CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING,
};
};
export const checkIsStoreOnboarded = (payload) => {
return (dispatch) => {
dispatch(checkIsStoreOnboardedInitiate());
API.getAccountSettings(payload)
.then((response) => {
checkIsStoreOnboardedSuccess(response.data);
})
.catch((err) => {
checkIsStoreOnboardedFailure(err);
});
};
};
const checkIsStoreOnboardedInitiate = () => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START,
};
};
const checkIsStoreOnboardedSuccess = (data) => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS,
data: data,
};
};
const checkIsStoreOnboardedFailure = (err) => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL,
data: err,
};
};
reducer.js
import * as actionTypes from '../actions';
const initialState = {
isLoading: true,
isError: false,
isDone: false,
data: [],
error: null,
};
const clearCheckIsStoreOnboarded = () => {
return initialState;
};
const checkIsStoreOnboardedStart = (state) => {
return { ...state, isLoading: true, error: null, isError: false };
};
const checkIsStoreOnboardedSuccess = (state, action) => {
return { ...state, data: action.data, isDone: true, isLoading: false };
};
const checkIsStoreOnboardedFailure = (state, action) => {
return { ...state, error: action.data, isLoading: false, isError: true };
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING:
return clearCheckIsStoreOnboarded();
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START:
return checkIsStoreOnboardedStart(state);
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS:
return checkIsStoreOnboardedSuccess(state, action);
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL:
return checkIsStoreOnboardedFailure(state, action);
default:
return state;
}
};
export default reducer;
actionTypes.js
export const CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING = 'CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL';
onboard.js
import React, { useState, useEffect } from 'react';
import { withCookies } from 'react-cookie';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import Crew from './Crew';
import Service from './Services';
import Address from './Address';
import { useStyles } from './css/index.css';
import Header from './header';
import Stepper from './stepper';
import { getStoreID } from '../../../utils';
import {
clearCheckIsStoreOnboarded,
checkIsStoreOnboarded,
} from '../../../store/actions/check-is-store-onboarded-for-onboarding'
import Loader from '../../../components/CircularProgressLoader';
const OnboardScreen = ({
cookies,
clearCheckIsStoreOnboarded,
checkIsStoreOnboarded,
checkIsStoreOnboardedData,
}) => {
const [step, setStep] = useState(0);
// eslint-disable-next-line no-unused-vars
const [width, isDesktop] = useWindowWitdh();
const classes = useStyles(isDesktop);
const store_id = getStoreID(cookies);
useEffect(() => {
checkIsStoreOnboarded({
store_id,
});
}, []);
useEffect(() => () => clearCheckIsStoreOnboarded(), []);
if(checkIsStoreOnboarded.isDone){
<Redirect to='/dashboard'>
}
const updateStep = () => {
const updatedStep = step + 1;
setStep(updatedStep);
};
const onboardingScreenToRender = () => {
switch (step) {
case 0:
return (
<Crew />
);
case 1:
return (
<Service />
);
case 2:
return <Address />;
}
};
return (
<div className={classes.container}>
<Header isDesktop={isDesktop} />
<div className={classes.contentOfContainer}>
<div className={classes.titleHeader}>
Onboarding
</div>
<Stepper stepNumber={step} setStepNumber={setStep} />
{checkIsStoreOnboardedData.isLoading && <Loader />}
</div>
</div>
// <OnboardLoader />
);
};
const mapStateToProps = (state, ownProps) => {
return {
...ownProps,
checkIsStoreOnboardedData: state.checkIsStoreOnboardedForOnboardingReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
checkIsStoreOnboarded: (payload) => dispatch(checkIsStoreOnboarded(payload)),
clearCheckIsStoreOnboarded: () => dispatch(clearCheckIsStoreOnboarded()),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withCookies(OnboardScreen));
You need to dispatch your actions:
export const checkIsStoreOnboarded = (payload) => {
return (dispatch) => {
dispatch(checkIsStoreOnboardedInitiate());
API.getAccountSettings(payload)
.then((response) => {
// here
dispatch(checkIsStoreOnboardedSuccess(response.data));
})
.catch((err) => {
// and here
dispatch(checkIsStoreOnboardedFailure(err)(;
});
};
};
That said: you are writing a very outdated style of Redux here - in modern Redux, all of that would probably be possible with 1/4 of the code. If you are just learning Redux, you are probably following a very outdated tutorial. Modern Redux does not require you to write action type strings or action creators and your reducers can contain mutable logic. Also, it does not use connect unless you are working with legacy class components (which you don't seem to be doing).
I really recommend you to read the official Redux tutorial at https://redux.js.org/tutorials/essentials/part-1-overview-concepts
Am new to redux.
I am working now on an app that shows a list of soccer leagues in each country.
Firstly, I am fetching a countries list. Afterwards, I am using the country name to loop through all countries to get the soccer leagues. Not every country has a soccer league, so I get some null as response, which I filter out. Then I click on a league and I am redirected to a league page. Now comes the tricky part. When I click "back", I go to my main page, but the whole api call process gets fired again. Why? How to prevent it? how to only use the data, that I fetched ones, and only use it as I need to.
If I would guess, than the mistake is somewhere in the reducer. I try there to cache the fetched api call in an object (data: { ...state.data, ...}), but am not sure, if I do this correctly.
The second place, where I could do a m istake is the useEffect. But of course anything else is also possible.
Please help!
Here is my code:
App.js
I use react-router-dom to move between the conatiners:
import React from 'react';
import {Switch, Route, NavLink, Redirect} from "react-router-dom";
import SignedIn from '../signedIn/signedIn';
import SignedOut from '../signedOut/signedOut';
//Components/Containers
import AllLeagues from '../allLeagues/allLeagues/allLeagues';
import League from "../allLeagues/league/league";
const App = () => {
return (
<div className="App">
<nav>
<NavLink to={"/"}>SEARCH</NavLink>
</nav>
<Switch>
<Route path={"/"} exact component={AllLeagues} />
<Route path={"/allLeagues/:league"} exact component={League} />
<Route path={"/signedin"} exact component={SignedIn} />
<Route path={"/signedout"} exact component={SignedOut} />
<Redirect to={"/"} />
</Switch>
</div>
);
}
export default App;
Here is my Page, where I make the api calls to get the countries and the soccer leagues:
allLeagues.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {Link} from "react-router-dom";
import _ from "lodash";
import shortid from "shortid";
import { allLeagues } from "../../../actions/leagues/allLeagues/allLeagues";
import { allCountries } from "../../../actions/allCountries/allCountries";
//the api provides 255 country names.
const ALL_COUNTRIES_LENGTH = 254;
const AllLeagues = () => {
const dispatch = useDispatch();
const selectAllCountries = useSelector(state => state.allCountries);
const selectAllLeagues = useSelector(state => state.allLeagues);
useEffect(() => {
dispatch(allCountries());
}, [dispatch]);
useEffect(() => {
if(!_.isEmpty(selectAllCountries.data)) {
selectAllCountries.data.countries.map(el => dispatch(allLeagues(el.name_en)));
}
}, [dispatch, selectAllCountries.data]);
let allCountriesArr = [];
let allLeaguesFiltered = [];
let getAllLeagues = [];
allCountriesArr = (Object.values(selectAllLeagues.data));
console.log(Object.values(selectAllLeagues.data));
if(allCountriesArr.length > ALL_COUNTRIES_LENGTH) {
allLeaguesFiltered = allCountriesArr.flat().filter(el => el !== null);
getAllLeagues = allLeaguesFiltered.flat();
}
let getAllZeroDivisionLeagues = [];
let getAllFirstDivisionLeagues = [];
let getAllSecondDivisionLeagues = [];
let getAllThirdDivisionLeagues = [];
if(!_.isEmpty(getAllLeagues)) {
getAllZeroDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "0");
getAllFirstDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "1");
getAllSecondDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "2");
getAllThirdDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "3");
}
const showData = () => {
if(!_.isEmpty(selectAllLeagues.data)) {
return(
<div>
Most Favorited Leagues:
<br/>
{getAllZeroDivisionLeagues.map(el => {
return (
<div key={shortid.generate()}>
<p>{el.strLeague}</p>
<Link to={`/allLeagues/${el.strLeague}`}>View</Link>
</div>
)}
)}
<br/>
<br/>
First Leagues:
<br/>
{getAllFirstDivisionLeagues.map(el => {
return (
<div key={shortid.generate()}>
<p>{el.strLeague}</p>
<Link to={`/allLeagues/${el.strLeague}`}>View</Link>
</div>
)}
)}
<br/>
<br/>
Second Leagues:
<br/>
{getAllSecondDivisionLeagues.map(el => {
return (
<div key={shortid.generate()}>
<p>{el.strLeague}</p>
<Link to={`/allLeagues/${el.strLeague}`}>View</Link>
</div>
)}
)}
<br/>
<br/>
Third Leagues:
<br/>
{getAllThirdDivisionLeagues.map(el => {
return (
<div key={shortid.generate()}>
<p>{el.strLeague}</p>
<Link to={`/allLeagues/${el.strLeague}`}>View</Link>
</div>
)}
)}
</div>
)
}
if (selectAllLeagues.loading) {
return <p>loading...</p>
}
if (selectAllLeagues.errorMsg !== "") {
return <p>{selectAllLeagues.errorMsg}</p>
}
return <p>Loading...</p>;
}
return (
<div>
<br/>
<br/>
All Leagues:
<br />
<br />
{showData()}
</div>
)
}
export default AllLeagues;
The both action files:
allCountries.js
import { GET_ALL_COUNTRIES_LOADING, GET_ALL_COUNTRIES_SUCCESS, GET_ALL_COUNTRIES_FAIL } from "../index";
import theSportsDB from "../../apis/theSportsDB";
export const allCountries = () => async (dispatch) => {
try {
dispatch ({
type: GET_ALL_COUNTRIES_LOADING
})
const response = await theSportsDB.get("all_countries.php");
dispatch ({
type: GET_ALL_COUNTRIES_SUCCESS,
payload: response.data
})
} catch (e) {
dispatch ({
type: GET_ALL_COUNTRIES_FAIL
})
}
}
and allCountriesReducer:
import {GET_ALL_COUNTRIES_LOADING, GET_ALL_COUNTRIES_SUCCESS, GET_ALL_COUNTRIES_FAIL} from "../../actions/index";
const DefaultState = {
loading: false,
data: [],
errorMsg: ""
};
const AllCountriesReducer = (state = DefaultState, action) => {
switch (action.type){
case GET_ALL_COUNTRIES_LOADING:
return {
...state,
loading: true,
errorMsg: ""
};
case GET_ALL_COUNTRIES_SUCCESS:
return {
...state,
loading: false,
data: {
...state.data,
countries: action.payload.countries
},
errorMsg: ""
};
case GET_ALL_COUNTRIES_FAIL:
return {
...state,
loading: false,
errorMsg: "unable to get all the Countries"
};
default:
return state;
}
}
export default AllCountriesReducer;
Now the files, with which I fetch the all the leagues (with the country name, that I got from allCountries):
import { GET_ALL_LEAGUES_LOADING, GET_ALL_LEAGUES_SUCCESS, GET_ALL_LEAGUES_FAIL } from "../../index";
import theSportsDB from "../../../apis/theSportsDB";
export const allLeagues = (country) => async (dispatch) => {
try {
dispatch ({
type: GET_ALL_LEAGUES_LOADING
})
const response = await theSportsDB.get(`search_all_leagues.php?c=${country}&s=Soccer`);
dispatch ({
type: GET_ALL_LEAGUES_SUCCESS,
payload: response.data,
countryName: country
})
} catch (e) {
dispatch ({
type: GET_ALL_LEAGUES_FAIL
})
}
}
and the reducer,
allLeaguesReducer.js
import {GET_ALL_LEAGUES_LOADING, GET_ALL_LEAGUES_SUCCESS, GET_ALL_LEAGUES_FAIL} from "../../../actions/index";
const DefaultState = {
loading: false,
data: {},
errorMsg: ""
};
const AllLeaguesReducer = (state = DefaultState, action) => {
switch (action.type){
case GET_ALL_LEAGUES_LOADING:
return {
...state,
loading: true,
errorMsg: ""
};
case GET_ALL_LEAGUES_SUCCESS:
return {
...state,
loading: false,
data:{
...state.data,
[action.countryName]: action.payload.countrys
},
errorMsg: ""
};
case GET_ALL_LEAGUES_FAIL:
return {
...state,
loading: false,
errorMsg: "unable to get all the leagues"
};
default:
return state;
}
}
export default AllLeaguesReducer;
Also the leagues page itself:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {Link} from "react-router-dom";
import _ from "lodash";
import shortid from "shortid";
import { getLeague } from "../../../actions/leagues/league/getLeague";
const League = (props) => {
const leagueName = props.match.params.league;
const dispatch = useDispatch();
const selectLeague = useSelector(state => state.league);
useEffect (() => {
dispatch(getLeague(leagueName));
}, [dispatch, leagueName]);
const showLeague = () => {
if(!_.isEmpty(selectLeague.data)) {
return selectLeague.data.teams.map(el => {
return (
<div key={shortid.generate()}>
{el.strTeam}
</div>
)
})
}
if(selectLeague.loading) {
return <p>loading...</p>
}
if(selectLeague.errorMsg !== "") {
return <p>{selectLeague.errorMsg}</p>
}
return <p>Unable to get the league data</p>
}
return (
<div>
<p>{leagueName}</p>
{showLeague()}
<Link to={"/"}>Back</Link>
</div>
)
}
export default League;
its action file:
import { GET_LEAGUE_LOADING, GET_LEAGUE_SUCCESS, GET_LEAGUE_FAIL } from "../../index";
import theSportsDB from "../../../apis/theSportsDB";
export const getLeague = (league) => async (dispatch) => {
try {
dispatch ({
type: GET_LEAGUE_LOADING
})
const response = await theSportsDB.get(`search_all_teams.php?l=${league}`);
dispatch ({
type: GET_LEAGUE_SUCCESS,
payload: response.data,
// leagueName: league
})
} catch (e) {
dispatch ({
type: GET_LEAGUE_FAIL
})
}
}
and the reducer:
import { GET_LEAGUE_LOADING, GET_LEAGUE_SUCCESS, GET_LEAGUE_FAIL } from "../../../actions/index";
const DefaultState = {
loading: false,
data: {},
errorMsg: ""
};
const LeagueReducer = (state = DefaultState, action) => {
switch (action.type) {
case GET_LEAGUE_LOADING:
return {
...state,
loading: true,
errorMsg: ""
};
case GET_LEAGUE_SUCCESS:
return {
...state,
loading: false,
data: action.payload,
errorMsg: ""
};
case GET_LEAGUE_FAIL:
return {
...state,
loading: false,
errorMsg: "league not found"
};
default:
return state
}
}
export default LeagueReducer;
In Redux dev Tools, when I press on back, to get again to my home page, the following is triggered (in status bar):
GET_ALL_COUNTRIES_LOADING
and after some time:
GET_ALL_LEAGUES_SUCCESS
again. So it is making an api call again.
You need to use a conditional in useEffect so that it doesn't run each time you load the page.
Try this:
useEffect(() => {
if (selectAllCountries.data.length < 1) {
disptch(getCountries());
}
})