React state lost when user refreshes page - javascript

I'm new to React/Node and working on a learning project. It's a platform that connects users (freelancers) with nonprofit companies. When a user logs in, they can view a list of companies and click a button to connect with that company. They can then go to the UserConnections page to view all the companies they connected with.
When they click the 'connect' button, the connection is made in the database, and the button becomes disabled. This is currently working correctly.. unless you refresh the page, in which case the button becomes clickable again.
I'm probably not using state correctly. I'm tracking two different states. The first is when it's a "fresh" connection the user just made. The second is when they visit their UserConnections page, I'm retrieving their "old" connections from the database.
What can I do to make sure the state of a connection persists if the user refreshes the page, or if they come back later? My code is below (shortened to only include relevant code)
App.js
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [connectionHandles, setConnectionHandles] = useState([]);
// Check if connected to this company
function hasConnectedToCompany(companyHandle) {
return connectionHandles.includes(companyHandle);
}
// Make the connection in the database
function connectToCompany(companyHandle) {
if (hasConnectedToCompany(companyHandle)) return;
VolunteerApi.connectToCompany(currentUser.username, companyHandle);
setConnectionHandles([...connectionHandles, companyHandle]);
}
return (
<BrowserRouter>
<UserContext.Provider value={{ connectionHandles, setConnectionHandles, currentUser, setCurrentUser, hasConnectedToCompany, connectToCompany }}>
<div>
<Navigation />
<Routes />
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
CompanyDetail.js
function CompanyDetail() {
const { companyHandle } = useParams();
const [company, setCompany] = useState(null);
const { currentUser, hasConnectedToCompany, connectToCompany } = useContext(UserContext);
const [connected, setConnected] = useState();
React.useEffect(function updateConnectedStatus() {
setConnected(hasConnectedToCompany(companyHandle));
}, [companyHandle, hasConnectedToCompany]);
// Handle connect
async function handleConnect(evt) {
if (hasConnectedToCompany(companyHandle)) return;
connectToCompany(companyHandle);
setConnected(true);
let connectUserInDb;
try {
connectUserInDb = await VolunteerApi.connectToCompany(currentUser.username, companyHandle);
} catch (err) {
setFormErrors(err);
return;
}
}
if (currentUser) {
return (
<div>
<h1>{company.companyName}</h1>
<p>
<button onClick={handleConnect} disabled={connected}> {connected ? "Connected" : "Connect"} </button>
</p>
</div>
);
}
}
UserConnections.js
function UserConnections() {
const { currentUser, connectionHandles } = useContext(UserContext);
const [companies, setCompanies] = useState([]);
useEffect(() => {
let isMounted = true;
const connections = currentUser.connections.concat(connectionHandles);
const comps = connections.map((c) => VolunteerApi.getCurrentCompany(c));
Promise.all(comps).then(comps => isMounted && setCompanies(comps));
return () => { isMounted = false };
}, [currentUser, connectionHandles]);
if (!companies || companies.length === 0) {
return (
<div>
<div>
<p>You have no connections</p>
</div>
</div>
)
} else {
return (
<div>
<div>
<h1>Connections</h1>
{companies && companies.map(c => (
<CompanyCard
key={c.companyHandle}
companyHandle={c.companyHandle}
companyName={c.companyName}
/>
))}
</div>
</div>
);
}
};

the state will reset when the page refreshes, but you can either use the DB to save the connection, or use local storage.

Indeed, State is lost with React when you Refresh the page.
You should use another method to keep this information even if the user refresh the page. Here are the options:
In the database. Could work if the user is behind an authentification (Login password). Example User name: Paul, id; 3131, did click on the button "connect". In your database, you add a column in the table User called userConnect = true
In the url. As soon as the user click on the button "connect" you change the URL with React router. For example the URL was mydomain.com, and after clicking it becomes mydomain.com?clicked=true. If the user refresh your page, you still have the information about the user who clicked on the button.
In a cookie (More info here https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
In local Storage (More info here https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local)

Related

Issue with 1st render of async local JSON data

I'm having an issue when it comes to rendering some data out in my React app. I have a page /users that renders a list of users and clicking on a specific user routes the client to a page with more info on that user /users/:id. That page has a component with user data inside of it and that data is being fetched from a local JSON file. The issue is that when I click on one of the users from the /users page, the page for that specific user breaks. Console is telling me userData is undefined.
I have attempted to render the specific user page once the data has been fetched but I don't think I'm doing it correctly. I have tried setting an isLoading state with useState as well as conditionally rendering the component based on the state of the data being fetched but I'm still not having much luck. Below is the User page and the UserInfo component.
User page
function User() {
const [userData, setUserData] = useState([]);
const { id } = useParams();
const fetchData = async () => {
const response = await fetch(`../data/userData/${id}.json`);
const data = await response.json();
setUserData(data);
};
useEffect(() => {
fetchData;
}, []);
return (
<div>
{userData ? (
<UserInfo userData={userData} />
) : (
<>
<h1>Loading...</h1>
</>
)}
</div>
);
}
UserInfo component
function UserInfo({ userData }) {
return (
<div className='userInfo__details'>
<div className='userInfo__name'>
<h1>{userData[0].name}</h1>
</div>
</div>
);
}
The page is being rendered before the userData is being retrieved by the fetch request and that's causing the page to break. Ideally I'd like to have a loading spinner or something while the data is retrieved but anytime I've been able to having else condition render to indicate the data is still being fetched, it just hangs there and never actually renders the page with the fetched data.
Any help is appreciated.
[] as your default state is will return as true which renders the UserInfo component before time.
You can do this instead
return (
<div>
{!!userData.length ? (
<UserInfo userData={userData} />
) : (
<>
<h1>Loading...</h1>
</>
)}
</div>
);

Issues rendering the right dashboard UI user based on roles

I am trying to render UI in my project based on selected roles (brands, agency, influencer) on click. However, the logic that I am putting together is not loading the right UI and I don't quiet understand why not.
I have tried moving the role and setRole to the top component and passed the props down to the child components that read role and updated it via setRole so that I can have the state to be available in two places.
I also set a logic that should display components based on if the role equals the value of the buttons.
What happened was the components weren't loading upon clicking the function that handles click. However, logging out to the console if the role equals the value of the clicked button returns true, the right string that the logic was based on.
What I am expecting to happen is to load the component e.g: "Brands" when users click and select "brands" which is the value of the clicked button. Vise versa for the other components.
My code is as follows:
import { useState } from 'react';
import { useSession } from 'next-auth/react';
import Brands from './Brands';
import Agency from './Agency';
import CreatorsDash from './CreatorsDashboard';
export default function FirstPageModal({ role: userRole }) {
const [role, setRole] = useState(userRole);
const { data: session } = useSession();
const handleClick = (e) => {
e.preventDefault();
let buttonValue = e.target.value;
const clickedRole = role?.map((user) => {
let { role } = user;
if (buttonValue) {
userRole = { role: buttonValue };
}
return { userRole };
});
console.log(clickedRole); //Returns an array
clickedRole.map((item) => {
const { role } = item.userRole;
console.log(role); //Returns string ("agency" / "brands" / "Influencer")
if (session && role === 'brands') {
console.log(role); //This logs "brands" as expected but doesn't return the component
// return <Brands session={session} role={role} />;
} else if (session && role === 'agency') {
return <Agency session={session} role={role} />;
} else if (session && role === 'Influencer') {
return <CreatorsDash session={session} role={role} />;
} else {
console.log('Select a role');
}
});
};
return (
<>
<div className="">
<button type="button" className="" onClick={handleClick} value="agency">
As an Agency
</button>
<button type="button" className="" onClick={handleClick} value="brands">
As a Brand
</button>
<button
type="button"
className=""
onClick={handleClick}
value="Influencer"
>
As an Influencer
</button>
</div>
</>
);
}
Returning a component from an onClick handler doesn't automatically render the component. One thing you could do is to keep track of the role in the state and then put the <Brands /> <Agency/> and <CreatorsDash /> components in the render function and dynamically show/hide them like {role === "brands" && <Brands />. This can also be done with css, although the benefits of this are not so clear,.
Side note, it is very helpful to post a codepen with your code, especially as your code gets more complicated

Render a react component in a new window

I am developing a Certificate Management System where after all the processes have been done, the user may print a certificate.
I am struggling to implement such that upon clicking the print button, a new tab will open containing the ready to print HTML certificate so that the user will only CTRL + P to have the certificate printed.
How do i render my react certificate component in a new window? Such that i would only pass the props which are the data to be put into the certificate e.g., name, date etc.. like <Certificate name={john} />
I have tried implementing the npm react-new-window but it does not work with
<Button onclick={() => {
<NewWindow>
<CertificateComponent>
</NewWindow>
}}
>
PRINT BUTTON
</Button>
I have looked into react portals but most use cases are for Modals, which is where my "PRINT" button is rendered.
Sorry for the bad english/explanation. Thank you!
New Solution based on CreatePortal
import React, { useEffect, useCallback, useMemo, useState } from "react";
import { render, createPortal } from "react-dom";
const App = () => {
const [isOpen, setOpenState] = useState(false);
const open = useCallback(() => setOpenState(true));
const close = useCallback(() => setOpenState(false));
return (
<div>
<h1>Portals in React</h1>
<button onClick={open}>Open</button>
<button onClick={close}>Close</button>
{isOpen && (
<NewWindow close={close}>
Example <button onClick={close}>Close</button>
</NewWindow>
)}
</div>
);
};
const NewWindow = ({ children, close }) => {
const newWindow = useMemo(() =>
window.open(
"about:blank",
"newWin",
`width=400,height=300,left=${window.screen.availWidth / 2 -
200},top=${window.screen.availHeight / 2 - 150}`
)
);
newWindow.onbeforeunload = () => {
close();
};
useEffect(() => () => newWindow.close());
return createPortal(children, newWindow.document.body);
};
render(<App />, document.getElementById("root"));
There can be multiple approaches for this.
Approach 1:
Create a new route and map the certificateComponent to it, make sure it doesn't have any authentication or any dependency to it.
Store the required data for certificateComponent either in session storage or local storage.
When the user clicks on print button, redirect the user to this new route using window.open("http://localhost:port/newroute").
In certificateComponent read the values stored in session/local storage and map it accordingly.
Approach 2:
Make the certificate component as an overlay which occupies the entire screen which shows up when the user click on print button.
If any UI elements need to be hidden, you can do something as shown below:
printFn = function() {
// show the certificate component
// hide the ui elements not required
// window.print()
}
This worked for me
const myWindow: any = window.open('','', 'height: 500;width:500');
ReactDOM.render(<Yourcomponent prop={propValue} /> , myWindow.document.body);
myWindow.document.close();
myWindow.focus();
myWindow.print();
myWindow.close();

Updating React state based on what's in the database

Hi I'm new to Node/React, and I'm creating a learning project. It's platform that connects freelancers with nonprofit companies. I would like users to click a button to connect to a company. Once this is clicked, the user will have that company added as a connection in the database, and the button will become disabled and remain disabled in future user sessions.
Right now adding the user<->company connection to the database is working, and the "connect" button is working. Once clicked, it become disabled like I wanted to. I have React state in place. The problem is once the user logs out and comes back in, and views a company he's already connected to, the "connect" button is available to be clicked again.
How do I change the React state based on what's in the database? How do I make it so that if the user is already connected to this one company from an older session, the 'connect' button will remain disabled? This is what I have so far (shortened):
schema
CREATE TABLE companies (
company_handle VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
company_name TEXT NOT NULL,
num_employees INTEGER CHECK (num_employees > 0),
);
CREATE TABLE users (
username VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
email TEXT NOT NULL CHECK (position('#' IN email) > 1),
skill TEXT NOT NULL
);
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)
);
App.js
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [token, setToken] = useLocalStorage(TOKEN_LOCAL_STORAGE_ID);
const [connectionHandles, setConnectionHandles] = useState(new Set([]));
function hasConnectedToCompany(companyHandle) {
return connectionHandles.has(companyHandle);
}
function connectToCompany(companyHandle) {
if (hasConnectedToCompany(companyHandle)) return;
VolunteerApi.connectToCompany(currentUser.username, companyHandle);
setConnectionHandles(new Set([...connectionHandles, companyHandle]));
}
return (
<BrowserRouter>
<UserContext.Provider value={{ currentUser, setCurrentUser, hasConnectedToCompany, connectToCompany }}>
<div>
<Navigation logout={logout} />
<Routes loginUser={loginUser} signupUser={signupUser} loginCompany={loginCompany} signupCompany={signupCompany} />
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
CompanyDetail.js
function CompanyDetail() {
const { companyHandle } = useParams();
const [company, setCompany] = useState(null);
const { currentUser, hasConnectedToCompany, connectToCompany } = useContext(UserContext);
const [connected, setConnected] = useState();
const [formErrors, setFormErrors] = useState([]);
React.useEffect(function updateConnectedStatus() {
setConnected(hasConnectedToCompany(companyHandle));
}, [companyHandle, hasConnectedToCompany]);
useEffect(function getCompanyDetail() {
async function getCompany() {
setCompany(await VolunteerApi.getCurrentCompany(companyHandle));
}
getCompany();
}, [companyHandle]);
async function handleConnect(evt) {
if (hasConnectedToCompany(companyHandle)) return;
connectToCompany(companyHandle);
setConnected(true);
let connectUserInDb;
try {
connectUserInDb = await VolunteerApi.connectToCompany(currentUser.username, companyHandle);
} catch (err) {
setFormErrors(err);
return;
}
}
if (currentUser) {
return (
<div>
<h1>{company.companyName}</h1>
<h4>{company.numEmployees}</h4>
<button onClick={handleConnect} disabled={connected}> {connected ? "Connected" : "Connect"} </button>
</div>
);
}
}
What you could do is make an API call after the user logs in to retrieve all the companies that a user is connected to from the database. Store the results in react state so your app will then be able to check whether or not the user has connected to this company before.

Check changes before routing in React / Next js

I am having a Next JS app where there are very simple two pages.
-> Home page
import Header from "../components/header";
const handleForm = () => {
console.log("trigger");
};
export default () => (
<>
<Header />
<h1>Home</h1>
<form onSubmit={handleForm}>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit"> Login </button>
</form>
</>
);
-> About page
import Header from "../components/header";
export default () => (
<>
<Header />
<h1>About us</h1>
</>
);
Requirement:
-> Home page has a login form
-> If user started typing in any of the fields then without submitting the form, if he tries to move to About us page then a warning needs to be displayed something similar like beforeunload_event.
I am not sure how we can handle it in react as I am new to it.. Kindly please help me to handle a alert if user trying to navigate to other url while editing the form fields..
From my understanding, you can achieve your goal by listen the event routeChangeStart as then throws exception in case of rejecting to move the target url.
I forked above codesandbox and created a simple demo based on your idea which doesn't allow to switch page in case of username having value (form is dirty).
Here is the general idea:
import router from "next/router";
export default () => {
// Assume this value holds the status of your form
const [dirty, setDirty] = React.useState();
// We need to ref to it then we can access to it properly in callback properly
const ref = React.useRef(dirty);
ref.current = dirty;
React.useEffect(() => {
// We listen to this event to determine whether to redirect or not
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, []);
const handleRouteChange = (url) => {
console.log("App is changing to: ", url, ref.current);
// In this case we don't allow to go target path like this
// we can show modal to tell user here as well
if (ref.current) {
throw Error("stop redirect since form is dirty");
}
};
return (
// ...
)
}
The link codesandbox is here https://codesandbox.io/s/react-spring-nextjs-routes-forked-sq7uj

Categories

Resources