Can we make API call to set the initial state of reducer? - reactjs

let initialData = {
products:[]
}
const ItemReducer = (state = initialData, action) => {
switch (action.type) {
case "FETCHDATA":
return {
...state,
products: action.payload,
};
}
return state;
}
let [productdata, setProductdata] = useState();
const dispatch = useDispatch();
useEffect(()=>{
axios.get("https://fakestoreapi.com/products")
.then((res) => setProductdata(res.data))
.catch((err) => console.log("error"))
},[]);
dispatch({type:"FETCHDATA",payload: productdata});
Initial state of ItemReducer must contain the "product details" which needs to be fetch from api call. While am using the above code, its returning undefined.

One possibility is to do the fetch in a parent component and only render the children (that has the useDispatch) when the productData is present. That way it can also handle error and loading states, something like this:
const ProductDetailsList = ({ products }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch({ type: "FETCHDATA", payload: products });
}, [dispatch])
return <div>...</div>;
};
const ProductsDetails = () => {
const [products, setProducts] = React.useState([])
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState(null)
useEffect(() => {
setLoading(true)
axios
.get("https://fakestoreapi.com/products")
.then((res) => setProducts(res.data))
.catch(setError)
.finally(() => setLoading(false))
}, []);
if (loading) {
return <div>Loading...</div>
}
if (error) {
return <div>Error: {error.message}</div>
}
return <ProductDetailsList products={products} />
}

Related to the docks you have to use
fetch('https://fakestoreapi.com/products')
.then(res=>res.json())
.then(json=>console.log(json))

Related

useState initial value don't use directly the value

I have an initial state that I never use directly in the code, only inside another set value state
Only a scratch example:
interface PersonProps {}
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("")
const [todayYear, setTodayYear] = useState<string>("")
const [birthYear, setBirthYear] = useState<string>("")
const [age, setAge] = useState<string>("")
const getPerson = async () => {
try {
const response = await getPersonRequest()
const data = await response.data
setName(data.name)
setTodayYear(data.today_year)
setBirthYear(data.future_year)
setAge(data.todayYear - data.birthYear)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getPerson()
})
return (
<h1>{name}</h1>
<h2>{age}</h2>
)
}
export default Person
In this case as you can see I will never use "todayYear" and "birthYear" on UI, so code give a warning
todayYear is assigned a value but never used
What can I do to fix this and/or ignore this warning?
If you don't use them for rendering, there's no reason to have them in your state:
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("")
const [age, setAge] = useState<string>("")
const getPerson = async () => {
try {
const response = await getPersonRequest()
const data = await response.data
setName(data.name)
setAge(data.todayYear - data.birthYear)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getPerson()
})
return (
<h1>{name}</h1>
<h2>{age}</h2>
)
}
Side note: In most cases, you can leave off the type argument to useState wen you're providing an intial value. There's no difference between:
const [name, setName] = useState<string>("")
and
const [name, setName] = useState("")
TypeScript will infer the type from the argument. You only need to be explicit when inference can't work, such as if you have useState<Thingy | null>(null).
As this other answer points out, unless you want your code to run every time your component re-renders (which would cause an infinite render loop), you need to specify a dependency array. In this case, probably an empty one if you only want to get the person information once.
Also, since it's possible for your component to be unmounted before the async action occurs, you should cancel your person request if it unmounts (or at least disregard the result if unmounted):
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("");
const [age, setAge] = useState<string>("");
const getPerson = async () => {
const response = await getPersonRequest();
const data = await response.data;
return data;
};
useEffect(() => {
getPerson()
.then(data => {
setName(data.name)
setAge(data.todayYear - data.birthYear)
})
.catch(error => {
if (/*error is not a cancellation*/) {
// (Probably better to show this to the user in some way)
console.log(error);
}
});
return () => {
// Cancel the request here if you can
};
}, []);
return (
<h1>{name}</h1>
<h2>{age}</h2>
);
};
If it's not possible to cancel the getPersonRequest, the fallback is a flag:
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("");
const [age, setAge] = useState<string>("");
const getPerson = async () => {
const response = await getPersonRequest();
const data = await response.data;
return data;
};
useEffect(() => {
let mounted = true;
getPerson()
.then(data => {
if (mounted) {
setName(data.name)
setAge(data.todayYear - data.birthYear)
}
})
.catch(error => {
// (Probably better to show this to the user in some way)
console.log(error);
});
return () => {
mounted = false;
};
}, []);
return (
<h1>{name}</h1>
<h2>{age}</h2>
);
};
I also would like to mention one more thing. It's not related to your question but I think it's important enough to talk about it.
you need to explicitly state your dependencies for useEffect
In your case, you have the following code
useEffect(() => {
getPerson()
})
it should be written as follow if you want to trigger this only one time when a component is rendered
useEffect(() => {
getPerson()
}, [])
or if you want to trigger your side effect as a result of something that has changed
useEffect(() => {
getPerson()
}, [name])
If this is not clear for I suggest read the following article using the effect hook

