How to link to next page url in handleSubmit formik? - javascript

I have two functional components in my process. First, I need the first component info to be filled and validated by Formik and Yup and then user can process the next step in the second component by click Next. For now, I can get everything validated and code can reach on handleSubmit() without any problem. But, the problem is that, I could not link to another component using <Link>. I have tried:
// Using this first one, no validation is performed and it will link to another component directly
<Link to="/Next">
<button type="submit">Next</Button>
</Link>
// I have put these inside handleSubmit() but it is undefined.
this.context.router.push('/Next');
Router.push('/Next')
this.props.history.push('/Next')
Mostly I got undefined output on console using these code. It's seems like that i could not access props from functional components like i could in the react class. Here is my first component:
import React from 'react';
import { withFormik, Field } from 'formik';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from "react-router-dom";
const MyForm = props => { const {handleSubmit} = props;
return (
<form onSubmit={handleSubmit}>
<Field type="email" name="email" placeholder="Email" />
<button type="submit">Next</button>
// <Link to="/Next">
// <button type="submit">Next</Button>
// </Link>
</Field>
);
};
const MyEnhancedForm = withFormik({
mapPropsToValues: () => ({ email: '' }),
handleSubmit: (values, formikBag) => {
// Link to next page code
},
})(MyForm);

Yes we can't use this.props in functional components but what we can do is that write the same routing logic in parent component and pass it as a prop in the functional component.
Then we can call the same function in handleSubmit().

If you are using hooks you can follow the approach described in this blog post https://dev.to/httpjunkie/programmatically-redirect-in-react-with-react-router-and-hooks-3hej in order to programmatically redirect with react-router and hooks. A summary:
Include useState in your imports:
import React, { useState } from 'react';
Inside your functional component add:
const [toNext, setToNext] = useState(false)
Inside handleSubmit add:
setToNext(true)
Inside the <form> add:
{toNext ? <Redirect to="/Next" /> : null}

Related

Push to new route without any further actions on the component

We use an external componet which we don't control that takes in children which can be other components or
used for routing to another page. That component is called Modulation.
This is how we are currently calling that external Modulation component within our MyComponent.
import React, {Fragment} from 'react';
import { withRouter } from "react-router";
import { Modulation, Type } from "external-package";
const MyComponent = ({
router,
Modulation,
Type,
}) => {
// Need to call it this way, it's how we do modulation logics.
// So if there is match on typeA, nothing is done here.
// if there is match on typeB perform the re routing via router push
// match happens externally when we use this Modulation component.
const getModulation = () => {
return (
<Modulation>
<Type type="typeA"/> {/* do nothing */}
<Type type="typeB"> {/* redirect */}
{router.push('some.url.com')}
</Type>
</Modulation>
);
}
React.useEffect(() => {
getModulation();
}, [])
return <Fragment />;
};
export default withRouter(MyComponent);
This MyComponent is then called within MainComponent.
import React, { Fragment } from 'react';
import MyComponent from '../MyComponent';
import OtherComponent1 from '../OtherComponent1';
import OtherComponent2 from '../OtherComponent2';
const MainComponent = ({
// some props
}) => {
return (
<div>
<MyComponent /> {/* this is the above component */}
{/* We should only show/reach these components if router.push() didn't happen above */}
<OtherComponent1 />
<OtherComponent2 />
</div>
);
};
export default MainComponent;
So when we match typeB, we do perform the rerouting correctly.
But is not clean. OtherComponent1 and OtherComponent2 temporarily shows up (about 2 seconds) before it reroutes to new page.
Why? Is there a way to block it, ensure that if we are performing router.push('') we do not show these other components
and just redirect cleanly?
P.S: react-router version is 3.0.0

Redirect to another page based on form input in Next.js

