How to use custom component multiple times in same component - javascript

Basically i've created one custom component for api calling
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export const useFetch = config => {
const [Response, setResponse] = useState({});
const [Error, setError] = useState({});
const [ShowModal, setShowModal] = useState(false);
const [ShowLoader, setShowLoader] = useState(false);
useEffect(() => {
callAPI();
}, []);
const callAPI = () => {
setShowLoader(true);
axios(config)
.then(res => {
console.log('==>>', res);
if (res.status == 200) {
setShowLoader(false);
setResponse(res.data);
}
})
.catch(err => {
console.log('==>>', err.response);
setError(err.response.data);
setShowLoader(false);
setShowModalErrorMessage(err.response.data.error);
setShowModal(true);
});
};
return {Response, Error, ShowModal, ShowLoader};
};
with the help on this i can call api and get response if i use it with useEffect/componentDidMount in component. But how to use same for calling different api on Button click. is it possible?
i followed this=> post

Add setUrl method (can expand to setConfig) in useFetch.
Here working demo for this in stackblitz
import React, {useState, useEffect} from 'react';
import axios from 'axios';
const useFetch = ({}) => {
const [Response, setResponse] = useState({});
const [Error, setError] = useState({});
const [ShowModal, setShowModal] = useState(false);
const [ShowLoader, setShowLoader] = useState(false);
const [url, setUrl] = useState("");
useEffect(() => {
if (url) {
console.log('making request ', url);
callAPI();
}
}, [url]);
const callAPI = () => {
setShowLoader(true);
axios(url)
.then(res => {
console.log('==>>', res);
if (res.status == 200) {
setShowLoader(false);
setResponse(res.data);
}
})
.catch(err => {
console.log('==>>', err.response);
setError(err.response.data);
setShowLoader(false);
setShowModalErrorMessage(err.response.data.error);
setShowModal(true);
});
};
return {Response, Error, ShowModal, ShowLoader, setUrl};
};
export default useFetch;
On the button click, set url (expand to config)
import React, {useState, useEffect} from 'react';
import useFetch from './use-fetch';
export default ({ name }) => {
const {Response, Error, ShowModal, ShowLoader, setUrl } = useFetch({});
return (<div>
<button key={'1'} onClick={() => setUrl("http://foo/items")}> Request 1 </button>
<button key={'2'} onClick={() => setUrl("http://foo/other")}> Request 2 </button>
</div>)
};

Common Request.js file using the fetch method
Request("POST","http://localhost/users/user",{'name':"",'add':""})
export default function Request(reqMethod, endPointUrl, bodyData) {
if (reqMethod === "POST") {
return fetch(endPointUrl, {
method: reqMethod,
body: JSON.stringify(bodyData),
})
.then((response) => {
return response.json();
})
.catch(() => {
localStorage.clear();
});
} else if (reqMethod === "GET") {
return fetch(endPointUrl, {
method: reqMethod,
})
.then((response) => {
return response.json();
}).catch(() => {
localStorage.clear();
});
}
}

Related

Array doesn't save information ReactJS

import './App.css';
import io from 'socket.io-client'
import { useEffect, useRef, useState } from 'react'
import React from 'react';
import ReactDOM from "react-dom/client";
const socket = io.connect("http://localhost:3001");
function App() {
const [message, setMessage] = useState("");
const [state, setState] = useState([]);
const [chat, setChat] = useState([]);
const socketRef = useRef();
const sendMessage = () => {
socket.emit("send_message", { message });
};
const renderChat = () => {
return (
chat.map(msg => {
console.log(msg.data)
return (
<h3>{msg.data["message"]}</h3>
)
})
)
}
useEffect(() => {
socketRef.current = io.connect("http://localhost:3001")
socketRef.current.on("receive_message", ({ message }) => {
setChat([ ...chat, { message } ])
})
return () => socketRef.current.disconnect()
},
[ chat ]
)
return (
<div className="App">
<input placeholder="Message..." onChange={(event) => {
setMessage(event.target.value);}}
/>
<button onClick={sendMessage}>Send Message</button>
<h1>Message:</h1>
{renderChat()}
</div>
);
}
export default App;
For some reason the useEffect that needs to store information doesn't work. I have tried a few solutions to store new values in an array useState but I always get this error:
When I do it like this:
useEffect(() => {
socket.on("receive_message", message => {
setChat([...chat, {message}]);
});
}, [socket])
it works but it doesn't save the information (it always has only 1 value which is the latest input text).
You can do it like in the second approach you mentioned, using the previous State:
useEffect(() => {
socket.on("receive_message", message => {
setChat(prevState => [...prevState, {message}]);
});
}, [socket])
You try
useEffect(() => {
socket.on("receive_message", ({ message }) => {
if(!!message){
setChat(prev => [ ...prev, { message } ])
}
})
return () => socket.disconnect()
},[ socket ])

How to call api only on mount and click of Fetch Next User button

