react props comes blank on first transaction - reactjs

I am using redux promise middleware. I am trying to pass the value in Propsx to state. Props comes empty in useEffect. How can I transfer the contents of the props to state. Props value comes next.
action:
export function fetchBasket() {
return dispatch => {
dispatch({
type: 'GET_BASKET',
payload: axios.get('url', {
})
.then(response => response.data)
});
};
}
reducer:
const initialState = {
fetching: false,
error: {},
basket: []
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_BASKET_PENDING:
return {
fetching: true
};
case types.GET_BASKET_FULFILLED:
return {
...state,
fetching: false,
basket: payload.result,
};
case types.GET_BASKET_REJECTED:
return {
fetching: false,
error: payload.result
};
default:
return state;
}
};
use in Component
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);

[enter link description here][1]If you want to have values in your first run(Mount). fetch here ==> useLayoutEffect and this will gives the values in useEffect()[]. [uselayouteffect]: https://reactjs.org/docs/hooks-reference.html#uselayouteffect

useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
Your props will update only in the next event loop cycle, to use react hooks data updation inside useEffect you need to useReducer https://reactjs.org/docs/hooks-reference.html#usereducer

Related

Can't map fetched data from custom useFetch() hook

I created a custom useFetch() hook so I can make my code more dynamic and less repetitive. The problem is that I can't display my data in App.js.
I get these errors:
Cannot read properties of undefined (reading 'map').
react-dom.development.js:67 Warning: Can't perform a React state
update on an unmounted component. This is a no-op, but it indicates a
memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in a useEffect cleanup function.
I did a console.log(genres) to see if there are any errors from my custom hook, but it works fine, logs all the genres. The problem is caused as soon as I try to display my data using the map method.
CodeSandbox link
useFetch.js
import { useReducer, useEffect } from "react";
import axios from "axios";
const ACTIONS = {
API_REQUEST: "api-request",
FETCH_DATA: "fetch-data",
ERROR: "error",
};
const initialState = {
data: [],
loading: false,
error: null,
};
function reducer(state, { type, payload }) {
console.log(payload);
switch (type) {
case ACTIONS.API_REQUEST:
return { ...state, data: [], loading: true };
case ACTIONS.FETCH_DATA:
return { ...state, data: payload, loading: false };
case ACTIONS.ERROR:
return { ...state, data: [], error: payload };
default:
return state;
}
}
function useFetch(url) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: ACTIONS.API_REQUEST });
axios
.get(url)
.then((res) => {
dispatch({ type: ACTIONS.FETCH_DATA, payload: res.data });
})
.catch((e) => {
dispatch({ type: ACTIONS.ERROR, payload: e.error });
});
}, [url]);
return state;
}
export default useFetch;
App.js
import "./styles.css";
import useFetch from "./useFetch";
export default function App() {
const BASE_URL =
"https://api.themoviedb.org/3/genre/movie/list?api_key=${API_KEY}";
const { data: genres, loading, error } = useFetch(BASE_URL);
console.log(genres);
return (
<div className="App">
{genres.genres.map((genre) => (
<div key={genre.id}>{genre.name}</div>
))}
</div>
);
}
Your initial state has data as an array:
const initialState = {
data: [],
loading: false,
error: null,
};
And your App component is trying to read the property genres on that array as soon as it loads. There is no property on an array with that name, so genres.genres is undefined, and the map call on it will throw an error.
I would initialise initialState.data as {genres: []}, by passing the data container as another argument to your hook rather than hardcoding it into the hook file.
function useFetch(url, data) {
const [state, dispatch] = useReducer(reducer, {...initialState, data});
...
}
const { data: genres, loading, error } = useFetch(BASE_URL, {genres: []});

Infinte loop in UseEffect retrieval from Firestore

