React router : update props - javascript

I am new to react, I have a component UpdateInvoice which has as props idInvoice and a boolean isNew. this component do two things if isNew is true the component will add a new invoice otherwise, it will update an existing invoice identied by its idInvoice.
I have also ListInvoices component. I want when I click on a button "NEW INVOICE" in this component I will be able to call my UpdateInvoice so I used React Router.
The problem
the props I send from ListInvoices to UpdateInvoice are empty!
in index.tsx
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="UpdateInvoice" element={<UpdateInvoice {...{idInvoice:'', isNew: null}} />} />
</Routes>
</BrowserRouter>,
);
in ListInvoices.tsx
<Button
variant="contained"
className="add"
onClick={() => navigate("/UpdateInvoice", {state: {idInvoice: '', isNew: true }})}
startIcon={<AddIcon />}>
NEW INVOICE
</Button>
I can't send the right props from index.tsx because ListInvoices who has idInvoice and isNew informations
UPDATE :
ListInvoices.tsx
const [data, setData] = React.useState([] as any[]);
//when the user is authenticated using an api, I update headers value
const [headers, setHeaders] = React.useState(
{
'Authorization': ``,
'My-Custom-Header': ''
});
useEffect(() => {
if(headers.Authorization != ''){
getData(); // I fetch data from an api
}
}, [headers])
const columns: GridColDef[] = [
{ field: 'idInvoice', headerName: 'ID', minWidth: 160 },
//more columns..
]
return (
<div>
<Button
variant="contained"
className="add"
onClick={() => navigate("/UpdateInvoice", {state: {idInvoice: '', isNew: true }})}
startIcon={<AddIcon />}>
NEW INVOICE
</Button>
<Paper sx={{width: '100%', p:1}} component="ul">
<div style={{ height: 400, width: '100%'}}>
{data.length != 0 ?
<DataGrid
rows={data}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
/> : <CircularProgress className='progress'/>}
</div>
</Paper>
</div>
);

All I had to do is using useLocation hook
in UpdateInvoice
const location = useLocation();
const [invoiceState, setInvoiceState] = React.useState<InvoiceProps>(location.state as InvoiceProps);
React.useEffect(() => {
setInvoiceState(location.state as InvoiceProps);
}, [location])
location variable contains the state I set here
<Button
variant="contained"
className="add"
onClick={() => navigate("/UpdateInvoice", {state: {idInvoice: '', isNew: true }})}
startIcon={<AddIcon />}>
NEW INVOICE
</Button>
This tutorial helped me

Related

How to pass a function to state in react router v6

