How can I avoid creating a new session when a user enters the same url and instead lets the new user join the host session? - node.js

I am trying to make an application which lets the first person to enter the url navigate to his specific hostId. The next person who comes to the same url is supposed to join the "hosts" session. This works, but it simultaneously creates a new session with the new user while also pushing the user to the hosts session. I do think it has something to do with the code where I am saying: "If (!host) { socket.emit("host:joined") }." And since the second user's host is "null" at the first render it also calls for emit("host:joined"), instead of only the "socket.emit("user:joined") (which is in the StartPage component).
How can the second user ONLY join the host session, without creating a new session?
const socket = socketio.connect(import.meta.env.VITE_APP_SOCKET_URL)
function App() {
const [host, setHost] = useState(null)
const [loading, setLoading] = useState(true)
const [showModal, setShowModal] = useState(false)
const [queueList, setQueueList] = useState([])
useEffect(() => {
if (!host) {
socket.emit("host:joined")
}
console.log("socket.id", socket.id)
socket.on("host:joined", (hostId) => {
setHost(hostId)
setLoading(false)
})
socket.on("user:joined", (id) => {
console.log("user joined session:", id)
})
socket.on("playlist", (data) => {
setQueueList(data)
})
if (host === undefined) {
socket.emit("disconnect")
}
console.log("HOST", host)
return () => {
console.log("Cleaning up")
socket.off("host:joined")
socket.off("disconnect")
socket.off("playlist")
}
}, [host, queueList])
return (
<div className="App">
{loading && <LoadingSpinner />}
<Routes>
<Route
path="/:id"
element={
<StartPage
socket={socket}
host={host}
queueList={queueList}
setShowModal={setShowModal}
showModal={showModal}
/>
}
/>
{host && <Route path="/" element={<Navigate to={`/${host}`} />} />}
</Routes>
</div>
)
}
export default App
Startpage component:
const StartPage = ({ socket, host, queueList, setShowModal, showModal }) => {
const { id } = useParams()
useEffect(() => {
if (id !== socket.id) {
socket.emit("user:joined", id)
}
}, [id])
return (
<>
</>
)
}
export default StartPage
Socket_controller:
let io = null
let sessions = []
// Create a session when host enters the site
const handleConnect = function (id) {
console.log("ID", id)
if (!id) {
console.log("Host joined with id:", this.id)
let session = {
id: this.id,
playlist: [],
users: [],
}
if (sessions.find((session) => session.id === this.id)) {
return
} else {
sessions.push(session)
this.join(session)
}
io.to(session).emit("host:joined", this.id)
} else {
console.log("User joined with id:", this.id)
const session = sessions.find((session) => session.id === id)
console.log("session", session)
session.users.push(this.id)
this.join(session)
io.to(session).emit("host:joined", id)
console.log("sessions", sessions)
}
}
/**
* Export controller and attach handlers to events
*/
module.exports = function (socket, _io) {
io = _io
// handle host connect
socket.on("host:joined", handleConnect)
socket.on("user:joined", handleConnect)
}
Backend and frontend consoles:
I have tried many different things, such as using a loadingspinner while host i loading. I've tried refactoring both in the controller and on the frontend. The positive thing is that it works as intended visually, which means that the two users are on the same session and can share information. However, I can see in the code that it is not corrent as new sessions are created, and also the second user is joining twice.

Related

React role based authentication if the two users are from two different tables

