React (Material UI) - Issues with form in component - javascript

So, I'm trying to create a signup form for a web-app - but are running into a few issues.
I'm using hooks with a function to render signup page, which I'm routing to from the login page.
It works fine assuming I return the html directly from the return in the function (signup), but once the signup has been engaged, I wish swap the form for an acknowledge of it being send.
From what I can tell, people simply wrap each html in an arrow function and then toggles between using a bool or similar. But that's where the issues arrive.
TLDR;
One of the signup textfields autocompletes, fetching from an API. The API then saves the content in a hook variable (address). The second I update the address variable, the form seem to reset - cursor going to the first inputfield.
This only happens when I wrap the html in components, not if I insert all the html in the (signup) return.
I tried to clean it up a bit, but the code more or less look like this.
Any help or pointers would be great :)
export default function SignUp(props)
{
const [activeStep, setActiveStep] = React.useState(0);
const [addresses, setAddresses] = React.useState([{ tekst: '' }]);
const APICall = async (e) =>
{
e.preventDefault();
// Fetchs JSON and set Addresses hook
}
const handleSubmit = props => form =>
{
form.preventDefault()
setActiveStep(activeStep + 1);
}
const CreateAccount = (e) =>
{
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Opret konto
</Typography>
<form className={classes.form} noValidate
onSubmit={handleSubmit(props)}>
<Autocomplete
id="address"
options={addresses}
getOptionLabel={(option) => option.tekst}
style={{ width: 300 }}
renderInput={(params) =>
<TextField {...params} label="Combo box" variant="outlined" onChange={userTest} />
}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign Up
</Button>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
);
}
const CreateAccountACK = () =>
{
return (
<React.Fragment>
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Nyt konto oprettet!
</Typography>
<Button
type="button"
variant="contained"
color="primary"
className={classes.submit}
onClick={() => { props.history.push('/') }}
>
Ok
</Button>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
</React.Fragment>
);
}
return (
<div>
{activeStep == 0 ? <CreateAccount /> : <CreateAccountACK />}
</div>
)
}

Got it working by breaking each function into its own functional component, then render these from the main class (signup) using conditionals. Swapping between pages are handled by callback to "handlesubmit" in this function. Pass history as the final page routes back to main. Feel like this isn't the best way of doing this tho =D, but it avoids the issues with re-renders while typing.
So now the Signup just return ...
export default function SignUp(props)
{
const [activeStep, setActiveStep] = React.useState(0);
const handleSubmit = props => form =>
{
form.preventDefault()
console.log(form)
setActiveStep(activeStep + 1);
}
return (
<div>
{activeStep == 0 ? <CreateAccount handleSubmit={handleSubmit} /> : <CreateAccountACK handleSubmit={handleSubmit} history={props.history}/>}
</div>
)
}
And each function, hooks/variables exist in their own file/function.

Related

Warning: Can't perform a React state update on an unmounted component. 2021 Not Async

getting this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
const MapBox = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [visible, setVisible] = useState(false)
const onClick = () => setVisible(true);
const closeHandler = () => {
setVisible(false);
}
useEffect(() => {
}, [visible])
return (
<>
<MapContainer ref={mapContainer} className="map-container" onClick={onClick}></MapContainer>
<ModalContainer visible={visible} closeHandler={closeHandler} />
</>
)
}
The modal container is just a simple modal pop, only returning a jsx component. That only shows if visible is true, and if runs closeHandler is a button is pressed.
const ModalContainer = ({ visible, closeHandler }) => {
useEffect(() => {}, [visible])
return (
<>
{visible && (
<div>
<Modal
closeButton
aria-labelledby="modal-title"
open={visible}
onClose={closeHandler}
>
<Modal.Header>
<Text id="modal-title" size={18}>
Welcome to
<Text b size={18}>
NextUI
</Text>
</Text>
</Modal.Header>
<Modal.Body>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Email"
/>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Password"
/>
<Row justify="space-between">
<Checkbox>
<Text size={14}>
Remember me
</Text>
</Checkbox>
<Text size={14}>
Forgot password?
</Text>
</Row>
</Modal.Body>
<Modal.Footer>
<Button auto flat color="error" onClick={closeHandler}>
Close
</Button>
<Button auto onClick={closeHandler}>
Sign in
</Button>
</Modal.Footer>
</Modal>
</div>)
}
</>
);
}
Any help would be much appreciated, all other people on here with this error message are running async code etc. This one is a lot simpler and the error only comes up the first time the click to close event happens.