I want to share state between two routes when I click on the link for one of the routes (NewUser). The state that I want to share and the logic modifying it are both held in the Users route. I want to pass the logic to change the state to the NewUsers route.
When I pass a string to the state object in router Link, I am able to access it in the NewUsers component. However, I get null when I pass a function.
I know that I can use context/redux, but I would prefer if I can do it this way.
Users route:
function Users() {
const [users, setUsers] = useState([]);
return (
<Card sx={{ padding: "2rem", mt: "2rem" }}>
<MDBox
display="flex"
flexDirection="row"
justifyContent="space-between"
>
<MDTypography variant="body2">{`You currently have ${users.length} users`}</MDTypography>
<MDButton variant="gradient" color="info" size="small">
<Link to="/settings/users/new-user" state={setUsers: setUsers}> //this is how I want to pass the state
<MDBox
display="flex"
alignItems="center"
color="white"
fontWeight="normal"
>
<Icon>add</Icon> Add New User
</MDBox>
</Link>
</MDButton>
</MDBox>
</Card>
NewUsers route:
function NewUser({history}) {
const location = useLocation();
const saveChanges = (e) => {
location.state.setUsers({
fname: values.firstName,
lname: values.lname,
email: values.email,
});
navigate("/settings/users");
};
return(
<MDBox py={3} mb={20} height="62vh">
<Grid
container
justifyContent="center"
alignItems="center"
sx={{ height: "100%", mt: 0 }}
>
<Grid item xs={12} lg={12}>
<Formik
initialValues={initialValues}
validationSchema={currentValidation}
onSubmit={(values) => {
setValues(values);
}}
>
{({ values, errors, touched, isSubmitting }) => (
<Form id={formId} autoComplete="off">
<Card sx={{ height: "100%", width: "100%" }}>
<MDBox px={3} py={4}>
<MDBox display="flex">
<ButtonWrapper
fullWidth={false}
handleClick={saveChanges}
>
Save Changes
</ButtonWrapper>
</MDBox>
<MDBox>
{getStepsContent({
values,
touched,
formField,
errors,
})}
</MDBox>
</MDBox>
</Card>
</Form>
)}
</Formik>
</Grid>
</Grid>
</MDBox>
)
}
Routing code:
{
type: "collapse",
name: "Settings",
key: "settings",
icon: <Icon fontSize="small">settings</Icon>,
collapse: [
{
name: "Users",
key: "users",
route: "/settings/users",
// icon: <Icon fontSize="small">users</Icon>,
component: <Users />,
},
{
name: "Companies",
key: "companies",
route: "/settings/companies",
component: <Companies />,
},
{
name: "Billing",
key: "billing",
route: "/settings/billing",
component: <Billing />,
},
{
name: "Integrations",
key: "integrations",
route: "/settings/integrations",
component: <Integrations />,
},
],
},
{
name: "New User",
key: "new user",
route: "/settings/users/new-user",
noCollapse: true,
component: <NewUser />,
},
{
type: "collapse",
name: "Sign Out",
key: "signout",
route: "/sign-out",
icon: <Icon fontSize="small">logout</Icon>,
component: <SignOut />,
noCollapse: true,
},
];
function that renders the routes:
const getRoutes = (allRoutes) =>
allRoutes.map((route) => {
if (route.collapse) {
return getRoutes(route.collapse);
}
if (route.route) {
return <Route exact path={route.route} element={route.component} key={route.key} />;
}
return null;
});
<Routes>
{getRoutes(routes)}
{/* <Route path="*" element={<Navigate to="/dashboard" />} /> */}
<Route path="*" element={<Console />} />
</Routes>
The state value sent via the Link component needs to be JSON serializable. Javascript functions are not serializable. Instead of trying to pass a function through to a target component I recommend lifting the state up to a common ancestor so the state and callback function is accessible to both components.
I would suggest using a React context to hold the users state and provide out the state value and an updater function to add a user object. react-router-dom has a "built-in" way to do this via a layout route component that renders an Outlet component that wraps nested routes.
Example:
import { Outlet } from 'react-router-dom';
const UsersProvider = () => {
const [users, setUsers] = useState([]);
const addUser = (user) => {
setUsers((users) => users.concat(user));
};
return <Outlet context={{ users, addUser }} />;
};
...
<Routes>
...
<Route path="/settings/users" element={<UsersProvider />}>
<Route index element={<Users />} />
<Route path="new-user" element={<NewUser />} />
</Route>
...
</Routes>
Users
const Users = () => {
const { users } = useOutletContext();
return (
<Card sx={{ padding: "2rem", mt: "2rem" }}>
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">
You currently have {users.length} users
</Typography>
<Button variant="gradient" color="info" size="small">
<Link to="/settings/users/new-user">
<Box
display="flex"
alignItems="center"
color="white"
fontWeight="normal"
>
<Icon>add</Icon>
Add New User
</Box>
</Link>
</Button>
</Box>
</Card>
);
};
NewUser
function NewUser({history}) {
const navigate = useNavigate();
const { addUser } = useOutletContext();
const saveChanges = (e) => {
addUser({
fname: values.firstName,
lname: values.lname,
email: values.email,
});
navigate("/settings/users");
};
return(
<MDBox py={3} mb={20} height="62vh">
<Grid
container
justifyContent="center"
alignItems="center"
sx={{ height: "100%", mt: 0 }}
>
<Grid item xs={12} lg={12}>
<Formik
initialValues={initialValues}
validationSchema={currentValidation}
onSubmit={(values) => {
setValues(values);
}}
>
{({ values, errors, touched, isSubmitting }) => (
<Form id={formId} autoComplete="off">
<Card sx={{ height: "100%", width: "100%" }}>
<MDBox px={3} py={4}>
<MDBox display="flex">
<ButtonWrapper
fullWidth={false}
handleClick={saveChanges}
>
Save Changes
</ButtonWrapper>
</MDBox>
<MDBox>
{getStepsContent({
values,
touched,
formField,
errors,
})}
</MDBox>
</MDBox>
</Card>
</Form>
)}
</Formik>
</Grid>
</Grid>
</MDBox>
)
}

I need a nested route in react router V5

my task is to want a nested route in the access/ route means I have a parent route access/ so I need a nested in this route like /access/add-team this nested I want to do in one click of a button mean I'm my access/ route component I have I one button called Add Team when someone clicks on that button I am pushing to that user on this /access/add-team route so the route is getting change based on click but my add team component is net getting render what I am missing I am not sure I have added that every this in Layout.js file my component are present in Layout.js let me know what I need to add to work fine this also I added complete code link bellow
AppRoutes.js
const Layout = lazy(() => import("./Layout"));
const PageNotFound = lazy(() => import("./PageNotFound"));
const isLoggedIn = true;
const PrivateRoute = ({ component: Component, isLoggedIn }) => {
return (
<Route
render={(props) =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
};
export const AppRoutes = () => {
return (
<HashRouter>
<React.Suspense fallback={""}>
<Switch>
<PrivateRoute path="/" isLoggedIn={isLoggedIn} component={Layout} />
<Route
path="*"
name="Not Found"
render={(props) => <PageNotFound {...props} />}
/>
</Switch>
</React.Suspense>
</HashRouter>
);
};
function Layout(props) {
const history = useHistory();
const { window } = props;
const [mobileOpen, setMobileOpen] = React.useState(false);
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const drawer = (
<div>
<Toolbar />
<Divider />
<List sx={{ minWidth: 230 }}>
{newText
?.filter((data) => data.permission)
?.map((value, index) => (
<ListItemButton
key={index}
sx={{ pt: 1, pb: 1, mt: 3.5 }}
onClick={() => history.push(value.route)}
>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary={value.label} />
</ListItemButton>
))}
</List>
<Divider />
</div>
);
const container =
window !== undefined ? () => window().document.body : undefined;
return (
<Box sx={{ display: "flex" }}>
<CssBaseline />
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` }
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: "none" } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Responsive drawer
</Typography>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
aria-label="mailbox folders"
>
<Drawer
variant="permanent"
sx={{
display: { xs: "none", sm: "block" },
"& .MuiDrawer-paper": {
boxSizing: "border-box",
width: drawerWidth
}
}}
open
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` }
}}
>
<Toolbar />
<Suspense fallback={""}>
<Switch>
{ROUTES.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
name={route.name}
render={(props) => <route.component {...props} />}
/>
) : null;
})}
<Redirect exact path="/" to="access" />
<Route
path="*"
name="Not Found"
render={(props) => <PageNotFound {...props} />}
/>
</Switch>
</Suspense>
</Box>
</Box>
);
}
Within the Switch component path order and specificity matters! You want to order the routes from more specific paths to less specific paths. In this case you are rendering the "/access" path prior to any of the sub-route "/access/***" paths, so it is matched and rendered instead of the one really matching the path in the URL.
To fix, move the "/access" route config below the more specific routes.
export const ROUTES = [
// move "/access" route from here
{
name: "addTeam",
path: "/access/add-team",
component: lazy(() => import("./AddTeam"))
},
{
name: "addUser",
path: "/access/add-user",
component: lazy(() => import("./AddUser"))
},
// to here
{
name: "access",
path: "/access",
component: lazy(() => import("./Access"))
},
{
name: "admin",
path: "/admin",
component: lazy(() => import("./Admin"))
}
];