I am trying to retrieve the document of the organization that is logged in after we see that Firebase Auth has the correct user uid. However, I'm getting an infinite loop in the useEffect function for that retrieval.
import { createContext, useEffect, useReducer} from 'react'
import { projectAuth, projectFirestore } from '../firebase'
export const AuthContext = createContext()
export const authReducer = (state, action) => {
switch (action.type) {
case 'LOGIN':
return { ...state, user: action.payload }
case 'LOGOUT':
return { ...state, user: null }
case 'AUTH_IS_READY':
return { ...state, user: action.payload, authIsReady: true}
case 'RETRIVED_ORG':
return { ...state, org: action.payload }
default:
return state
}
}
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
user: null,
org: null,
authIsReady: false
})
useEffect(() => {
const unsub = projectAuth.onAuthStateChanged((user) => {
dispatch({ type: 'AUTH_IS_READY', payload: user})
unsub()
})
}, [])
console.log('AuthContext state:', state)
useEffect(() => {
if (state.authIsReady) {
projectFirestore.collection('orgs').doc(state.user.uid).get()
.then(snapshot => {
dispatch({ type: 'RETRIVED_ORG', payload: snapshot.data()})
})
}
}, [state])
return (
<AuthContext.Provider value={{ ...state, dispatch }}>
{children}
</AuthContext.Provider>
)
}
This is the Context for Auth. As you can see, the first useEffect is run once and when it is complete, it triggers dispatch and sets the user and 'authIsReady' state to true from the reducer.
For the second useEffect, I want to run it when the authIsReady state is true because only then, I will know I have the user.uid. This does fire and uses the dispatch and sets the org but it is running multiple times. I think this is because I am including [state] in the second parameter, but if I remove it, I get an error message:
"React Hook useEffect has missing dependencies: 'state.authIsReady' and 'state.user.uid'. Either include them or remove the dependency array".
Is there a more elegant way of doing this?
Your 2nd useEffect needs to depend just on state.authIsReady and state.user.uid. Right now its triggered in every state update, and when you do dispatch({ type: 'RETRIVED_ORG', payload: snapshot.data()}) you update the state, and it triggers 2nd useEffect again, fetches again, updates again and this is what is creating the infinite loop.
It should be:
useEffect(() => {
if (state.authIsReady) {
projectFirestore.collection('orgs').doc(state.user.uid).get()
.then(snapshot => {
dispatch({ type: 'RETRIVED_ORG', payload: snapshot.data()})
})
}
}, [state.authIsReady, state.user.uid])

how to set the initialState with useReducer React

