ReactJS search bar child to parent - reactjs

I have a search bar component which is viewed on all pages of a project. Is there a way that I can have the same search bar component but have a property that I could pass so that it knows which data to search through. My search component is <SearchBar type="PASS THIS VALUE" /> and this is rendered:
import React, { Component } from 'react';
class SearchBar extends Component {
render () {
function handleClick(e) {
e.preventDefault();
console.log("Clicked!!!");
}
return (
<div className="row main-search">
<div className="column">
<form action="">
<fieldset>
<label htmlFor="search">
<input type="text" placeholder="Start typing..." id="search-box" />
</label>
</fieldset>
</form>
</div>
<div className="column">
<div className="float-right"><button className="add-new" onClick={handleClick}>Add New</button></div>
</div>
</div>
)
}
}
export default SearchBar;
If this isn't possible is there a way that the searchBar can know which page the user is on and know which data to search through?
Update:
I am using a router element and the searchBar component is located inside parent App.js. I'm using the router like:
<SearchBar />
<Route
exact path="/" children={({ match, ...rest }) => (
<TransitionGroup component={firstChild}>
{match && <TourList {...rest} />}
</TransitionGroup>
)}/>

You can access the props through this.props and set the value of input accordingly, but beware that the component will become controlled by react flow, you won't be able to change the input, thus you need onChange event handler for that and controlled local state version of that prop instead.
It may go like this:
import React, { Component } from 'react';
class SearchBar extends Component {
state = { // pass default value from "type" prop
type: this.props.type
};
// making sure we don't call setState excessively
// as per https://reactjs.org/docs/react-component.html#componentwillreceiveprops
componentWillReceiveProps(nextProps) {
if (nextProps.type !== this.props.type) {
this.setState({ type: nextProps.type });
}
}
// pulling out handleClick function from render()
// to avoid creating it on every render call
handleClick = (e) => {
e.preventDefault();
console.log("Clicked!!!");
}
// making sure we update our controlled component state
// so we can actually type and get the change back in the value
// more info here: https://reactjs.org/docs/forms.html#controlled-components
handleChange = e => {
this.setState({ type: e.target.value });
};
render () {
return (
<div className="row main-search">
<div className="column">
<form action="">
<fieldset>
<label htmlFor="search">
<input type="text"
placeholder="Start typing..."
id="search-box"
onChange={this.handleChange}
// listening for our local and controlled version
// of "type" prop that is now reacting both to
// parent prop change and to local input change through typing
value={this.state.type}/>
</label>
</fieldset>
</form>
</div>
<div className="column">
<div className="float-right">
<button className="add-new" onClick={handleClick}>Add New</button>
</div>
</div>
</div>
)
}
}
export default SearchBar;

Related

state value is undefined on Redirect