React function triggered reloading?

So, I have a parent function getData() which will request data from local server. I pass the function to my submit form. I call the function when submitting the form. It get called, but my form state keeps getting reset by itself. Why is this happening?
Also is there a way to refactor my Form useState, I kinda use too many useState I guess.
Thanks before.
This is my Form
const AddForm = ({ getData, classes }) => {
console.log("Rendering AddFORM");
const [checkboxes, setChecked] = React.useState([]);
const [inputs, setInputs] = React.useState({});
const [files, setFiles] = React.useState(null);
const [open, isOpen] = React.useState(false);
const [success, isSuccess] = React.useState(false);
const history = useHistory();
React.useEffect(() => {
setInputs({ ...inputs, platform: checkboxes });
}, [checkboxes]);
const onChangeForField = React.useCallback(({ target: { name, value } }) =>
setInputs((state) => ({ ...state, [name]: value }), [])
);
const onChangeForFiles = ({ target: { files } }) => setFiles(files);
const handleCheck = ({ target: { value } }) => {
checkboxes.includes(value)
? setChecked(checkboxes.filter((item) => item !== value))
: setChecked([...checkboxes, value]);
};
const handleClose = () => isSuccess(false);
async function submitForm() {
isOpen(true);
const formdata = new FormData(); // for adding form files i guess
for (let i = 0; i < files.length; i++) {
formdata.append("files", files[i], files[i].name); // "name", files, filename
}
for (let key in inputs) {
formdata.append(key, inputs[key]);
}
const response = await axios.post("/games", formdata);
const { data } = response;
isOpen(false);
isSuccess(true);
console.log(data.message);
}
return (
<Paper elevation={2} className={classes.Container}>
<form
onSubmit={async (e) => {
e.preventDefault();
await submitForm();
// getData();
// history.push("/");
}}
>
<Snackbar open={success} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
Game successfully added!
</Alert>
</Snackbar>
<Typography variant="h5" color="initial">
Add a new game
</Typography>
<TextField
required
id="standard-required"
name="title"
label="Title"
fullWidth
placeholder="Game title"
margin="normal"
onChange={onChangeForField}
/>
<TextField
fullWidth
multiline
required
id="standard-required"
name="description"
label="Description"
placeholder="Description"
margin="normal"
onChange={onChangeForField}
/>
<FormControl margin="normal" fullWidth>
<FormLabel component="legend">Select Platforms</FormLabel>
<FormGroup row>
{platforms.map((p, idx) => (
<FormControlLabel
key={idx}
control={
<Checkbox name="platforms" onChange={handleCheck} value={p} />
}
label={p}
/>
))}
</FormGroup>
</FormControl>
<FormControl>
<Button variant="contained">
<label htmlFor="file-upload">Upload Files</label>
</Button>
<input
type="file"
multiple
id="file-upload"
name="images"
style={{ display: "none" }}
onChange={onChangeForFiles}
></input>
</FormControl>
<div className={classes.ButtonsContainer}>
<Button
variant="contained"
color="primary"
type="submit"
onClick={(e) => {
e.stopPropagation();
}}
>
Submit
</Button>
<Button variant="contained" color="secondary">
<Link to="/">Go Back</Link>
</Button>
</div>
</form>
<Backdrop className={classes.backdrop} open={open}>
<Paper elevation={3} className={classes.Loading}>
<Typography variant="body1" align="center">
Submitting your form
</Typography>
<CircularProgress color="primary" className={classes.Circular} />
</Paper>
</Backdrop>
<Button onClick={() => isSuccess(true)}>Text</Button>
</Paper>
);
};
This is the parent which hold the getData()
function App() {
const [games, setGames] = React.useState([]);
React.useEffect(() => {
getData();
}, []);
async function getData() {
const response = await axios.get("/games");
const { data } = response;
setGames(data);
}
return (
<div className="App">
<Navbar />
<Container maxWidth="lg" style={{ margin: "1.5rem auto" }}>
<Switch>
<Route
path="/"
exact
component={() => <GameList games={games} getData={getData} />}
/>
<Route
path="/add"
exact
component={() => <AddForm getData={getData} />}
/>
<Route path="/addimg" exact component={() => <ImageForm />} />
<Route
path="/games/:id"
exact
component={() => <GameDetails getData={getData} />}
/>
<Route
path="/games/:id/edit"
exact
component={() => <EditForm getData={getData} />}
/>
<Route
path="/games/:id/reviews/:rid/edit"
exact
component={() => <EditReviewForm getData={getData} />}
/>
</Switch>
</Container>
</div>
);
}
Issue
You are rendering all your components on routes with anonymous components via the component prop, this cause new components to be created and mounted.
component
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).
Solution
Use the render prop to pass additional props to your route components.
Since ImageForm component isn't being passed any additional props it can be passed directly to the component prop of a Route.
Additionally, you should reorder your routes so you specify more specific paths prior to less specific paths, so they can be attempted to be matched first. This removes the need to append the exact prop to every route for matching.
<Switch>
<Route path="/add" render={() => <AddForm getData={getData} />} />
<Route path="/addimg" component={ImageForm} />
<Route
path="/games/:id/edit"
render={() => <EditForm getData={getData} />}
/>
<Route
path="/games/:id/reviews/:rid/edit"
render={() => <EditReviewForm getData={getData} />}
/>
<Route
path="/games/:id"
render={() => <GameDetails getData={getData} />}
/>
<Route
path="/"
render={() => <GameList games={games} getData={getData} />}
/>
</Switch>
Also is there a way to refactor my Form useState, I kinda use too many
useState I guess.
IMO you don't have "too may" state hooks. You can either keep all your state values simple and separate, or you can combine them into a more complex state object. Selecting one over the other is a subjective issue. In my opinion, each "chunk" of state should be capable of standing on its own, as a single atomic entity. As such it seems you've separated the state concerns sufficiently (checkboxes, inputs, toggles, etc...). I think trying to merge your state would only make your state updates needlessly more complex.