Firebase + react : read document in auth state changed and add it to context

Based on https://dev.to/bmcmahen/using-firebase-with-react-hooks-21ap I have a authentication hook to get user state and firestore hook to get user data.
export const useAuth = () => {
const [state, setState] = React.useState(() => { const user = firebase.auth().currentUser return { initializing: !user, user, } })
function onChange(user) {
setState({ initializing: false, user })
}
React.useEffect(() => {
// listen for auth state changes
const unsubscribe = firebase.auth().onAuthStateChanged(onChange)
// unsubscribe to the listener when unmounting
return () => unsubscribe()
}, [])
return state
}
function useIngredients(id) {
const [error, setError] = React.useState(false)
const [loading, setLoading] = React.useState(true)
const [ingredients, setIngredients] = React.useState([])
useEffect(
() => {
const unsubscribe = firebase
.firestore()
.collection('recipes')
.doc(id)
.collection('ingredients') .onSnapshot( snapshot => { const ingredients = [] snapshot.forEach(doc => { ingredients.push(doc) }) setLoading(false) setIngredients(ingredients) }, err => { setError(err) } )
return () => unsubscribe()
},
[id]
)
return {
error,
loading,
ingredients,
}
}
Now in my app I can use this to get user state and data
function App() {
const { initializing, user } = useAuth()
const [error,loading,ingredients,] = useIngredients(user.uid);
if (initializing) {
return <div>Loading</div>
}
return (
<userContext.Provider value={{ user }}> <UserProfile /> </userContext.Provider> )
}
Since UID is null before auth state change trigger, firebase hook is getting called with empty key.
How to fetch data in this scenario once we understand that user is logged in.
May be you can add your document read inside auth hook.
export const useAuth = () => {
const [userContext, setUserContext] = useState<UserContext>(() => {
const context: UserContext = {
isAuthenticated: false,
isInitialized: false,
user: auth.currentUser,
userDetails: undefined
};
return context;
})
function onChange (user: firebase.User | null) {
if (user) {
db.collection('CollectionName').doc(user.uid)
.get()
.then(function (doc) {
//set it to context
})
});
}
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(onChange)
return () => unsubscribe()
}, [])
return userContextState
}
You can use some loading spinner in your provider to wait for things to complete.

React hooks - fetching data from api and passing to a component