I'm new to React/Node and working on a learning project. It's a platform that connects users (freelancers) with nonprofit companies. I would like users to sign up and login as A) user or B) company. I can't figure out how to do this, and all the guides I found are for when your users are all coming from the same table, but with different auth levels (eg. user, admin, etc..).
In my case, it's different. users and companies are two different resources. A user can view /companies and click a button to connect to that company. A user can view a page that lists all their connections. Likewise, a company can login and view a page that lists all the users that connected with them.
Right now, the backend is working successfully. Both users/companies can signup/login, and you get a token back as expected (tested in Insomnia). I'm using JSON Web Tokens.
On the frontend, users can signup, login, make connections, and view their connections successfully. Now I just want companies to do the same, but have no idea how. I made an attempt at doing it, but when a company tries to login, they're directed to the homepage and they're not logged in. No error messages show up.
Not sure what code to post, but I will keep this concise. This is all the relevant code (shortened). I would appreciate any help, or pointers.
schema
CREATE TABLE companies (
company_handle VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
company_name TEXT NOT NULL
role TEXT DEFAULT 'company'
);
CREATE TABLE users (
username VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
role TEXT DEFAULT 'user'
);
CREATE TABLE connections (
username VARCHAR(25)
REFERENCES users ON DELETE CASCADE,
company_handle VARCHAR(25)
REFERENCES companies ON DELETE CASCADE,
PRIMARY KEY (username, company_handle)
);
Frontend
App.js
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [currentCompany, setCurrentCompany] = useState(null);
const [token, setToken] = useLocalStorage(TOKEN_LOCAL_STORAGE_ID);
const [connectionHandles, setConnectionHandles] = useState([]);
// Load user info from the API
useEffect(function loadUserInfo() {
async function getCurrentUser() {
if (token) {
try {
let { username } = jwt.decode(token);
let { companyHandle } = jwt.decode(token);
VolunteerApi.token = token;
if (username) {
let currentUser = await VolunteerApi.getCurrentUser(username);
setCurrentUser(currentUser);
}
if (companyHandle) {
let currentCompany = await VolunteerApi.getCurrentCompany(companyHandle);
setCurrentCompany(currentCompany);
}
} catch (err) {
console.error("Problem with the loadUserInfo function", err);
setCurrentUser(null);
setCurrentCompany(null);
}
}
}
getCurrentUser();
}, [token]);
// Login user function
async function loginUser(loginData) {
try {
let token = await VolunteerApi.loginUser(loginData);
setToken(token);
return {
success: true
};
} catch (err) {
console.error("Problem with the login function", err);
return {
success: false, err
};
}
}
// Login company function
async function loginCompany(loginData) {
try {
let token = await VolunteerApi.loginCompany(loginData);
setToken(token);
return {
success: true
};
} catch (err) {
console.error("Problem with the login function", err);
return {
success: false, err
};
}
}
return (
<BrowserRouter>
<UserContext.Provider value={{ connectionHandles, setConnectionHandles, currentUser, setCurrentUser, currentCompany, setCurrentCompany }}>
<div>
<Navigation />
<Routes loginUser={loginUser} loginCompany={loginCompany} />
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
api.js
class VolunteerApi {
static token;
static async request(endpoint, data = {}, method = "get") {
console.debug("API Call:", endpoint, data, method);
const url = `${BASE_URL}/${endpoint}`;
const headers = { Authorization: `Bearer ${VolunteerApi.token}` };
const params = (method === "get")
? data
: {};
try {
return (await axios({ url, method, data, params, headers })).data;
} catch (err) {
console.error("API Error:", err.response);
let message = err.response.data.error.message;
throw Array.isArray(message) ? message : [message];
}
}
// Login company
static async loginCompany(data) {
let res = await this.request(`auth/login-company`, data, "post");
return res.token;
}
// Login user
static async loginUser(data) {
let res = await this.request(`auth/login-user`, data, "post");
return res.token;
}
}
Backend
auth.js
router.post("/login-company", async function (req, res, next) {
try {
const { companyHandle, password } = req.body;
const company = await Company.authenticate(companyHandle, password);
const token = createToken(company);
return res.json({ token });
} catch (err) {
return next(err);
}
});
router.post("/login-user", async function (req, res, next) {
try {
const { username, password } = req.body;
const user = await User.authenticate(username, password);
const token = createToken(user);
return res.json({ token });
} catch (err) {
return next(err);
}
});
token.js
function createToken(user) {
console.assert(undefined,
"createToken passed user with an undefined user");
let payload = {
username: user.username,
companyHandle: user.companyHandle
};
return jwt.sign(payload, SECRET_KEY);
}
If I understand correctly what you wish to achieve is that your same app can be viewed with 2 different perspectives (User view or Company view) using who logged in as your flag to show the correct data. Having different roles for the same page can be tricky but thankfully there are a number of ways to achieve this.
What I recommend as the simplest approach would be conditional rendering.
When someone logs in as a user or a company you can save that detail to the browsers local storage using localStorage.setItem("UserType", "Example"); and you can get this information using localStorage.getItem("UserType");
Then when the user or company is in your page using that detail you can render the right elements like so:
{condition == true && (<> <Module/> </>)}
Now since we are using react we can import whole js files as modules. so you can have something that looks like this:
import UserPage from 'somewhere/User.js'
import CompanyPage from 'somewhere/Company.js'
function MainApp() {
const userOrCompany = localStorage.getItem("UserType")
return(
<>
{userOrCompany === 'User' && (<> <UserPage/> </>)}
{userOrCompany === 'Company' && (<> <CompanyPage/> </>)}
</>
);
}
export default MainApp;
Also, I recommend handling tokens from the backend for security reasons. That way you can condition your backend data to needing a token before returning anything :D

Receiving a status of 404 when implementing GET request - MERN Stack

I'm trying to display a particular group detail page from the group's list. I have managed to display all the group lists from the backend, but when it comes to displaying each group detail page, I'm receiving a status of 404. I don't understand what the issue is, why it can't find that group on that particular id.
On Backend, I made a getGroup controller:
export const getGroup = async (req, res) => {
// Route parameters are named URL segments that are used to capture the values specified at their position in the URL.
// Deconstructing it so that we can use it. It's like unpacking it.
const { id } = req.params
try {
// finding a group by it's Id
const group = await Group.findById(id)
// Send it in response
res.status(200).json(group)
} catch (error) {
// In case it didn't work out
res.status(404).json({ message: error.message })
}
}
Then Routes:
router.get('/:id', getGroup)
Main Route:
app.use('/groups', groupRoutes)
On Front End, I'm using Axios to create an API.
export const fetchGroup = (id) => API.get(`/groups/${id}`)
Using Redux for state management, so here is my action creator for a particular group.
// Will get a particular group
export const getGroup = (id) => async (dispatch) => {
try {
// Over here we are fetching all the data from api and dispatching it.
const { data } = await api.fetchGroup(id)
dispatch({type: FETCH_GROUP, payload: data})
} catch (error) {
console.log(error.message)
}
}
Reducer for fetching a group:
// Reducers take state and an action as arguments and return a new state in result.
const groups = ( state = { groups: [] }, action ) => {
switch (action.type) {
case FETCH_ALL:
return {
...state,
groups: action.payload
}
case FETCH_GROUP:
// set to group because we are getting a single group
return { ...state, group: action.payload }
default:
return state
}
}
export default groups
My GroupPage.js, where I'm dispatching my functions.
const GroupPage = () => {
const { group } = useSelector((state) => state.groups)
const dispatch = useDispatch()
const { id } = useParams()
useEffect(()=> {
dispatch(getGroup(id))
}, [id])
//if(!group) return null
return (
isLoading ? <div style={{width: 50, height: 50 }}>
<CircularProgressbar value={66} text={66} />
</div> : (
<div>
<Container>
<Top>
<div>
<h1>{group.groupName}</h1>
<p>{group.location}</p>
<p>{group.members}</p>
</div>
</Top>
</Container>
</div>
)
)
}
export default GroupPage
From the above code, if I check for a group:
if(!group) return null
Then I'm getting the error
typeError: Cannot read property 'groupName' of undefined
Not sure what the problem is. I have also tried the data fetching with the loading state, but that didn't resolve the issue either.
Please, any help would be appreciated.

Channel not being connected when trying to enable one on one chat in Stream (JS)

I have an app I'm building where one user (called admin) has a one on one channel with most users. My understanding is that only one channel will exist when I create a one on one chat, regardless of who (admin or user) creates it first. My issue is that when I add a user and a new one on one chat should be established, other channels are being duplicated in my channel list as well. Either that, or sometimes the channel just doesn't connect at all, and the user is left without a chat. The code/pseudo-code I am using is below, does anyone know how to create multiple one on one chats using stream, or why certain channels might be connecting twice?
P.S. I'm editing the code to simplify it to only relevant parts here on stack overflow, so please ignore any simple syntax errors below. Thanks!
setupUserChats = async (chatClient) => {
let { currentUser, participants } = this.props;
if (the current user is an admin) {
for (let i = 0; i < participants.length; i++) {
let participant = participants[i];
if (if the participant is a user) {
let conversation = await chatClient.channel('messaging', {
name: participant.name,
members: [`${currentUser.participant_id}`, `${participant.participant_id}`]
});
await conversation.watch();
}
}
}
if (currentUser is a user) {
for (let i = 0; i < participants.length; i++) {
let participant = participants[i];
if (participant is an admin) {
let conversation = await chatClient.channel('messaging', {
name: currentUser.name,
members: [`${currentUser.participant_id}`, `${participant.participant_id}`]
});
await conversation.watch();
}
}
}
this.setState({ chatClient: chatClient });
}
I call another function (outlined below) to set up the basic chat, and then call the above code inside of this next function:
setupChat = async () => {
let { currentUser, participants } = this.props;
let chatClient = await new StreamChat(process.env.REACT_APP_STREAM_API_KEY);
let serverResult = await getStreamToken(currentUser.participant_id);
let userToken = serverResult.token;
await chatClient.setUser(
{
id: currentUser.participant_id,
name: currentUser.name,
},
userToken,
);
const moderatorToAdmin = await chatClient.channel('messaging', `conference-ID`, {
name: "moderator-chat"
});
await moderatorToAdmin.watch();
this.setupUserChats(chatClient);
moderatorToAdmin.addMembers([`${currentUser.participant_id}`]);
this.setState({ chatClient: chatClient });
};
And then its all rendered here:
render() {
const { currentUser } = this.props;
const filters = { members: { $in: [currentUser.participant_id] } }
return (
<div>
<Chat client={this.state.chatClient} theme={'messaging light'} >
<div className="title">Chat</div>
<ChannelList
filters={filters}
Preview={MyChannelPreview}
/>
<Channel Message={MyMessageComponent} >
<Window>
<MessageList />
<MessageInput />
</Window>
<Thread />
</Channel>
</Chat>
</div>
);
}
class MyChannelPreview extends React.Component {
render() {
const { setActiveChannel, channel } = this.props;
return (
<div className={`channel_preview` + currentChannelClass}>
<a href="#" onClick={(e) => setActiveChannel(channel, e)}>
{channel._data.name || channel.data.name}
</a>
</div>
);
}
}

Where to set up notifications/status updates for app?

I am implementing status updates/notifications, but I do not know where to make the calls from. Right now I am making them in my container app AsyncApp.js that holds all the navigation bars and my components(except login/logout etc...) the issue is that when ever I go to a new component my notifications start from fresh, which is wrong because I want it stay continuous throughout all pages.
AsyncApp.js
class AsyncApp extends Component {
constructor(props){
super(props)
this.startTimer = this.startTimer.bind(this)
this.handleEvent = this.handleEvent.bind(this)
this.handleClose = this.handleClose.bind(this)
this.state = {
redirect: false,
maxSessionInactivity: null,
showAlert: false,
sinceLastCheck: ''
}
}
async componentDidMount() {
this.show = null
let self = this
let messages;
const { dispatch } = this.props
await document.body.addEventListener("keypress", this.handleEvent);
await document.body.addEventListener("click", this.handleEvent);
await fetch('/api/getStatus').then(res => res.json()).then(function(res){
// if(!res.data.is_active){
// self.setState({redirect: true})
// }
console.log("IN GET STATUS ", res)
})
.catch(err => self.setState({redirect: true}))
await fetch('/api/getFirstNotification')
.then(res => res.json())
.then(function(res){
// if(res.status.errorOccured){
// self.setState({redirect: true})
// }
messages = res.data.messages
dispatch(updateMessages(res.data.messages))
self.setState({sinceLastCheck: res.data.since_last_check})
})
.catch(err => self.setState({redirect: true}))
//await fetch('/api/getStatus').then(res => res.json()).then(res => this.setState({maxSessionInactivity: res.data.session_inactivity_minutes - 1 * 1000}));
await this.startTimer()
await console.log("STATE J", this.state)
await this.interval(messages)
await this.notifications()
}
startTimer() {
this.firstTimer = setTimeout(function() {
this.setState({showAlert: true})
}.bind(this), 100000);
this.lastTimer = setTimeout(function() {
this.setState({redirect: true})
}.bind(this), 600000)
}
handleEvent(e){
console.log("event", e)
clearTimeout(this.firstTimer)
clearTimeout(this.lastTimer)
this.startTimer()
}
async interval(messages){
this.intervalStatus = await setInterval(async () => {
await this.notify(messages)
}, 15000)
};
async notifications(){
const { dispatch } = this.props
this.newNotifications = await setInterval( async () => {
let data = { since_last_checked : this.state.sinceLastCheck }
let res1 = await fetch('/api/getNotifications', {
method:'POST',
headers: {
'Content-type': 'application/json',
'accept': 'application/json'
},
body:JSON.stringify(data)
})
.then(res => res.json())
.catch(err => console.log(err))
console.log("NOTIFICATIONS NEXTTT", res1)
if(res1 === undefined || res1.data === undefined || res1.data === null){
this.setState({redirect: true})
}
if(res1 != undefined && res1.data != null) dispatch(updateMessages(res1.data.messages))
let res2 = await fetch('/api/getStatus')
.then(res => res.json())
.catch(err => console.log(err))
console.log("STATUSS", res2)
if(res2 === undefined || res2.data === undefined || res2.data === null || res2.data.is_active === 'N' || res2.data.status === 'closed'){
this.setState({redirect: true})
}
}, 5000)
}
handleClose(event){
this.setState({showAlert: false})
}
componentWillUnmount(){
console.log("componentWillUnmount!!!!")
clearInterval(this.newNotifications)
clearInterval(this.intervalStatus)
clearTimeout(this.firstTimer)
clearTimeout(this.lastTimer)
document.body.removeEventListener("keypress", this.handleEvent);
document.body.removeEventListener("click", this.handleEvent);
}
notify(arr){
if(arr === undefined) return null
if(typeof arr === 'string'){
return toast.success(`${arr}`)
}
if(arr.length < 4){
let messages = arr.map(message => toast.success(`${message.message_text}`))
return messages
} else {
return toast.success(`You have ${arr.length} new Notifications!`)
}
};
render() {
const { classes } = this.props
if (this.state.redirect) return <Redirect to="/logout" />
return (
<div>
<ToastContainer />
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.showAlert}
autoHideDuration={6000}
onClose={this.handleClose}
>
<MySnackbarContentWrapper
onClose={this.handleClose}
variant="warning"
message="Your session will expire in one minute!"
/>
</Snackbar>
<ThemeProvider theme={theme}>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer}>
<Hidden xsDown implementation="css">
<Navigator PaperProps={{ style: { width: drawerWidth } }} />
</Hidden>
</nav>
<div className={classes.appContent}>
<Header onDrawerToggle={this.handleDrawerToggle} />
<main className={classes.mainContent}>
<div>
<Switch>
<Route exact path="/EditContracts/:contractId/sections/:section" component={EditSection} />
<Route exact path="/EditContracts/:contractId" component={EditContract} />
<Route exact path="/EditUsers/:userId" component={EditUser} />
<Route exact path="/EditEndpoints/:epId" component={EditEndpoint} />
<Route exact path="/EditContracts/:contractId/addSection" component={CreateSection} />
<Route exact path="/Contracts/List" component={Contracts} />
<Route exact path="/Contracts/Create" component={CreateContract} />
<Route exact path="/Contracts/Import" component={ImportContract} />
<Route exact path="/Users/List" component={Users} />
<Route exact path="/Users/Create" component={CreateUser} />
<Route exact path="/Endpoints/Create" component={CreateEndpoint} />
<Route exact path="/Endpoints/List" component={Endpoints} />
<Route exact path="/Pug_Community" component={PugCommunity} />
<Redirect exact from="/Users" to="/Users/List" />
<Redirect exact from="/Endpoints" to="/Endpoints/List" />
<Redirect exact from="/Contracts" to="/Contracts/List" />
</Switch>
</div>
</main>
</div>
</div>
</ThemeProvider>
</div>
)
}
}
App.js
export default class App extends Component {
render() {
return (
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route exact path="/changePassword" component={ChangePassword} />
<Route exact path="/logout" component={Logout} />
<Redirect exact from="/" to="/signin" />
<Route path="/" component={AsyncApp} />
</Switch>
)
}
}
Root.js
const store = configureStore()
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
)
}
}
At a first glance, it looks like you are doing more work than you need to in your AsyncApp using timers. I can think of two possible solutions that could simplify your workflow:
Since you are already using redux, I would take your fetch() calls out of the AsyncApp component entirely and store them in async redux action creators using redux-thunk.
Set only one timer on AsyncApp, call your async action methods like updateNotifications() and updateStatus() every interval, and then check whether the returned data is new in a redux reducer.
Your reducer can then also be the place that determines whether notifications have been "read". If you have control over the server and database, you will want a server side attribute to also store whether notifications have been read so that a total cache refresh would not redisplay old notifications as new.
While the first suggestion will likely be more intuitive with your current app design, I would argue a more elegant solution would use some kind of websocket implementation. Subscribing to a socket channel using socket.io or PusherJS. Implementing this kind of change requires server changes too, so it may not be feasible for you, but it would remove the need for timers. Pusher, for example, can be configured to subscribe to a notification channel and kick off a redux action with the new data anytime a new notification is received. See: pusher-redux.
Here are some ideas for how to organize the code for the first suggestion:
AsyncApp.js
import { updateNotifications, updateStatus } from './actionCreators.js'
class AsyncApp extends Component {
// ... other necessary code
public interval: any;
public getData = () => {
const { dispatch } = this.props
dispatch(updateNotifications())
dispatch(updateStatus())
}
public componentWillMount(){
//Grab data when the component loads the first time
this.getData()
// Then set a single interval timer to handle both data updates.
this.interval = setInterval(()=>{
this.getData()
},30000)
}
public componentWillUnMount(){
// clear the interval when the component unmounts.
clearInterval(this.interval)
}
}
actionCreators.js
// action type constants
export const RECEIVE_NOTIFICATIONS = 'RECEIVE_NOTIFICATIONS'
function receiveNotifications(notifications){
return({
type: RECEIVE_NOTIFICATIONS,
notifications,
})
}
// Here's the part that uses thunk:
export function updateNotifications() {
return function(dispatch) {
return fetch(`api/getNotifications`)
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receiveNotifications(json))
)
}
}
// ... use the same pattern for updateStatus()
reducers.js
import { RECEIVE_NOTIFICATIONS } from './actionCreators.js'
const notifications = (state=[], action) => {
switch(action.type){
case RECEIVE_NOTIFICATIONS:
/* Depending on the structure of your notifications object, you can filter
or loop through the list to determine whether or not any items in the
list are new. This is also where you can toast if there are N number of
new notifications. */
return action.notifications
default:
return state
}
}