hi thank you for reading this. I am working a github finder react app that uses useReducer and i am try to set the initialstate when onload to load some users instead of an empty array. if i hard code the api data into the array, it will display as i wanted, but i want to make a GET to the api and pass the data into the array. I am very new to react, thank you all for the help
const GithubState = (props) => {
const initialState = {
users: [],
user: {},
repos: [],
loading: false,
};
//dispatcher
const [state, dispatch] = useReducer(GithubReducer, initialState);
//search Github users
const searchUsers = async (text) => {
setLoading();
const res = await axios.get(
`https://api.github.com/search/users?q=${text}&client_id=${githubClientId}&client_secret=${githubClientaSecret}`
);
//dispatch to reducer object
dispatch({
type: SEARCH_USERS,
payload: res.data.items,
});
};
//Reducer
import {
SEARCH_USERS,
SET_LOADING,
CLEAR_USERS,
GET_USER,
GET_REPOS,
} from "../types";
//action contains type and payload
// export default (state, action) => {
const Reducer = (state, action) => {
switch (action.type) {
case SEARCH_USERS:
return {
...state,
users: action.payload,
loading: false
}
case GET_USER:
return {
...state,
user: action.payload,
loading: false
}
case GET_REPOS:
return {
...state,
repos: action.payload,
loading: false
}
case SET_LOADING:
return {
...state,
loading: true
}
case CLEAR_USERS:
return {
...state,
users: [],
loading: false
}
default:
return state;
}
};
export default Reducer;
You can just call the searchUsers() function in useEffect() to get some users and set the state.
if you want to get initial users with some other logic you should probably write a different function and then call it when setting up the component.
const getInitialUsers = async () => {
setLoading();
let text = "blabla"; // or whatever your initial user query should look like modify the url below accordingly
const res = await axios.get(
`https://api.github.com/search/users?q=${text}&client_id=${githubClientId}&client_secret=${githubClientaSecret}`
);
//dispatch to reducer object
dispatch({
type: SEARCH_USERS,
payload: res.data.items,
});
};
useEffect(()=>{
getInitialUsers();
},[]);
The reasonable and suggest place to get your first async data is componentDidMount which in hook world it is translated to use Effect with an empty array as its dependencies
const [state, dispatch] = useReducer(githubReducer, initialState);
useEffect(() => {
searchUsers('initalTextSearchFromPropsOrAnyWhere')
}, [])
for more enhancement, you can call .then to show snack bar and .catch on to retry in case it fails for some reason and .finally to set loading to false in both cases.

Can't dispatch action in class component

// action
export const getEvents = () => async (dispatch) => {
try {
dispatch({ type: GET_EVENTS_REQUEST })
const data = await axios.get('http://localhost:5000/api/schedule').then((response) => response.data)
dispatch({ type: GET_EVENTS_SUCCESS, payload: data })
} catch (error) {
dispatch({
type: GET_EVENTS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
// reducer
export const getEventsReducer = (state = { event: [] }, action) => {
switch (action.type) {
case GET_EVENTS_REQUEST:
return { loading: true }
case GET_EVENTS_SUCCESS:
return { loading: false, event: action.payload }
case GET_EVENTS_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
// and this is how I'm trying to call my action:
import { getEvents } from '../../redux/actions/calendarActions'
class Calendar extends React.PureComponent {
componentDidMount() {
const { dispatch } = this.props
console.log(dispatch(getEvents()))
}
}
export default connect()(Calendar)
// component is much bigger, I only added relevant parts
Up until my reducer, if I console.log my data, it is correct, as well as in my redux dev tools tab: an array with a few entries. But when console.logging in my Calendar component, it returns a promise, with undefined result:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
What am I doing wrong?
Normally you want to have access to either the dispatch or the store of Redux within a component. you already have the dispatch function within the component, but if you need access to Redux state inside it:
first you need to define such function, which makes the redux store available in the component.
const mapStateToProps = (state) => ({
state: state // a "state" prop is available in the component which points to redux state,
})
or you can customize it if you only need certain properties of Redux state:
const mapStateToProps = (state) => ({
state: state.event //
})
and change the connect function like this:
connect(mapStateToProps)(Calendar)

React useEffect doesn't dispatch the redux action after page refresh

I'm trying to render the data from the following object of data which is coming from an API.
{
"code": 0,
"c": "verified",
"d": "verified",
"leaseInfo": {
"infoId": 6
},
"cpfPrice": "500.00",
"carCurrentLocation": {
"id": 1,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
"n": "verified",
"p": "false",
"ownerCarInfo": {
"brand": "Ferrari",
"model": "0"
},
"serviceFeeRate": 0.10,
"depositPrice": "100.00",
"pics": [
{
"picid": 49,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
],
"items": {
"itemid": 5,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
}
}
I'm using react-redux to dispatch an action, where I will be provided with the data under a state named 'carDetails'.
However, when I try to access the data, if my component is refreshed, carDetails becomes undefined and hence gives "Cannot read property ownerCarInfo of undefined."
I'm obtaining and de-structuring the data of carDetails like this in my React component:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(match.params.id));
}, [dispatch, match]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
);
)
}
As long as the component or the React application is not refreshed, it retrieves data and displays it correctly. The carDetails becomes an empty array as soon as the page is refreshed.
This is the getCarDetails() action:
export const getCarDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: CAR_DETAILS_REQUEST,
});
const { userLogin } = getState();
const { userInfo } = userLogin;
const config = {
headers: {
Authorization: userInfo.token,
'Content-Type': 'application/json',
},
};
const { data } = await axios.get(
`${BASE_API}/car/info/getDetails/${id}/${userInfo.bscId}`,
config
);
dispatch({
type: CAR_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CAR_DETAILS_FAIL,
payload:
error.response && error.response.data.msg
? error.response.data.msg
: error.msg,
});
}
};
This is my reducer:
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { loading: true };
case CAR_DETAILS_SUCCESS:
return { loading: false, carDetails: action.payload };
case CAR_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
This is how I declare carDetails in the redux store.
const reducer = combineReducers({
carDetails: carsDetailsReducer,
});
What is the cause for carDetails becoming undefined and the useEffect not running on page refresh?
If you are using axios your action should look like this with async function and await while you are calling API.
If you are passing API car id in the api link then pass the id in the parameters:
import axios from "axios";
export const loadData = (id) => async (dispatch) => {
dispatch({
type: "CAR_DETAILS_REQUEST",
});
const detailData = await axios.get("http:\\****/id");
dispatch({
type: "CAR_DETAILS_SUCCESS",
payload: {
success: detailData.data,
},
});
};
Reducer:
const initailState = { carDetails: [], loading: true };
export const carsDetailsReducer = (state = initailState, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { ...state,
loading: true
};
case CAR_DETAILS_SUCCESS:
return {...state,
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return { ...state,
loading: false,
error: action.payload };
default:
return ...state;
}
};
Your useEffect should only work when data is fetched:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(id));
}, [dispatch]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
You can also use it without a useEffect by making an onclick() function like this:
const loadDetailHandler = () => {
dispatch(getCarDetails(id));
};
return (
<div onClick={loadDetailHandler} >
</div>
If carDetails initial state is an array, then why are you destructuring object properties from it in your UI? Question for another time...
If after reloading the page the state reverts back to the initial state, an empty array is still a defined object. You need to track down what is causing your state.carDetails.carDetails to become undefined. If you examine your reducer notice that your CAR_DETAILS_REQUEST case wipes the carDetails state out and it becomes undefined. Honestly I'm surprised you aren't seeing this issue when your code runs normally without a page reload.
You need to hold on to that state. For good measure, you should always shallow copy the existing state when computing the next state object unless you've good reason to omit parts of state.
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
case CAR_DETAILS_SUCCESS:
return {
...state, // <-- shallow copy existing state
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return {
...state, // <-- shallow copy existing state
loading: false,
error: action.payload,
};
default:
return state;
}
};
for me, I think you should save the state in the
`case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
`
to be able to use it before o when you want to use a reducer you should each case
have the old state the reducer return the same sharp of initial state that put it you also used is loading and that not found in the initial state
so try to make the shape of the state
state={
isloading:false,
carDetails: []
}
also try each time to same the state by {...state ,is loading:true}
The problem is in CAR_DETAILS_REQUEST. You only return { loading: true }; so carDetails will be lost and become undefined.
Just update your reducer like this:
case CAR_DETAILS_REQUEST:
return { ...state, loading: true };

Resources