So basically, I'm trying to fetch data from api and pass it to Component.
I create usePosition hook to get my positon from browser, and then get response from api. I really don't know how to wait with useEffect for my position, when i'm executing this code now I'm getting always log 'no position'.
const usePosition = () => {
const [error, setError] = useState(null);
const [position, setPosition] = useState();
useEffect(() => {
const geo = navigator.geolocation;
if(!geo) {
setError('Geolocation is not supported.');
return;
}
const handleSuccess = position => {
const { latitude, longitude } = position.coords;
setPosition({
latitude,
longitude
});
};
const handleError = error => {
setError(error.message);
};
geo.getCurrentPosition(handleSuccess, handleError);
}, []);
return { position, error };
}
function App() {
const {position, error} = usePositon();
const [weather, setWeather] = useState([]);
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, []);
return (
<div className="App">
<div>
<Swiper weather={weather}/>
</div>
</div>
)
}
It's all because of [] empty dependencies list down in App's useEffect. It runs exactly once on mount, when usePosition has not requested anything yet. And once it successes later and returns different { error, position } App does not react.
How to solve? Provide things as dependencies:
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, [position, error]);

How can i use useReducer to assign initial state after calling custom Datafetch hook? I keep getting null

I created a custom datafetch hook but when i use the reducer function to set it as initial state it says its null.
Component where i call the custom Hook.
const collection = 'items'
const whereClause = { array: "lists", compare: 'array-contains', value: 'Pantry' }
const res = useDataFetchWhere(collection, whereClause)
const data = res.response
const [state, dispatch] = useReducer(reducer, data)
When I console.log(state) I get null.
My custom data fetch hook
const useDataFetchWhere = (collection, whereClause) => {
const [response, setResponse] = useState(null)
const [error, setError] = useState(null)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const fetchData = async () => {
setIsLoading(true)
setError(false)
try {
await db.collection(collection).where(whereClause.array, whereClause.compare, whereClause.value).get()
.then(data => {
setResponse(data.docs.map(doc => ({ ...doc.data(), id: doc.id })))
setIsLoading(false)
console.log('hello where')
})
} catch (error) {
setError(error)
}
}
fetchData()
return function cleanup() {
console.log('cleaned up check')
};
}, [])
return { response, error, isLoading }
}
Is there anything i need to do or call in a different way?
Thanks.
The problem is that useDataFetchWhere does not immediately return the result of the data fetching, but only after a while the request is done and then the setResponse will set the actual data. So you cannot set the response as initial state for the useReducer call.
You need to wait until the request is done before using it's result. You could create an action (e.g. SET_DATA) for the reducer that sets the result once it's there.
You already have the isLoading flag available:
const [state, dispatch] = useReducer(reducer, null);
useEffect(() => {
if (!isLoading) {
const data = res.response;
dispatch({type: 'SET_DATA', data});
}
}, [isLoading]);

React hooks - How to call useEffect on demand?

I am rewriting a CRUD table with React hooks. The custom hook useDataApi below is for fetching data of the table, watching the url change - so it'll be triggered when params change. But I also need to fetch the freshest data after delete and edit. How can I do that?
const useDataApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl)
const [state, dispatch] = useReducer(dataFetchReducer, { data: initialData, loading: true })
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' })
const result = await instance.get(url)
dispatch({ type: 'FETCH_SUCCESS', payload: result.data })
}
fetchData()
}, [url])
const doFetch = url => {
setUrl(url)
}
return { ...state, doFetch }
}
Since the url stays the same after delete/edit, it won't be triggered. I guess I can have an incremental flag, and let the useEffect monitor it as well. But it might not be the best practice? Is there a better way?
All you need to do is to take the fetchData method out of useEffect and call it when you need it. Also make sure you pass the function as param in dependency array.
const useDataApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl)
const [state, dispatch] = useReducer(dataFetchReducer, { data: initialData, loading: true })
const fetchData = useCallback(async () => {
dispatch({ type: 'FETCH_INIT' })
const result = await instance.get(url)
dispatch({ type: 'FETCH_SUCCESS', payload: result.data })
}, [url]);
useEffect(() => {
fetchData()
}, [url, fetchData]); // Pass fetchData as param to dependency array
const doFetch = url => {
setUrl(url)
}
return { ...state, doFetch, fetchData }
}

Resources