How to fetch (Express) data ONLY once the (React) form-submitted data has been successfully received and served?

I'm currently building a league of legends (a MOBA or multiplayer online battle arena game) search-based web app that essentially allows the user to search for their summoner's name and obtain general information regarding their search input. (The data is provided by the game's own third-party api)
I've been able to successfully retrieve the form data and perform the intended backend processes, however, upon the client's initial render, my results-listing component is already trying to fetch the nonexistent processed data.
How do I prevent the server request from firing until the server has actually successfully served the data?
(abridged single-component client example)
the summoner data endpoint is set to http://localhost:3001/api/summoner
server does not contain any additional routes
const App = () => {
const [summName, setSummName] = useState('');
const summonerFormData = new FormData();
// let data;
const findSummoner = () => {
summonerFormData.set('summonerName', summName);
}
// problem here
const data = axios.get('http://localhost:3001/api/summoner');
// axios.get('http://localhost:3001/api/summoner')
// .then(res => {
// data = res;
// });
return (
<div>
<form
method="POST"
action="http://localhost:3001/api/summoner"
onSubmit={findSummoner}
>
<input
value={summName}
name="summName"
onChange={e => setSummName(e.target.value)}
/>
<button type="submit">submit</button>
</form>
{data !== undefined &&
<div className="results">
data.map(match => {
<div>
<p>{match.kills}</p>
<p>{match.deaths}</p>
<p>{match.assists}</p>
</div>
})
</div>
}
</div>
)
}
Here's the Repo for some more context, but please don't hesitate to ask if you need more information or have any questions at all!
I really appreciate any help I can get!
Thanks!
Edits:
I've also tried using the useEffect hook considering the lifecycle point I'm trying to fetch would be componentDidMount, but wasn't quite sure what the solution was. Doing more research atm!
Close, but no cigar. Request gets stuck at 'pending'.
let data;
const fetchData = () => {
axios.get('http://localhost:3001/api/summoner');
};
useEffect(() => {
if (summName !== '') {
fetchData();
}
}, summName);
I tried putting the axios request within an async function and awaiting on the request to respond, and it seems to be working, however, the server is still receiving undefined when the client starts, which then is attempting to be fetched, never allowing the promise to be fulfilled.
const fetchData = async () => {
await axios
.get('http://localhost:3001/api/summoner')
.then(res => {
data = res;
})
.catch(() => {
console.log('error');
});
};
useEffect(() => {
fetchData();
}, [])
So I took the advice and recommendations from #imjared and #HS and I'm literally so close..
I just have one more problem... My data-mapping component is trying to map non-existent data before actually receiving it, giving me an error that it's unable to map match of undefined..
const [modalStatus, setModalStatus] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({ hits: [] });
const [summName, setSummName] = useState('');
const [summQuery, setSummQuery] = useState('');
const summonerFormData = new FormData();
const prepareResults = async () => {
await setSummQuery(summName);
};
const findSummoner = async () => {
setLoading(true);
setModalStatus(false);
await summonerFormData.set('summonerName', summQuery);
};
useEffect(() => {
const fetchData = async () => {
if (summQuery) {
setData({ hits: [] });
console.log('fetching');
await axios
.get('http://localhost:3001/api/summoner')
.then(res => {
setData(res.data);
setLoading(false);
setModalStatus(true);
return data;
})
.catch(error => {
console.log(error);
});
}
};
fetchData();
}, [summQuery]);
SUCCESS! Thank you guys! Here's what ended up working for me:
const findSummoner = async () => {
setSummQuery(summName);
};
useEffect(() => {
setData({ hits: [] });
summonerFormData.set('summonerName', summQuery);
const fetchData = async () => {
setModalStatus(false);
setLoading(true);
if (summQuery !== '') {
setLoading(true);
console.log('fetching');
await axios
.get('/api/summoner')
.then(res => {
setData({
hits: res.data,
});
setError(false);
setLoading(false);
setModalStatus(true);
return data;
})
.catch(() => {
setError(true);
console.log('error');
});
}
};
if (summQuery !== '') {
fetchData();
}
}, [summQuery]);
This flow will help you design better -
1. User - input
2. Hit - search
3. Set loading in state - true,
5. Set data in state - empty
6. Call api
7. Get data
8. Then, set data in state
6. Set loading in state - false
Along the side in the render/return -
1. if loading in the state - indicate loading.
2. if loading done( false ) and data is not empty - show data.
3. if loading done and data is empty - indicate 'not-found'.
Coming to the initial render part - the axios.get() calls the api, which should only be initiated once the form is submitted in the case. Therefore, that logic should be moved inside the event-handler.

Resources