Sharing component states with useState() for active link in Nav

I'm trying to learn react and fairly new to the framework. I am trying to create a simple navbar component wih material-ui that is responsive (will show all links on medium devices and up, and open a side drawer on small devices). I have most of it setup to my liking, however, the issue I am currently having, is getting and setting the active link according to the page I am on.
It seems to works correctly on the medium devices and up, but when transitioning to a smaller device, the link is not updated correctly, as it will keep the active link from the medium screen set, while updating the side drawer active link.
Navbar.js
const Navbar = () => {
const classes = useStyles();
const pathname = window.location.pathname;
const path = pathname === '' ? '' : pathname.substr(1);
const [selectedItem, setSelectedItem] = useState(path);
const handleItemClick = (event, selected) => {
setSelectedItem(selected);
console.log(selectedItem);
};
return (
<>
<HideNavOnScroll>
<AppBar position="fixed">
<Toolbar component="nav" className={classes.navbar}>
<Container maxWidth="lg" className={classes.navbarDisplayFlex}>
<List>
<ListItem
button
component={RouterLink}
to="/"
selected={selectedItem === ''}
onClick={event => handleItemClick(event, '')}
>
<ListItemText className={classes.item} primary="Home" />
</ListItem>
</List>
<Hidden smDown>
<List
component="nav"
aria-labelledby="main navigation"
className={classes.navListDisplayFlex}
>
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === 'account/login'}
onClick={event => handleItemClick(event, 'account/login')}
>
<ListItemText className={classes.item} primary="Login" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/account/register"
selected={selectedItem === 'account/register'}
onClick={event => handleItemClick(event, 'account/register')}
>
<ListItemText className={classes.item} primary="Register" />
</ListItem>
</List>
</Hidden>
<Hidden mdUp>
<SideDrawer />
</Hidden>
</Container>
</Toolbar>
</AppBar>
</HideNavOnScroll>
<Toolbar id="scroll-to-top-anchor" />
<ScrollToTop>
<Fab aria-label="Scroll back to top">
<NavigationIcon />
</Fab>
</ScrollToTop>
</>
)
}
SideDrawer.js
const SideDrawer = () => {
const classes = useStyles();
const [state, setState] = useState({ right: false });
const pathname = window.location.pathname;
const path = pathname === "" ? "" : pathname.substr(1);
const [selectedItem, setSelectedItem] = useState(path);
const handleItemClick = (event, selected) => {
setSelectedItem(selected);
console.log(selectedItem);
};
const toggleDrawer = (anchor, open) => (event) => {
if (
event &&
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setState({ ...state, [anchor]: open });
};
const drawerList = (anchor) => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List component="nav">
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === "account/login"}
onClick={(event) => handleItemClick(event, "account/login")}
>
<ListItemText className={classes.item} primary="Login" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === "account/register"}
onClick={(event) => handleItemClick(event, "account/register")}
>
<ListItemText className={classes.item} primary="Register" />
</ListItem>
</List>
</div>
);
return (
<React.Fragment>
<IconButton
edge="start"
aria-label="Menu"
onClick={toggleDrawer("right", true)}
>
<Menu fontSize="large" style={{ color: "white" }} />
</IconButton>
<Drawer
anchor="right"
open={state.right}
onClose={toggleDrawer("right", false)}
>
{drawerList("right")}
</Drawer>
</React.Fragment>
);
};
Code Sandbox - https://codesandbox.io/s/async-water-yx90j
I came across this question on SO: Is it possible to share states between components using the useState() hook in React?, which suggests that I need to lift the state up to a common ancestor component, but I don't quite understand how to apply this in my situation.
I would suggest to put aside for a moment your code and do a playground for this lifting state comprehension. Lifting state is the basic strategy to share state between unrelated components. Basically at some common ancestor is where the state and setState will live. there you can pass down as props to its children:
const Parent = () => {
const [name, setName] = useState('joe')
return (
<>
<h1>Parent Component</h1>
<p>Child Name is {name}</p>
<FirstChild name={name} setName={setName} />
<SecondChild name={name} setName={setName} />
</>
)
}
const FirstChild = ({name, setName}) => {
return (
<>
<h2>First Child Component</h2>
<p>Are you sure child is {name}?</p>
<button onClick={() => setName('Mary')}>My Name is Mary</button>
</>
)
}
const SecondChild = ({name, setName}) => {
return (
<>
<h2>Second Child Component</h2>
<p>Are you sure child is {name}?</p>
<button onClick={() => setName('Joe')}>My Name is Joe</button>
</>
)
}
As you can see, there is one state only, one source of truth. State is located at Parent and it passes down to its children. Now, sometimes it can be troublesome if you need your state to be located at some far GreatGrandParent. You would have to pass down each child until get there, which is annoying. if you found yourself in this situation you can use React Context API. And, for most complicated state management, there are solutions like redux.