I need to pass data to another page using react redirect method, but I'm getting undefined value of state.
Form Component:
onSubmit I'm getting values of state through console.log but these values are not passing to Show component
class RegisterForm extends React.Component{
//constructor here
// handleInputChange function here
submit(){
console.log(this.state);
const data = this.state;
return <Redirect to={{
pathname: '/show',
state: {data: data},
}}
/>
}
render(){
return(
<div>
<div class="row">
<div class="col-md-6 offset-md-3">
<br /><br />
<h3>Register Form</h3><br />
<form
action="show"
>
<div class="form-row">
<div class="form-group col-md-6">
<label>First Name :</label>
<input type="text" class="form-control" name="first_name" onChange={this.handleInputChange} />
</div>
<div class="form-group col-md-6">
<label>Last Name :</label>
<input type="text" class="form-control" name="last_name" onChange={this.handleInputChange} />
</div>
</div>
<div class="form-row">
<div class="col-md-12 text-center">
<button type="submit" class="btn btn-primary" onClick={()=>this.submit()}>Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
)
}
}
export default RegisterForm;
props.location.state // undefined
can you help me?
Form action - Attribute for form submission
The URL that processes the form submission.
Question
It redirects to show page because I've used action="show" can you
tell me why submit function is not calling
The submit function isn't linked to anything in the UI. The form has a button of type "submit", so when clicked the default form actions are taken, i.e. the form is submitted and tries to go to the page specified by the action. React doesn't quite work in this way.
Since the button type is already "submit" you can simply replace the action prop with the onSubmit callback.
<form onSubmit={submit}> ...
Now that the submit callback handler is being invoked you'll also want to not take the default submit action on the form (likely a page reload). Returning JSX won't work here either, it won't ever get rendered. In order to do the redirect you'll need to do this using the history object from the Router/Route. This assumes RegisterForm is rendered directly as component or render prop of a Route or has been decorated with the withRouter Higher Order Component.
submit(event) {
event.preventDefault();
console.log(this.state);
const data = this.state;
const { history } = this.props;
history.replace({
pathname: '/show',
state: { data },
});
}
I would recommend you to use Function Components insted of class components,
Class components are too old, Life becomes too much easier with function components, and react hooks. I suggest you to go though react fucntion components and react hooks.
here I converted your class component into the function component with some hooks,
in submit method, just use history.push('/show',state).
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
const RegisterForm = () => {
const [state, setState] = useState({
first_name: "",
last_name: "",
});
const history = useHistory();
const handleInputChange = (key) => (e) => {
let value = e.target.value;
setState((s) => ({ ...s, [key]: value }));
};
const handleSubmit = (e) => {
history.push("/show", state);
};
return (
<div>
<div className="row">
<div className="col-md-6 offset-md-3">
<br />
<br />
<h3>Register Form</h3>
<br />
<div className="form-row">
<div className="form-group col-md-6">
<label>First Name :</label>
<input
type="text"
className="form-control"
name="first_name"
value={state.first_name}
onChange={handleInputChange("first_name")}
/>
</div>
<div className="form-group col-md-6">
<label>Last Name :</label>
<input
type="text"
className="form-control"
name="last_name"
value={state.last_name}
onChange={handleInputChange("last_name")}
/>
</div>
</div>
<div className="form-row">
<div className="col-md-12 text-center">
<button
className="btn btn-primary"
onClick={handleSubmit}
>
Submit
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default RegisterForm;
Now, In the component at /show route,
you can use,
import { useLocation } from "react-router-dom";
....
....
// inside the Component,
let location = useLocation();
console.log(location);
console.log(location.state); // you can see, your state is available here...
that's it!
let me know if you found some difficulties here.
good luck ;)

Setting the default value of an input field after data is retrieved causes the content to overlap and the "onChange" event not to be triggered

I have an "edit category" component in my React application.
The ID is passed through the URL.
When the component is mounted, the action "fetchCategory" is called, which updates the props on the component with the current category.
I have a form which I want to be pre-populated, which I'm currently doing using the defaultValue on the input.
However, this isn't reflected on the state and the label for the text field overlaps the input field.
Any help would be greatly appreciated. I'll leave snippets of my code below which could help with understanding what I'm trying to do.
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchCategory } from "../../store/actions/categoryActions";
class AddOrEditCategory extends Component {
componentDidMount() {
this.props.fetchCategory(this.props.match.params.id);
if (this.props.match.params.id) {
this.setState({
_id: this.props.match.params.id
});
}
}
handleSubmit = e => {
e.preventDefault();
console.log(this.state);
};
handleChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
render() {
const addingNew = this.props.match.params.id === undefined;
return (
<div className="container">
<h4>{addingNew ? "Add category" : "Edit category"}</h4>
<form onSubmit={this.handleSubmit}>
<div className="input-field">
<input
type="text"
id="name"
defaultValue={this.props.category.name}
onChange={this.handleChange}
/>
<label htmlFor="name">Category name</label>
</div>
<div className="input-field">
<input
type="text"
id="urlKey"
onChange={this.handleChange}
defaultValue={this.props.category.urlKey}
/>
<label htmlFor="urlKey">URL Key</label>
</div>
<button className="btn">{addingNew ? "Add" : "Save"}</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
category: state.categoryReducer.category
};
};
export default connect(
mapStateToProps,
{ fetchCategory }
)(AddOrEditCategory);
EDIT: Included whole component as requested
You need to replace the 'defaultValue' attribute with 'value' in the inputs.
You are using a controlled vs uncontrolled component. You dont need to use defaultValue.
You can set the initial values on the promise success for fetchCategory
componentDidMount() {
this.props.fetchCategory(this.props.match.params.id).then(response => {
// Set the initial state here
}
}
OR in
componentWillReceiveProps(nextProps) {
// Compare current props with next props to see if there is a change
// in category data received from action fetchCategory and set the initial state
}
React docs
<form onSubmit={this.handleSubmit}>
<div className="input-field">
<input
type="text"
id="name"
onChange={this.handleChange}
value={this.state.name} //<---
/>
<label htmlFor="name">Category name</label>
</div>
<div className="input-field">
<input
type="text"
id="urlKey"
onChange={this.handleChange}
value={this.state.urlKey}
/>
<label htmlFor="urlKey">URL Key</label>
</div>
<button className="btn">{addingNew ? "Add" : "Save"}</button>
</form>

How can I get an input's value on a button click in a Stateless React Component?

I have the following functional component
const input = props => (
<div>
<input placeholder="Type a message..." />
<div onClick={props.send} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
How could I possibly pass the value of the input to props.send()?
I found a solution for this exact scenario on React's official docs: https://reactjs.org/docs/refs-and-the-dom.html#refs-and-functional-components
This approach allows your component to remain stateless and also doesn't require you to update the parent component on every change.
Basically,
const input = props => {
let textInput = React.createRef();
function handleClick() {
console.log(textInput.current.value);
}
return (
<div>
<input ref={textInput} placeholder="Type a message..." />
<div onClick={handleClick} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
}
Edit May 2021: Since this answer seems to be getting some attention, I have updated the answer to use a hooks based approach as well, since that is what I would use now (If using React 16.8 and above).
const input = props => {
const [textInput, setTextInput] = React.useState('');
const handleClick = () => {
console.log(textInput);
props.send(textInput);
}
const handleChange = (event) => {
setTextInput(event.target.value);
}
return (
<div>
<input onChange={handleChange} placeholder="Type a message..." />
<div onClick={handleClick} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
}
There are many ways to do it since you're very much concerned about performance. Here is the implementation, your component will be rendered only when you click on send button which actually means state will be updated once and input value will be displayed in parent component.
const Input = props => {
return (
<div>
<input onChange={props.changeHandler} placeholder="Type a message..." />
<button onClick={props.send}>send</button>
</div>
);
};
class App extends Component {
state = {
inputValue: ""
};
inputValue = '';
send = () => {
this.setState({ inputValue: this.inputValue });
};
changeHandler = event => {
this.inputValue = event.target.value;
};
render() {
console.log("In render");
return (
<React.Fragment>
<Input changeHandler={this.changeHandler} send={this.send} />
<div> {this.state.inputValue}</div>
</React.Fragment>
);
}
}
Since you mentioned that you just started with React, I'd suggest that you work through the documentation (which offers nice explanation).
According to your comment, the usage of a functional component is not a requirement. Therefore I'd recommend to do it that way -->
Your CustomInput component:
import React from "react";
import PropTypes from "prop-types";
class CustomInput extends React.Component {
constructor() {
super();
this.textInput = React.createRef();
}
static propTypes = {
send: PropTypes.func
};
render() {
const { send } = this.props;
return (
<React.Fragment>
<input placeholder="Type a message..." ref={this.textInput} />
<div
onClick={() => send(this.textInput.current.value)}
className="icon"
>
CLICK ME
</div>
</React.Fragment>
);
}
}
export default CustomInput;
If you noticed, I've replaced the empty div with React.Fragment. In that case you can omit the unnecessary <div> wrappings (if those are not required) which will keep your DOM clean (Read more about it here.
Usage:
<CustomInput
send={(prop) => {
console.log(prop)
}}
/>
I just used a dummy function which will log the input value to the console..
You can check the working example (Make sure to trigger the console in the editor) here
Posting this answer, If incase someone is using an earlier release of React 16.3. We can achieve the same thing by using callback refs instead without having to maintain state or having onChange event handler:
const input = props => (
<div>
<input ref={props.myRef} placeholder="Type a message..." />
<div onClick={props.send} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
Calling that Input Component
handleClick = () => console.log(this.inputRef.value);
<Input myRef={el => this.inputRef = el} send={this.handleClick} />

update list array from child component

new react user here.
i am trying to access form data in my parent app from the child form. I am trying to alert or console the data from the parent so I can visually see what was typed in the form. Once I can access the data in the parent I will try and move it to my list array.
PARENT
class App extends Component {
constructor() {
super();
this.state = {
lists: [],
items: {}
};
}
handleAddList(s) {
alert('I am calling function from child')
console.log(this.refs.id.value) // this errors out on me
}
render() {
return (
<div className="App">
<AddList addList={this.handleAddList.bind(this)} />
<div id="listsDiv" className="List">
<Lists lists={this.state.lists} items={this.state.items} addItem {this.handleAddItem.bind(this)} />
</div>
</div>
);
}
}
CHILD
class AddList extends Component {
handleSubmit(e) {
e.preventDefault();
alert(this.refs.id.value)
this.props.addList()
}
render() {
return (
<div id="addListDiv">
<form onSubmit={this.handleSubmit.bind(this)}>
<div id='addList'>
<label>What will be on your next list?
<input type='text' ref='id' id='newID'></input>
</label>
</div><br />
<input type='submit' value='Create List' />
</form>
</div>
);
}
}
You should set the ref on the input using a callback, like this:
<input type='text' ref={input => { this.input = input; }} id='newID'></input>
Then access it in your event handler like this:
alert(this.input.value);
However, if you are new to React, you should try using controlled components before you try to use refs.
https://reactjs.org/docs/forms.html
CHILD
import ReactDOM from 'react-dom';
class AddList extends Component {
handleSubmit(e) {
e.preventDefault();
var day = ReactDOM.findDOMNode(this.refs.id).value.trim();
this.props.addList(day);
}
render() {
return (
<div id="addListDiv">
<form onSubmit={this.handleSubmit.bind(this)}>
<div id='addList'>
<label>What will be on your next list?
<input type='text' ref='id' id='newID'></input>
</label>
</div><br />
<input type='submit' value='Create List' />
</form>
</div>
);
}
}
PARENT
class App extends Component {
handleAddList(args) {
console.log(args);
}
render() {
return (
<div className="App">
<AddList addList={this.handleAddList.bind(this)} />
</div>
);
}
}
Edit it a little to work for you.

Reactjs Redux dispatcher for onChange event resets input field

I'm trying to implement a simple login solution using redux pattern and react-router v4.
I'm saving the email input in global state by using onChange event. The function in onchange event dispatches action, passing e.target as its input.
However as soon as the dispatch occurs, the input field gets reset. I have tested the input field by commenting out the dispatcher and it works, thus concluding the dispatcher is somehow resetting input field.
class LoginForm extends React.Component {
constructor(props) {
super(props)
this.state = this.props.store.getState()
}
handleSignup(){
this.setState({showSignup:!this.state.showSignup})
console.log(this.state.showSignup);
}
validatorDispatch(e){
e.preventDefault()
var target = e.target
this.props.store.dispatch(input_validator_action(target));
}
render(){
let emailValidationClassName = "form-control"
let passwordValidationClassName = "form-control"
let confirmPasswordValidationClassName = "form-control"
if (this.state.password){
passwordValidationClassName="form-control " + (this.state.isPasswordValid ? "is-valid":"is-invalid")
}
if (this.state.email){
emailValidationClassName="form-control " + (this.state.isEmailValid ? "is-valid":"is-invalid")
}
if (this.state.confirmPassword){
confirmPasswordValidationClassName="form-control " + (this.state.doesPasswordMatch ? "is-valid":"is-invalid")
}
return (<div>
<div className="row m-5">
<div className="col-md-4"></div>
<div className="col-md-4">
<div className="form-group">
<label htmlFor="emailAddress">Email address: </label>
<input type="email" className={emailValidationClassName} name="email" ref="email" onChange={(e)=>this.validatorDispatch(e)} placeholder="Enter email"/>
{this.state.isEmailValid ? null
: <div className="invalid-feedback">
Please provide a valid email address.
</div>}
</div>
<div className="form-group">
<label htmlFor="password">Password: </label>
<input type="password" className={passwordValidationClassName} name="password" ref="password" onChange={this.validatorDispatch.bind(this)} placeholder="Password"/>
{this.state.isPasswordValid ? null
: <div className="invalid-feedback">
Password length should be greater than 8, and contain lowercase, uppercase, digits and special symbols.
</div>}
<Link to="/forgotpassword"><small>Forgot Password?</small></Link>
</div>
<div className="form-check">
<label className="custom-control custom-checkbox">
<input type="checkbox" className="custom-control-input" checked={this.state.showSignup} onChange={this.handleSignup.bind(this)}/>
<span className="custom-control-indicator"></span>
<span className="custom-control-description">I am a new User</span>
</label>
</div>
<button className="btn btn-info">Submit</button>
</div>
<div className="col-md-4"></div>
</div>
</div>
)
}
}
I'd really appreciate your insights.
Edited validatorDispatch function below
var input = e.target.value
var name = e.target.name
this.props.store.dispatch(input_validator_action(input,name));
My reducer looks like this
import validator from 'email-validator';
var passwordValidator = require('password-validator')
var schema = new passwordValidator();
schema
.has().symbols()
.is().min(8)
.has().uppercase()
.has().lowercase()
.has().digits()
.has().not().spaces()
const initialState = {}
function input_validator(action,state){
let input = action.payload.textInput
let name = action.payload.textName
switch (name) {
case "email":
if(validator.validate(input)){
return Object.assign({},state,{isEmailValid:true,email:input})
}
else{
return Object.assign({},state,{isEmailValid:false,email:input})
}
case "password":
if(schema.validate(input)){
return Object.assign({},state,{isPasswordValid:true,password:input})
}
else{
return Object.assign({},state,{isPasswordValid:false,password:input})
}
case "confirmPassword":
if(input===state.password){
return Object.assign({},state,
{doesPasswordMatch:true,confirmPassword:input})
}
else{
return Object.assign({},state,
{doesPasswordMatch:false,confirmPassword:input})
}
default:
return state
}
}
export default (state=initialState, action) => {
switch(action.type){
case 'INPUT_VALIDATOR':
return input_validator(action,state)
default:
return state
}
}
Action
export const input_validator_action = (input,name) =>{
return{
type:'INPUT_VALIDATOR',
payload:{
textInput:input,
textName:name
}
}
}
I finally figured it out. It helped me understand React Router v4 even better. I was rendering my LoginForm class file (named login.js) from a different file (named EntryRoute.js) as a route using component prop.
i.e.
<Switch>
<Route exact path="/" component={EnteryButton}/>
<Route path="/login" component={()=><Login store={props.store}/>} />
</Switch>
Instead it should be done using render prop.
as in-
<Route path="/login" render={()=><Login store={props.store}/>} />
The concept behind it is that when you pass inline function to component prop, it unmounts and mounts the passed function at every state change. So that's why my input field was being reset, since value inside the field was used inside global redux state.

Resources