I took this all from this example. Creating a React Login Page
So I cannot take any credit. However, this page works fine. I am trying to retro fit it to pushing a React Video Player page.
My code is as follows (Snippet from Axios Post):
if (response.status == 200) {
console.log("Login successfull");
var videoPlayer = [];
this.setState( {isLoggedIn : true });
videoPlayer.push(<Videoplayer appContext={self.props.appContext} parentContext={this} />);
self.props.appContext.setState({loginPage: [], videoPlayer: videoPlayer});
The existing code was this:
var uploadScreen=[];
uploadScreen.push(<UploadScreen appContext={self.props.appContext}/>);
self.props.appContext.setState({loginPage: [], uploadScreen: uploadScreen})
render() {
var browserHistory = createBrowserHistory();
if(this.state.isAuthenticated) {
return <Redirect to={'/VideoPlayer '}/>
}
return(
//<Router history={ browserHistory }>
//<Route path="/VideoPlayer" component={() => <VideoPlayer title="Arlo Video" style="home-header"/> }/>
<div>
<MuiThemeProvider>
<div>
<AppBar
title="Login"
/>
<TextField
hintText="Enter your Username"
id = "username"
floatingLabelText="Username"
onChange={(event, newValue) => this.setState({username: newValue})}
/>
<br/>
<TextField
type="password"
id = "password"
hintText="Enter your Password"
floatingLabelText="Password"
onChange={(event, newValue) => this.setState({password: newValue})}
/>
<br/>
<RaisedButton label="Submit" primary={true} style={style}
onClick={(event) => this.handleClick(event)}/>
</div>
</MuiThemeProvider>
</div>
// </Router>
)
}
}
My code does not render the VideoPlayer Page and that code is from the standard example at this github
ReactJS Video Player
I have a feeling that it has to do with Context but I don't know enough about React or how to debug this using the Chrome tools. My backend is Django. I'm almost thinking of going back to using Jquery so I can get pages at least functioning but wanted to try and learn React.
Any help would be great. The code above is just testing code so I could get some thing functional.
Updating the state by returning null, empty array [] or any initial value will not re-render the component. You need to change the state value to something else and then only the component will be re-rendered.
You may also take care of setState will not re-render the component when shouldComponentUpdate hook returns false.
The offending code was the following:
AppContext={self.props.appContext}
Which was a typo to this:
videoPlayer.push(<Videoplayer appContext={self.props.appContext} parentContext={this} />)
Related
I have tried a few ways to do this correctly, but lack the testing experience to catch what I'm missing. I have a LoginForm.tsx component that inside holds a few event handlers and a couple bits of local state using React.useState(). The component returns a ternary statement conditionally rendering two components, and within one of them, that component renders different content based on another boolean condition.
authSuccess: when false, main component returns a <Card /> component; when true, the component returns <Navigate to={...} replace /> to redirect user to account.
isLoading: when false, children of <Card /> is form content, when true, children is a <Spinner /> component.
The problem is, I can't seem to find how to change those useState values in my tests and mock the behavior of this component. I would like to test that errors are rendering correctly as well. I am not using Enzyme since it seems it is dead for anything after React 17, so I have been trying to find a way to do this using just React Testing Library out of the box with Create React App Typescript.
The component code looks like this:
import * as React from 'react'
// ...
export default function LoginForm() {
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [errors, setErrors] = React.useState<{ [key: string]: string } | any>({});
const [authSuccess, setAuthSuccess] = React.useState<boolean>(false);
const initialState: LoginFormInitialState = {
email: '',
password: '',
};
// Form Hook
const { values, onChange, onSubmit } = useForm({ callback: handleLogin, initialState });
// ==> HANDLERS
function successCallback(data) {
// ...
return setAuthSuccess(true); // <---- Changes state to return redirect
}
function errorHandler(e: any) {
// ...
setErrors(errors); // <---- // sets errors object to render errors
return setIsLoading(false); // <---- Changes contents of <Card />
}
function handleLogin() {
setIsLoading(true); // <---- Changes content of card
setErrors({}); // <---- Clears errors
// Passes handlers as callbacks into api
return userAccountAPI.login({ data: values, successCallback, errorHandler });
}
return authSuccess ? (
// If authentication was successful, redirect user to account page
<Navigate to={ACCOUNT_OVERVIEW} replace />
) : (
// No auth success yet, keep user on login page
<Card
title={<h1 data-testid="form-header">Login To Account</h1>}
data-testid={'login-card'}
bodyStyle={{
display: isLoading ? 'flex' : 'block',
justifyContent: 'center',
padding: isLoading ? '100px 0 ' : '',
}}>
{isLoading ? (
<Spin size='large' data-testid={'login-spinner'}></Spin>
) : (
<Form name='login_form' data-testid={'login-form'} initialValues={{ remember: true }}>
<Form.Item name='email' rules={[{ required: true, message: 'Please input your email!' }]}>
<Input
name='email'
onChange={onChange}
prefix={<UserOutlined className='site-form-item-icon' />}
placeholder='Email'
/>
</Form.Item>
<Form.Item name='password' rules={[{ required: true, message: 'Please input your Password!' }]}>
<Input
name='password'
onChange={onChange}
prefix={<LockOutlined className='site-form-item-icon' />}
type='password'
placeholder='Password'
/>
</Form.Item>
<Form.Item>
<Button
data-testid='login-button'
onClick={onSubmit}
type='primary'
name='login'
htmlType='submit'
className='login-form-button'
block>
Log in
</Button>
</Form.Item>
</Form>
)}
{/* Render out any form errors from the login attempt */}
{Object.entries(errors).length > 0 && (
<Alert
type='error'
message={
<ul style={{ margin: '0' }}>
{Object.keys(errors).map((er, i) => {
return <li key={i}>{errors[er]}</li>;
})}
</ul>
}
/>
)}
</Card>
);
}
I would like to be able to make an assertion about the card, but not if authSuccess=true, in which case I'd want to assert that we do not have the card and that the redirect has been rendered. I would want to test that the Spinner is a child of the card if isLoading = true, but also that I have the form as a child if it is false.
I have tried some of the approaches I've seen in other issues, but many of them have a button in the UI that directly changes the value and the solution is typically "grab that button and click it" and there you go. But the only button here is the login, and that doesn't directly change the local state values I need to mock.
I have also tried something like this which seems to have worked for some people but not for me here..
import * as React from 'react'
describe('<LoginForm />', () => {
const setup = () => {
const mockStore = configureStore();
render(
<Provider store={mockStore()}>
<LoginForm />
</Provider>
);
};
it('should have a spinner as child of card', () => {
setup();
jest.spyOn(React, 'useState')
.mockImplementationOnce(() => ['isLoading', () => true])
.mockImplementationOnce(() => ['errors', () => {}])
.mockImplementationOnce(() => ['authSuccess', () => false]);
const card = screen.getAllByTestId('login-card');
const spinner = screen.getAllByTestId('login-spinner');
expect(card).toContainElement(spinner);
});
});
It seems like Enzyme provided solutions for accessing and changing state, but as mentioned, I am not using Enzyme since I am using React 18.
How can I test this the way I intend to, or am I making a fundamental mistake with how I am approaching testing this? I am somewhat new to writing tests beyond that basics.
Thanks!
From the test I see that you are using react testing library. In this case you should "interact" with your component inside the test and check if the component reacts properly.
The test for spinner should be like that:
render the component
find the email input field and "type" there an email - use one of getBy* methods and then type with e.g. fireEvent.change(input, {target: {value: 'test#example.com'}})
find the password input field and "type" there a password - same as above
find the submit button and "click" it - use one of getBy* methods to find it and then use fireEvent to click it
this (I assume) should trigger your onSubmit which will call the handleLogin callback which will update the state and that will render the spinner.
check if spinner is in the document.
Most probably you would need some mocking for your userAccountAPI so it calls a mock function and not some real API. In here you can also mock that API to return whatever response you want and check if component displays correct content.
I have a component for 4 digit code of phone validation. By itself it works fine and looks good as well. The only issue I am facing - I can't autotab between numbers. I have to go to each input manually and write the number. Is it possible to do with Formik Field?
This is my piece of code:
<Formik
onSubmit={values =>
VerifyGarageFunc({ code: values.code.join(''), requestId: PhoneCodeData.data }, data.showModal)
}>
{({ values, handleChange, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<FieldArray
name="code"
render={arrayHelpers => (
<div className={styles.inputWrapper}>
{values.code.map((item, index) => (
<div key={index}>
<Field
name={`code.${index}`}
type="text"
component={CustomInput}
onChange={handleChange}
value={values.code[index]}
/>
</div>
))}
</div>
)}
/>
<LoginActionButton onSubmit={handleSubmit} text={'Send'} />
<FieldArray />
</form>
)}
</Formik>
I tried https://www.npmjs.com/package/react-auto-tab but it works only with <input/>, for some reason it doesn't work at all with Formik Field.
P.S. I am using Next.js with React.js
You'll likely have more luck with a hook based solution. Install https://github.com/Romr1ch/react-pin-input-hook, which just does the logic without being opinionated about display.
Create a new component called PinInput and create a new field using the formik hook primitives.
Ive setup an example codesandbox: https://codesandbox.io/s/react-pin-input-hook-custom-input-1ze5dv?file=/src/App.js, but note this doesn't use your exact components as I don't have them. The below code should match closer your exact case.
import React from 'react'
import { useField } from 'formik'
import { usePinInput } from 'react-pin-input-hook'
export const PinInput = (props) => {
const [field, meta, helpers] = useField(props)
const { fields } = usePinInput({
values: field.value,
onChange: (values) => {
helpers.setValue(values)
},
})
return fields.map((fieldProps, index) =>
<CustomInput key={index} type="text" {...fieldProps} />
)
}
Then in your main file do this (by the way if you use Form component from Formik you dont need to do any of the onSubmit binding yourself, so I changed that along the way -- the button can just be "submit" type):
<Formik
onSubmit={values =>
VerifyGarageFunc({ code: values.code.join(''), requestId: PhoneCodeData.data }, data.showModal)
}>
<Form>
<PinInput name="code" />
<LoginActionButton type="submit" text={'Send'} />
</Form>
</Formik>
Note that this lib requires your CustomComponent to attach a ref to the underlying thing that needs focusing so you'll need to use forwardRef on that component and attach it to the underlying input. It also needs to support onBlur, onFocus, onChange and onKeyDown.
I have created a simple login function where once the user log in, he is redirect to another page. Then I wanted to change the login form with a form dialog. And the problem is here. The login dialog works, but when I enter the username and password, I'm not send to another page but to the same login page :/.
Here is the code:
Login.jsx:
class Login extends Component {
constructor(props) {
super(props);
this.state = {
islogged: false,
loginSettings: {
lUsername: "",
lPassword: ""
}
};
}
handleInput = (event) => {
let loginSettingsNew = { ...this.state.loginSettings };
let val = event.target.value;
loginSettingsNew[event.target.name] = val;
this.setState({
loginSettings: loginSettingsNew
});
};
login = (event) => {
let lUsername = this.state.loginSettings.lUsername;
let lPassword = this.state.loginSettings.lPassword;
if (lUsername === "admin" && lPassword === "password") {
localStorage.setItem("token", "T");
this.setState({
islogged: true
});
} else {
console.log("Erreur");
}
event.preventDefault();
};
render() {
if (localStorage.getItem("token")) {
return <Redirect to="/" />;
}
return (
<div className="Login">
<Dialog handleInput={this.handleInput} login={this.login} />
<p>Username: admin - Password: password</p>
</div>
);
}
}
Dialog.js:
export default function FormDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open form dialog
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Login</DialogTitle>
<DialogContent>
<form onSubmit={this.login}>
<label>
<span>Username</span>
<input name="lUsername" type="text" onChange={this.handleInput} />
</label>
<label>
<span>Password</span>
<input name="lPassword" type="password" onChange={this.handleInput}/>
</label>
<Button onClick={handleClose} color="primary">Cancel</Button>
<Button type="submit" value="submit" color="primary">Login</Button>
</form>
</DialogContent>
</Dialog>
</div>
);
}
I also have created a sandbox of my code: https://codesandbox.io/s/react-login-auth-forked-r06ht
I thinks that the problem come from the form dialog that can't set the state of islogged of the login function. I tried quite a lot of things but nothing worked, so I would like to ask some help please.
I thank in advance anyone who will take the time to help me .
I notice that your Login component is a class, while the child FormDialog component is a function component using Hooks. There's nothing wrong with that, in itself - particularly if you started with an application using all class components and are slowly converting your components. But I detect some confusion here, because your function component seems to assume it's a class, in a sense. When you need to do things a bit differently.
Specifically, you reference this.login in your function component - but this is meaningless. login is passed in as a prop. In a class component, you would reference it as this.props.login. This doesn't work in a function component - but this.login isn't the substitute. Indeed this will tend to be undefined so this would even give an error. Even if not, it's not correct.
All you need to do is to make use of the argument to your function component - this is where the props object "lives" in a function component. You happen to ignore it by not using any arguments - but you don't have to do this.
So in short, what you need to do is:
replace export default function FormDialog() with export default function FormDialog(props)
replace this.login with props.login
repeat 2) for all other props which you have referenced using this
Ok all fixed. In your App.js you didn't have a home component to redirect to with this path="/". I've created a new component called home.js. Tidied the Routes for you.
<Route path="/login" component={Login} />
<ProtectedRoute path="/dashboard" component={Dashboard} />
<Route exact path="/" component={Home} />
Please check ur code sandbox https://codesandbox.io/s/react-login-auth-forked-24m8e?file=/src/App.js
I am facing an issue while passing the state to the child component, so basically I am getting customer info from child1(Home) and saving in the parent state(App) and it works fine.
And then I am passing the updated state(basketItems) to child2(Basket). But when I click on the Basket button the basket page doesn't show any info in console.log(basketItems) inside the basket page and the chrome browser(console) looks refreshed too.
Any suggestion why it is happening and how can I optimize to pass the data to child2(basket) from main (APP).
update:2
i have tired to simulated the code issue in sand box with the link below, really appreciate for any advise about my code in codesandbox (to make it better) as this is the first time i have used it
codesandbox
Update:1
i have made a small clip on youtube just to understand the issue i am facing
basketItems goes back to initial state
Main (APP)___|
|_Child 1(Home)
|_Child 2 (Basket)
Snippet from Parent main(App) component
function App() {
const [basketItems, setBasketItems] = useState([]);
const addBasketitems = (product, quantity) => {
setBasketItems(prevItems => [...prevItems, { ...product, quantity }])
}
console.log(basketItems) // here i can see the updated basketItems having customer data as expected [{...}]
return (
<Router>
<div className="App">
<header className="header">
<Nav userinfo={userData} userstatus={siginalready} />
</header>
<Switch>
<Route path="/" exact render={(props) => (
<Home {...props} userData={userData} userstatus={siginalready}
addBasketitems={addBasketitems}
/>
)}
/>
<Route path="/basket" exact render={(props) =>
(<Basket {...props} basketItems={basketItems} />
)}
/>
</Switch>
</div>
</Router>
Snippet from the child(basket)
function Basket({basketItems}) {
console.log(basketItems) // here i only get the [] and not the cusotmer data from parent component
return (
<div>
{`${basketItems}`} // here output is blank
</div>
);
}
export default Basket;
Snippet from the child(Home)
... here once the button is pressed it will pass the userselected details to parent
....
<Button name={producNumber} value={quantities[productName]} variant="primary"
onClick={() => {
addBasketitems(eachproduct, quantities[productName])
}}>
Add to Basket
</Button >
Your function works fine, the reason your output in addbasketItem does not change is the when using setState it takes some time to apply the changes and if you use code below you can see the result.
useEffect(()=>{
console.log('basket:',basketItems)
},[basketItems])
Your Basket component only renders once so replace it with this code and see if it works:
function Basket({ basketItems }) {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(basketItems);
}, [basketItems]);
return <div>{`${items}`}</div>;
}
but for passing data between several components, I strongly suggest that you use provided it is much better.
Lets say I have a view component that has a conditional render:
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
MyInput looks something like this:
class MyInput extends React.Component {
...
render(){
return (
<div>
<input name={this.props.name}
ref="input"
type="text"
value={this.props.value || null}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleTyping.bind(this)} />
</div>
);
}
}
Lets say employed is true. Whenever I switch it to false and the other view renders, only unemployment-duration is re-initialized. Also unemployment-reason gets prefilled with the value from job-title (if a value was given before the condition changed).
If I change the markup in the second rendering routine to something like this:
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<span>Diff me!</span>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
It seems like everything works fine. Looks like React just fails to diff 'job-title' and 'unemployment-reason'.
Please tell me what I'm doing wrong...
Change the key of the component.
<Component key="1" />
<Component key="2" />
Component will be unmounted and a new instance of Component will be mounted since the key has changed.
Documented on You Probably Don't Need Derived State:
When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
What's probably happening is that React thinks that only one MyInput (unemployment-duration) is added between the renders. As such, the job-title never gets replaced with the unemployment-reason, which is also why the predefined values are swapped.
When React does the diff, it will determine which components are new and which are old based on their key property. If no such key is provided in the code, it will generate its own.
The reason why the last code snippet you provide works is because React essentially needs to change the hierarchy of all elements under the parent div and I believe that would trigger a re-render of all children (which is why it works). Had you added the span to the bottom instead of the top, the hierarchy of the preceding elements wouldn't change, and those element's wouldn't re-render (and the problem would persist).
Here's what the official React documentation says:
The situation gets more complicated when the children are shuffled around (as in search results) or if new components are added onto the front of the list (as in streams). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a key.
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
You should be able to fix this by providing a unique key element yourself to either the parent div or to all MyInput elements.
For example:
render(){
if (this.state.employed) {
return (
<div key="employed">
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div key="notEmployed">
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
OR
render(){
if (this.state.employed) {
return (
<div>
<MyInput key="title" ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
<MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
Now, when React does the diff, it will see that the divs are different and will re-render it including all of its' children (1st example). In the 2nd example, the diff will be a success on job-title and unemployment-reason since they now have different keys.
You can of course use any keys you want, as long as they are unique.
Update August 2017
For a better insight into how keys work in React, I strongly recommend reading my answer to Understanding unique keys in React.js.
Update November 2017
This update should've been posted a while ago, but using string literals in ref is now deprecated. For example ref="job-title" should now instead be ref={(el) => this.jobTitleRef = el} (for example). See my answer to Deprecation warning using this.refs for more info.
Use setState in your view to change employed property of state. This is example of React render engine.
someFunctionWhichChangeParamEmployed(isEmployed) {
this.setState({
employed: isEmployed
});
}
getInitialState() {
return {
employed: true
}
},
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<span>Diff me!</span>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
I'm working on Crud for my app. This is how I did it Got Reactstrap as my dependency.
import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';
const LifeActionCreate = () => {
let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();
const onCreate = e => {
const db = firebase.firestore();
db.collection('actions').add({
label: newLifeActionLabel
});
alert('New Life Insurance Added');
setNewLifeActionLabel('');
};
return (
<Card style={{ padding: '15px' }}>
<form onSubmit={onCreate}>
<label>Name</label>
<input
value={newLifeActionLabel}
onChange={e => {
setNewLifeActionLabel(e.target.value);
}}
placeholder={'Name'}
/>
<Button onClick={onCreate}>Create</Button>
</form>
</Card>
);
};
Some React Hooks in there