Warning: Cannot update a component while rendering a different component. ReactJS

In my ReactHooks/Typescript app, I have a Navigation component, that renders a PatientInfo component. The PatientInfo child is rendered conditionally based on what props it is passed, as decided by a searchbox in another child component - MyPatients.
In this structure, I am getting the following error:
Navigation.tsx:
// code....
<Route exact path="/" component={MyPatients} />
<Route
exact
path="/Pasient"
render={() => (
<PatientInfo
setName={setName}
setSchema={setSchema}
patientID={patientID}
/>
)}
/>
// code....
MyPatients:
const MyPatients = (props: { history: History }) => {
localStorage.clear();
const [patientID, setPatientID] = useState(
localStorage.getItem('myData') || '',
);
useEffect(() => {
localStorage.setItem('myData', patientID);
}, [patientID]);
return (
<>
<div className="search-container"></div>
<Row gutter={[60, 40]} justify={'center'}>
<Col span={1000}>
<p>Søk med personnummer for å finne en pasient</p>
<Search
style={{ width: 400 }}
className="search-bar"
placeholder="Søk etter en pasient!"
onSearch={(value: string) => setPatientID(value)}
/>
</Col>
</Row>
{patientID &&
props.history.push({ pathname: 'Pasient', state: patientID })}
</>
);
};
export default MyPatients;
I am not familliar with this issue, and don't understand what's happening. My educated guess is that React doesn't like the fact that the state of the parent component is being updated by functions passed to the children, which again are dependant on the props passed along with it. Am I on to something? Any ideas as to what is causing this if not?
Any help is appreciated.
You are navigating with history.push on each render.
As #HMR mentioned in the comment, you have to remove navigation from JSX template and add it into a separate effect.
const MyPatients = (props: { history: History }) => {
localStorage.clear();
const [patientID, setPatientID] = useState(
localStorage.getItem("myData") || ""
);
useEffect(() => {
localStorage.setItem("myData", patientID);
}, [patientID]);
// separate effect here
useEffect(() => {
if (patientID) {
props.history.push({ pathname: "Pasient", state: patientID });
}
}, [props, patientID]);
return (
<>
<div className="search-container"></div>
<Row gutter={[60, 40]} justify={"center"}>
<Col span={1000}>
<p>Søk med personnummer for å finne en pasient</p>
<Search
style={{ width: 400 }}
className="search-bar"
placeholder="Søk etter en pasient!"
onSearch={(value: string) => setPatientID(value)}
/>
</Col>
</Row>
</>
);
};
export default MyPatients;
EDIT
This might cause your error:
<PatientInfo
setName={setName}
setSchema={setSchema}
patientID={patientID}
/>
If you call setName or setSchema on render of PatientInfo then Navigation state gets updated before PatientInfo render is finished.