I'm just getting started with Next.js and SPA world. I'm used to PHP and plain JS so this is totally new to me.
So I have a form with basically a text input and a submit button. What I need to do is as simple as redirecting the user based on the text field they submit but I can't figure out how.
For example: the form is in the homepage, the user inputs "foo" and submits. What the result should be is that the user is redirected to "/channel/foo".
Any tip? Thank you!
Ok..
For this you can use useRouter hook provided by the nextjs
first you can import it
import { useRouter } from "next/router";
then you can create it's instance and use it to push the react app to new route
based on the value of the given form input value
Here I am using a react state to go to new route
import {useRouter} from 'next/router'
import {useState} from 'react'
export default function SampleComponent(){
const router = useRouter()
const [route, setRoute] = useState()
const handleSubmit = (e) => {
e.preventDefault()
router.push("someBasePath/" + route)
}
return(
<div>
<h1>Example Form</h1>
<form onSubmit={handleSubmit}>
<input type="text" name='route' onChange={(e)=>{setRoute(e.target.value)}} />
<button type="submit">Submit</button>
</form>
</div>
)
}
I hope you are familial with react and useState hook
I hope it will solve your problem
You can use useRouter hook to navigate to another page. For example:
import { useRouter } from "next/router";
const Component = () => {
const [inputValue, setInputValue] = useState("");
const router = useRouter();
const onChange = e => {
setInputValue(e.target.value);
}
const handleSubmit = e => {
e.preventDefault();
router.push(`/channel/${inputValue}`)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={onChange} />
</form>
)
}
To handle redirections in Next.js, you can use the router hook useRouter() available in any component, with push. You can find a good explanation in the documentation.
Depending on the way you handle your form, you could have a callback on the button, or handle it with the onSubmit of the form.
About react forms: react forms
About handling button click in React: react buttons

Navigate to welcome page after login page using React Router Dom v6

I'm a beginner learning React and using React v 17.0.2, react-router-dom v 6.0.2. I'm following a course made for react-router-dom v4. I'm not able to get page navigation working if I try to navigate from a successful login to append a welcome message to the url. In v4 this is achieved by a {this.props.history.push("/welcome") method. I'm not able to something equivalent in V6. Specifically, I would like to know how to handle the loginClicked method.
Based on the helpful guidance from Himanshu Singh, I tried the following:
import { computeHeadingLevel } from '#testing-library/react'
import React, { Component } from 'react'
import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'
class TodoApp extends Component {
render() {
return (
<div className="TodoApp">
<Router>
<Routes>
<Route path="/" exact element={<LoginComponent />} />
<Route path="/enlite" element={<LoginComponent />} />
<Route path="/welcome" element={<WelcomeComponent />} />
</Routes>
</Router>
{/* <LoginComponent /> */}
</div>
)
}
}
class WelcomeComponent extends Component {
render() {
return <div>Welcome to Enlite</div>
}
}
class LoginComponent extends Component {
constructor(props) {
super(props)
this.state = {
username: 'testuser',
password: '',
hasLoginFailed: false,
showSuccessMessage: false
}
this.handleChange = this.handleChange.bind(this)
this.loginClicked = this.loginClicked.bind(this)
}
handleChange(event) {
this.setState(
{
[event.target.name]
: event.target.value
})
}
**loginClicked() {
if (this.state.username === 'testuser' &&
this.state.password === 'dummy') {
function HandlePageNav() {
let navigate = useNavigate()
navigate('/welcome')
}
**HandlePageNav();**
}
else {
this.setState({ showSuccessMessage: false })
this.setState({ hasLoginFailed: true })
}
}**
render() {
return (
<div>
{this.state.hasLoginFailed && <div>Invalid Credentials</div>}
{this.state.showSuccessMessage && <div>Welcome to Enlite</div>}
User Name: <input type="text" name="username" value={this.state.username} onChange={this.handleChange} />
Password: <input type="password" name="password" value={this.state.password} onChange={this.handleChange} />
<button onClick={this.loginClicked}>Login</button>
</div>
)
}
}
export default TodoApp
This gives the following error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
Basically calling hooks in class components is not supported. I also tried to completely do away with the function like this:
loginClicked() {
if (this.state.username === 'testuser' &&
this.state.password === 'dummy') {
let navigate = useNavigate()
navigate('/welcome')
}
else {
this.setState({ showSuccessMessage: false })
this.setState({ hasLoginFailed: true })
}
}
This gives a compile error:
Line 85:32: React Hook "useNavigate" cannot be called in a class
component. React Hooks must be called in a React function component or
a custom React Hook function react-hooks/rules-of-hooks Line 89:13:
'HandlePageNav' is not defined
no-undef
The above makes me wonder if I need to refactor my entire code into a function component or if there's a way to achieve this page navigation but keeping the class component. Other than that I would appreciate any help or insights on this problem. Thanks in advance.
UseNavigate Hook will not work here because hooks are meant to be used in functional components not class components.
What you can do for now is, since no proper doc is provided for class component
Try to use Functional Components : the most easiest way
Use a HOC component around the class component and pass history and other necessary props to it through that component.
Note: Here I tried second approach. You can follow this: https://codesandbox.io/s/snowy-moon-30br5?file=/src/App.js