Cannot get material UI Textfield data

I'm quite a newbie with React. I made a login page using firebase auth and have any input field to get the person's contact to validate but I can't get this data. Already tried everything I found here but it is not working.
I tried the ref={x => this.contacto = x} as shown below but not working.
export class Login extends Component {
handleClick=()=>{
const recaptcha = new firebase.auth.RecaptchaVerifier('recaptcha');
const number = this.contacto.value;
console.log(number)
firebase.auth().signInWithPhoneNumber(number, recaptcha).then( function(e) {
var code = prompt('Enter the otp', '');
if(code === null) return;
e.confirm(code).then(function (result) {
console.log(result.user);
document.querySelector('label').textContent += result.user.phoneNumber + "Number verified";
}).catch(function (error) {
console.error( error);
});
})
.catch(function (error) {
console.error( error);
});
}
render(){
return (
<div>
<div id="recaptcha"></div>
<Grid container justify="center">
<Grid item component={Card} xs={6} md={3} className={cx(styles.card)}>
<CardContent>
<img className={styles.image} src={image} alt="cmcq" />
<Typography variant="h6" className={cx(styles.titulo)} gutterBottom>
Login
</Typography>
<TextField
id="outlined-primary"
label="Insert phone number"
variant="outlined"
color="primary"
size="small"
ref={x => this.contacto = x}
className={cx(styles.field)}
/>
<Button variant="contained" color="primary" className={cx(styles.button)} onClick={this.handleClick} >
Entrar
</Button>
</CardContent>
</Grid>
</Grid>
</div>
);
}
};
export default Login;
Try using the onChange handler for TextField to set a state variable. You can reference it with this.state.varName
onTextChange = (event) => {
this.setState({varName: event.target.value});
}
<TextField
id="outlined-primary"
label="Insert phone number"
variant="outlined"
color="primary"
size="small"
onChange={this.onTextChange}
className={cx(styles.field)}
/>
You shouldn't use ref to access data of TextField, you should use onChange callback method to access data, try:
<TextField
onChange={e => console.log(e)}
/>
The logged e contains your expected data. For using ReactJS mindset you should use state and add it to the state and then access to it.

React Jest test button onClick