I have the below code
import React, {useState, useEffect, useCallback} from 'react';
import axios from 'axios';
const Users = () => {
const [users, setUsers] = useState([]);
const [nextPageNumber, setNextPageNumber] = useState(1);
const fetchUsers = useCallback(() => {
axios.get(`https://randomuser.me/api?page=${nextPageNumber}`).then(response => {
const updatedUsers = [...users, ...response.data.results];
setUsers(updatedUsers);
setNextPageNumber(response.data.info.page + 1);
}).catch(error => {
console.log(error)
})
}, [nextPageNumber, users])
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
const fetchNextUser = () => {
fetchUsers();
}
if(users.length === 0){
return <div>No Users available</div>
}
return(
<div>
<button onClick={fetchNextUser}>Fetch Next user</button>
{users.map(user => (
<div key={user.id}>
<div>Name: {user.name.title} {user.name.first} {user.name.last}</div>
<div>
<img src={user.picture.large} alt="Not available"/>
</div>
</div>
))}
</div>
)
}
export default Users;
This is calling the api repeatadlly. I have used the fetchUsers dependency in useEffect and useCallback hook due to eslint errors. I just want to call the api on first mount and on click of Fetch Next user button without any eslint error.
Is there any way we can achieve that?
Have a try with the below changes it will not give you the eslint error messages.
import React, {useState, useEffect, useRef, useCallback} from 'react';
import axios from 'axios';
const Users = () => {
const [users, setUsers] = useState([]);
const nextPageRef = useRef(1)
const fetchUsers = useCallback(() => {
axios.get(`https://randomuser.me/api?page=${nextPageRef.current}`).then(response => {
const updatedUsers = [...response.data.results];
setUsers(prevUsers => [...prevUsers, ...updatedUsers]);
nextPageRef.current = response.data.info.page + 1
}).catch(error => {
console.log(error)
})
}, [])
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
You could simply use
useEffect(() => {
fetchUsers();
}, []);
This will call the fetchUsers function only once.
And when button is pressed, again call the fetchUsers() function

wait 500 miliseconds before running react hook

I have built this custom react hook :
import { useEffect, useContext, useState } from 'react';
import { ProductContext } from '../contexts';
import axios from 'axios';
export default function useProducts(searchQuery) {
const [products, setProducts] = useContext(ProductContext);
const [isLoading, setIsloading] = useState(true);
useEffect(() => {
axios
.get(`/shoes?q=${searchQuery ? searchQuery : ''}`)
.then((res) => {
setProducts(res.data);
setIsloading(false);
})
.catch((err) => {
console.error(err);
});
}, [searchQuery, setProducts]);
return { products, isLoading };
}
It basically fetches some data based on a query string that i pass in. The query string comes from an input field :
import React, { useState } from 'react';
import { FiSearch } from 'react-icons/fi';
import { useProducts } from '../../hooks';
export default function SearchBar() {
const [query, setQuery] = useState('');
const handleChange = (e) => {
e.preventDefault();
setQuery(e.target.value);
};
useProducts(query);
return (
<div className="search-form">
<FiSearch className="search-form__icon" />
<input
type="text"
className="search-form__input"
placeholder="Search for brands or shoes..."
onChange={handleChange}
/>
</div>
);
}
The problem is it will fetch while the user is typing. I want it to fetch after the user didnt type for 500 miliseconds.
What I tried is :
setTimeout(() => {
useProducts(query);
}, 500);
But this will return an error saying :
src\components\header\SearchBar.js
Line 14:5: React Hook "useProducts" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
You can debounce your value with an additional piece of state. Once query is changed, we set off a 500 ms timer that will set the value of debounced. However, if the effect re-runs, we clear that timer and set a new timer.
import React, { useState, useEffect } from 'react';
import { FiSearch } from 'react-icons/fi';
import { useProducts } from '../../hooks';
export default function SearchBar() {
const [query, setQuery] = useState('');
const [debounced, setDebounced] = useState('');
useEffect(() => {
const timeout = setTimeout(() => {
setDebounced(query);
}, 500);
return () => { clearTimeout(timeout) }
}, [query])
const handleChange = (e) => {
e.preventDefault();
setQuery(e.target.value);
};
useProducts(debounced);
return (
<div className="search-form">
<FiSearch className="search-form__icon" />
<input
type="text"
className="search-form__input"
placeholder="Search for brands or shoes..."
onChange={handleChange}
/>
</div>
);
}
I'd change the useProducts hook to accept a debounce time as a parameter, and have it make the axios call only once the debounce time is up:
useProducts(query, 500);
export default function useProducts(searchQuery, debounceTime = 0) {
const [products, setProducts] = useContext(ProductContext);
const [isLoading, setIsloading] = useState(true);
const [timeoutId, setTimeoutId] = useState();
useEffect(() => {
clearTimeout(timeoutId);
setTimeoutId(setTimeout(() => {
axios
.get(`/shoes?q=${searchQuery ? searchQuery : ''}`)
.then((res) => {
setProducts(res.data);
setIsloading(false);
})
.catch((err) => {
console.error(err);
});
}, debounceTime));
}, [searchQuery, setProducts]);
return { products, isLoading };
}

React Native Undefined is not an object (evaluating 'userData.id)

I'm new to react native, I have a personal project, I am trying to get data from Firestore cloud, but I keep getting this error on the screen change.
It works fine when I comment out the database code, so I'm wondering what could be the cause.
My code
import React from "react";
import auth from "#react-native-firebase/auth";
import firestore from "#react-native-firebase/firestore";
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData(prev => {
return { ...prev, uid: usr.uid };
});
}, []);
const userRef = firestore().collection("users");
const snapshot = userRef
.where("uid", "==", userData.uid)
.onSnapshot()
.then(console.log(uid))
.catch(error => {
Alert.alert(error.message);
});
const [userData, setuserData] = React.useState({
uid: ""
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
You can try below code
import React from 'react';
import auth from '#react-native-firebase/auth';
import firestore from '#react-native-firebase/firestore';
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData((prev)=>{
return {...prev,uid: usr.uid};
});
}, []);
React.useEffect(() => {
fetchdata()
}, [userData]);// Once userData value has been updated then only call fetchData()
const fetchdata = ()=>{
const userRef = firestore().collection('users').doc(userData.uid).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document found!");
console.log(doc.data())
} else {
console.log("No such document!");
}
});
}
const [userData, setuserData] = React.useState({
uid: '',
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
#Maheshvirus is right. But I think you have tried to fetch data when userData.uid is not empty.
Try this way if looking for such a way.
import React from 'react';
import auth from '#react-native-firebase/auth';
import firestore from '#react-native-firebase/firestore';
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData((prev)=> {
return {...prev,uid: usr.uid};
});
}, []);
React.useEffect(() => {
if(userData.uid !== ''){
getData()
}
}, [userData]);
const getData = () => {
firestore()
.collection('users');
.where('uid', '==', userData.uid)
.onSnapshot()
.then(() => {
console.log(uid)
})
.catch((error)=> {
Alert.alert(error.message);
});
}
const [userData, setuserData] = React.useState({
uid: '',
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;

How to wait until context value is set?

I'm trying to render a header.
First, in InnerList.js, I make an API call, and with the data from the API call, I set a list in context.
Second, in Context.js, I take the list and set it to a specific data.
Then, in InnerListHeader.js, I use the specific data to render within the header.
Problem: I currently get a TypeError undefined because the context is not set before rendering. Is there a way to wait via async or something else for the data to set before loading?
My code block is below. I've been looking through a lot of questions on StackOverflow and blogs but to no avail. Thank you!
InnerList.js
componentDidMount() {
const { dtc_id } = this.props.match.params;
const {
setSpecificDtcCommentList,
} = this.context;
MechApiService.getSpecificDtcCommentList(dtc_id)
.then(res =>
setSpecificDtcCommentList(res)
)
}
renderSpecificDtcCommentListHeader() {
const { specificDtc = [] } = this.context;
return (
<InnerDtcCommentListItemHeader key={specificDtc.id} specificDtc={specificDtc} />
)
}
Context.js
setSpecificDtcCommentList = (specificDtcCommentList) => {
this.setState({ specificDtcCommentList })
this.setSpecificDtc(specificDtcCommentList)
}
setSpecificDtc = (specificDtcCommentList) => {
this.setState({ specificDtc: specificDtcCommentList[0] })
}
InnerListHeader.js
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
In general, you should always consider that a variable can reach the rendering stage without a proper value (e.g. unset). It is up to you prevent a crash on that.
For instance, you could rewrite you snippet as follows:
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{Boolean(specificDtc.dtc_id) && specificDtc.dtc_id.dtc}
</div>
</div>
);
}
When you make an api call you can set a loader while the data is being fetched from the api and once it is there you show the component that will render that data.
In your example you can add a new state that will pass the api call status to the children like that
render() {
const { specificDtc, fetchingData } = this.props;
if (fetchingData){
return <p>Loading</p>
}else{
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
}
``
in my case, i am calling external api to firebase which lead to that context pass undefined for some values like user. so i have used loading set to wait untile the api request is finished and then return the provider
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
GoogleAuthProvider,
signInWithPopup,
updateProfile
} from 'firebase/auth';
import { auth } from '../firebase';
import { useNavigate } from 'react-router';
import { create_user_db, get_user_db } from 'api/UserAPI';
import { CircularProgress, LinearProgress } from '#mui/material';
import Loader from 'ui-component/Loader';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState();
const [user_db, setUserDB] = useState();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const createUser = async (email, password) => {
const user = await createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const googleSignIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const logout = () => {
setUser();
return signOut(auth).then(() => {
window.location = '/login';
});
};
const updateUserProfile = async (obj) => {
await updateProfile(auth.currentUser, obj);
return updateUser(obj);
};
const updateUser = async (user) => {
return setUser((prevState) => {
return {
...prevState,
...user
};
});
};
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
setLoading(true);
if (currentUser) {
const user_db = await get_user_db({ access_token: currentUser.accessToken });
setUserDB(user_db);
setUser(currentUser);
setIsAuthenticated(true);
}
setLoading(false);
});
return () => {
unsubscribe();
};
}, []);
if (loading) return <Loader />;
return (
<UserContext.Provider value={{ createUser, user, user_db, isAuthenticated, logout, signIn, googleSignIn, updateUserProfile }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};

Categories