How to communicate between React components which do not share a parent?

I am adding React to an already existing front end and am unsure how to communicate data between components.
I have a basic text Input component and a Span component, mounted separately. When the user types into the input, I want the text of the span to change to what is input.
Previously I would start a React project from scratch and so have the Input and Span share an App component as a parent. I'd use a prop function to lift the text state from the Input to the App and pass it down the value to the Span as a prop. But from scratch is not an option here.
I've considered:
Redux etc. As I'm introducing React piece by piece to this project and some team members have no React experience, I want to avoid using Redux or other state management libraries until very necessary, and it seems overkill for this simple case.
React Context API. This doesn't seem correct either, as my understanding was that context API should be kept for global data like "current authenticated user, theme, or preferred language" shared over many components, not just for sharing state between 2 components.
UseEffect hook. Using this hook to set the inner HTML of the Span component i.e
function Input() {
const inputProps = useInput("");
useEffect(() => {
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;
})
return (
<div>
<h3>Name this page</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}
Which sort of negates the whole point of using React for the Span?
I've gone with the UseEffect hook for now but haven't found any clear answers in the React docs or elsewhere online so any advice would be helpful.
Thanks.
Input.jsx
import React, { useState, useEffect } from 'react';
function useInput(defaultValue) {
const [value, setValue] = useState(defaultValue);
function onChange(e) {
setValue(e.target.value);
}
return {
value,
onChange
}
}
function Input() {
const inputProps = useInput("");
useEffect(() => {
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;
})
return (
<div>
<h3>React asks what shall we name this product?</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}
export default Input;
PageTitle.jsx
import React from 'react';
function PageTitle(props) {
var title = "Welcome!"
return (
<span>{props.title}</span>
)
}
;
export default PageTitle
Index.js
// Imports
const Main = () => (
<Input />
);
ReactDOM.render(
<Main />,
document.getElementById('react-app')
);
ReactDOM.render(
<PageTitle title="Welcome"/>,
document.getElementsByClassName('page-title')[0]
);
In React, data is supposed to flow in only one direction, from parent component to child component. Without getting into context/redux, this means keeping common state in a common ancestor of the components that need it and passing it down through props.
Your useEffect() idea isn't horrible as a kind of ad hoc solution, but I would not make PageTitle a react component, because setting the value imperatively from another component really breaks the react model.
I've used useEffect() to set things on elements that aren't in react, like the document title and body classes, as in the following code:
const siteVersion = /*value from somewhere else*/;
//...
useEffect(() => {
//put a class on body that identifies the site version
const $ = window.jQuery;
if(siteVersion && !$('body').hasClass(`site-version-${siteVersion}`)) {
$('body').addClass(`site-version-${siteVersion}`);
}
document.title = `Current Site: ${siteVersion}`;
}, [siteVersion]);
In your case, you can treat the span in a similar way, as something outside the scope of react.
Note that the second argument to useEffect() is a list of dependencies, so that useEffect() only runs whenever one or more changes.
Another side issue is that you need to guard against XSS (cross site scripting) attacks in code like this:
//setting innerHTML to an unencoded user value is dangerous
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;
Edit:
If you want to be even more tidy and react-y, you could pass a function to your input component that sets the PageTitle:
const setPageTitle = (newTitle) => {
//TODO: fix XSS problem
document.getElementsByClassName('page-title')[0].innerHTML = newTitle;
};
ReactDOM.render(
<Main setPageTitle={setPageTitle} />,
document.getElementById('react-app')
);
//inside Main:
function Input({setPageTitle}) {
const inputProps = useInput("");
useEffect(() => {
setPageTitle(inputProps.value);
})
return (
<div>
<h3>React asks what shall we name this product?</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}
You can create a HOC or use useContext hook instead

Tests React component with jest and enzyme

i have components presented below. I am totally new in unit testing. Can anyone give any one give me advice how and what should I test in this component? I was trying to shallow render it, to check is text in h2 is present but i still getting errors.
import React, { useEffect } from 'react';
import { Form, Field } from 'react-final-form';
import { useHistory, Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { loginUser, clearErrorMessage } from '../../redux/auth/authActions';
import Input from '../Input/Input';
import ROUTES from '../../routes/routes';
import './LoginForm.scss';
const LoginForm = () => {
const dispatch = useDispatch();
const history = useHistory();
const { loading, isLogged, errorMessage } = useSelector(state => state.auth);
useEffect(() => {
if (isLogged) {
history.push('/');
}
return () => {
dispatch(clearErrorMessage());
};
}, [dispatch, history, isLogged]);
const handleSubmitLoginForm = values => {
if (!loading) {
dispatch(loginUser(values));
}
};
const validate = ({ password }) => {
const errors = {};
if (!password) {
errors.password = 'Enter password!';
}
return errors;
};
return (
<article className="login-form-wrapper">
<h2>SIGN IN</h2>
<Form onSubmit={handleSubmitLoginForm} validate={validate}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit} autoComplete="off" className="login-form">
<div className="login-form__field">
<Field name="email" component={Input} type="email" label="E-mail" />
</div>
<div className="login-form__buttons">
<button type="submit" className={loading ? 'button-disabled' : ''}>
Sign in
</button>
</div>
</form>
)}
</Form>
</article>
);
};
export default LoginForm;
I am open for any advices :)
First of all, I am not recommending using shallow in your tests and here is a great article why.
I also recommend to check out react-testing-library instead of Enzyme as it is much nicer to use.
Now, to answer your question. You are using here hooks for redux and react-router, so you need to provide the necessary data to your componenent in test so that it can use those hooks. Let me show you an example test (that checks text in h2 element):
import React from 'react';
import { mount } from 'enzyme';
import {Provider} from 'react-redux';
import {MemoryRouter, Route} from 'react-router';
import LoginForm from './LoginForm';
describe('Login Form', () => {
it('should have SIGN IN header', () => {
const store = createStore();
const component = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/login']}>
<Route path="/:botId" component={LoginForm} />
</MemoryRouter>
</Provider>
)
expect(component.find('h2').text()).toEqual('SIGN IN');
});
});
Some explanation to this example.
I am using mount instead of shallow as I prefer to render as much as possible in my test, so that I can check if everything works together as it should.
You can see that I am not rendering my component directly, but rather it is wrapped with other components (Provider from react-redux and MemoryRouter from react-router). Why? Because I need to provide context to my Component. In this case it's redux and router context so that the data used inside exists and can be found (for example useSelector(state => state.auth) must have some state provided so that it can access auth property). If you remove any of them you would get some error saying that this context is missing - go ahead and check for yourself :).
See here for some details around testing with router context
As for testing with redux in my example there is a createStore function that I didn't define as there are a few approaches to this. One involves creating a real store that you use in your production application. This is the one that I prefer and colleague of mine wrote great article around this topic here. Other is to create some kind of mock store, like in this article. Again, I prefer the first approach, but whichever is better for you.
Answering your other question on what should you test in this example. There are multiple possibilities. It all depends mostly on you business case, but examples that I would test here includes:
Typing something into an input, clicking a button and observing that login is successful (by redirecting to new path - / in this case)
not typing a password and clicking a button - error should be shown
Checking if button class changes when it's loading
Do not dispatch login action twice, when already loading
And so on...
That's really just a tip of an iceberg on what could be written around testing, but I hope it helps and gives you a nice start to dig deeper into the topic.

Categories

Resources