React js. Cant' get value of undefined, displayName of user. I have used props to use the user state in this file but it can't access any of user data

I can't display users data such as name when he is logged in. I have used props and state user as currentUser but i am unable to access these fields since the error says that it can't read property of undefined.
class UserPanel extends React.Component {
state = { user: this.props.currentUser }
dropdownOptions = () => [
{
key: "user",
text: (
<span>
Sign in as <strong>{this.state.user.displayName}</strong>
</span>
),
disabled: true
},
{
key: "avatar",
text: <span>Change Avatar</span>
},
{
key: "signout",
// Set a signout Function to enable user to sign out of the chat
text: <span onClick={event => this.handleSignOut(event)}>SignOut</span>
}
];
handleSignOut = (event) => {
// You need to prevent form submission. Use event.preventDefault() in your handle submit function.
event.preventDefault();
firebase
.auth()
.signOut()
.then(() => console.log("See you"));
}
render(){
console.log(this.props.currentUser);
return (
<Grid style={{ background: '#4c3c4c' }}>
<Grid.Column>
<Grid.Row style={{ padding: '1.2rem', margin: 0 }}>
<Header inverted floated='left' as='h2'>
<Icon name='code' />
<Header.Content>VirtualChat</Header.Content>
</Header>
</Grid.Row>
{/* User Dropdown Choices */}
<Header style={{ padding: "0.25em" }} as="h4" inverted>
<Dropdown
trigger={<span>{this.state.user.displayName}</span>}
options={this.dropdownOptions()}
/>
</Header>
</Grid.Column>
</Grid>
)
}
}
// index.js
const store = createStore(rootReducer, composeWithDevTools());
// change root component to a statefull component
class Root extends React.Component {
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
// If firebase has detect a user
if (user) {
// console.log(user);
this.props.setUser(user);
// We will redirect them to the home Route
this.props.history.push("/");
} else {
// In case user signout
this.props.history.push('/login');
this.props.clearUser();
}
});
}
render(){
return this.props.isLoading ? <Spinner /> : (
// All of our indivicuals routes will be nested in switch component which is nested to router component
<Switch>
{/* Root route of the app, we first set the path and then which component we watn */}
{/* We added exact keyword in order to secure that the main route will not match multiple components */}
<Route exact path="/" component={App} />
{/* Create routes for Login and Register */}
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
</Switch>
);
}
}
// To get loading data from our state object to see when user actions is loaded
const mapStateFromProps = state => ({
isLoading: state.user.isLoading
});
const RootWithAuth = withRouter(
connect(
// Using mapStateFromProps because, since state update are asynchronous and take some amount of time
mapStateFromProps,
{ setUser, clearUser }
)(Root)
);
// We render root because app is now our route
// In order to provide this global state/store to the other components we wrap the router in to a provider
// Provider will provide this global state to any component who want to make use of it
ReactDOM.render(
<Provider store={store}>
<Router>
<RootWithAuth />
</Router>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
I think that the value is undefined because you are not checking if the props has a value maybe the data that your are trying to render is not ready or is async. To handle this you can set your state in a componentDidMount so if the state.currentUser is null it means that the data isn't ready and you can render a loader or something similar.
class UserPanel extends React.Component {
state = { user: null }
dropdownOptions = () => [
{
key: "user",
text: (
<span>
Sign in as <strong>{this.state.user.displayName}</strong>
</span>
),
disabled: true
},
{
key: "avatar",
text: <span>Change Avatar</span>
},
{
key: "signout",
// Set a signout Function to enable user to sign out of the chat
text: <span onClick={event => this.handleSignOut(event)}>SignOut</span>
}
];
handleSignOut = (event) => {
// You need to prevent form submission. Use event.preventDefault() in your handle submit function.
event.preventDefault();
firebase
.auth()
.signOut()
.then(() => console.log("See you"));
}
componentDidMount(){
this.setState({ user: this.props.currentUser })
}
render(){
if( !this.state.user){
return <div>Curernt User doesnt exist!</div>
}
return (
<Grid style={{ background: '#4c3c4c' }}>
<Grid.Column>
<Grid.Row style={{ padding: '1.2rem', margin: 0 }}>
<Header inverted floated='left' as='h2'>
<Icon name='code' />
<Header.Content>VirtualChat</Header.Content>
</Header>
</Grid.Row>
{/* User Dropdown Choices */}
<Header style={{ padding: "0.25em" }} as="h4" inverted>
<Dropdown
trigger={<span>{this.state.user.displayName}</span>}
options={this.dropdownOptions()}
/>
</Header>
</Grid.Column>
</Grid>
)
}
}
You call this.props.state.user instead of this.state.user

Categories

Resources