I'm developing an app with React, and I'm new to its testing framework. I'm trying to develop a testing method to check if an onClick button is clicked. I've read that I need a "spy" function to get the information about whether or not it's been called.
My testing method:
test("Btn1 click", () => {
const wrapper = mount(<Register />);
const spyOn = jest.spyOn(wrapper.instance(), "openWindow")
const element = wrapper.find({id: "btn-how-to-choose-provider"}).first();
element.simulate("click");
expect(spyOn).toHaveBeenCalled();
});
The Register.js component that I want to test:
export default function Register() {
const classes = useStyles();
function openWindow(url) {
window.open(url);
}
return (
<div>
<NavBar />
<Container component="main" maxWidth="sm">
<Card className={classes.root} elevation={4}>
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<AccountCircleIcon />
</Avatar>
<Typography component="h1" variant="h5">Hi! Welcome to Solid.</Typography>
<div className={classes.form}>
<Button
id="btn-how-to-choose-provider"
fullWidth
color="primary"
className={classes.link}
startIcon={<EmojiPeopleIcon/>}
onClick={(e) => openWindow('https://solid.inrupt.com/how-it-works')}
>How to choose a Provider?</Button>
<Button
id="btn-inrupt-provider"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
startIcon={<ContactsOutlinedIcon/>}
onClick={(e) => window.open('https://inrupt.net/register')}
>Inrupt</Button>
<Button
id="btn-solid-community-provider"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
startIcon={<ContactsIcon/>}
onClick={() => window.open('https://solid.community/register')}
>Solid Community</Button>
</div>
</div>
</Card>
</Container>
<Grid
item
xs={12}
sm={12}
md={12}
style={{marginTop: '36rem'}}
>
<Footer />
</Grid>
</div>
);
}
With this configuration, I obtain the following error:
TypeError: Cannot read property 'openWindow' of null
34 | test("Btn1 click", () => {
35 | const wrapper = mount(<Register />);
> 36 | const spyOn = jest.spyOn(wrapper.instance(), "openWindow")
| ^
37 | const element = wrapper.find({id: "btn-how-to-choose-provider"}).first();
38 | element.simulate("click");
39 | expect(spyOn).toHaveBeenCalled();
at ModuleMockerClass.spyOn (node_modules/jest-mock/build/index.js:837:28)
at Object.<anonymous> (src/components/containers/register/Register.test.js:36:24)
My question is: is there any way to test the onClick function of a button with Jest or any other testing framework?
Okay, so after more research, I've found that the React Testing Library provides some tools to test the onClick event of some buttons (at least those that are like the ones I've shown in my example above). One of those tools is the fireEvent, which helps to accomplish what I wanted:
test("Click", () => {
const {container} = render(<Register />);
const button = getByTestId(container, 'btn-how-to-choose-provider');
fireEvent.click(button);
});

Expected an assignment or function call and instead saw an expression react router

I have button in a Material Table. I am using react routers to route pages to different URLs.
This page is supposed to set up functions and call the Material Table <MuiTable> and then render a button below the material table. It is set up this way due to the reusability of the MuiTable element.
export default function ListJobs(props) {
const url = 'http://localhost:8000/api/Jobs/'
const [data, loading] = DataLoader(url);
const handleEdit = (e,rowData) => {
<EditJob id={rowData.id} />
}
const handleDelete = (e,rowData) => {
//edit operation
<ListJobs />
DataDelete(url, rowData.id)
}
const createButton =
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Button
component={Link} to='/Job/Create'
variant="contained"
color="primary">
Create New Job
</Button>
</div>
return (
<> {loading ? (
<Grid
container
spacing={0}
alignItems="center"
justify="center"
style={{ minHeight: '90vh' }}
>
<CircularProgress size="10vh" />
</Grid>
) : (
<MuiTable
model="Job"
data={data}
url={url}
handleEdit={handleEdit}
handleDelete={handleDelete}
createButton={createButton}
/>
)}
</>
);
}
This currently throws and error "Expected an assignment or function call and instead saw an expression" on the lines that call <EditJob...> and <ListJobs>. I know this is not the correct way to write this but, I want to change it to using react routers. I have my routers set up already but don't know how to use them in this instance. I want it to work something like this.
const handleEdit = (e,rowData) => {
<component ={Link} to='Jobs' />
}
I know this isn't correct eit,her because the react router link must be inside of a component like a<button>or <MenuItem>.
Try to return EditJob and ListJobs
const handleEdit = (e,rowData) => {
return <EditJob id={rowData.id} /> // return the function <EditJob />
}
const handleDelete = (e,rowData) => {
//edit operation
DataDelete(url, rowData.id) // Any operation before the return
return <ListJobs /> // return the function <ListJobs />
}

Categories

Resources