Event Handlers in React Stateless Components - javascript

Trying to figure out an optimal way to create event handlers in React stateless components. I could do something like this:
const myComponent = (props) => {
const myHandler = (e) => props.dispatch(something());
return (
<button onClick={myHandler}>Click Me</button>
);
}
The drawback here being that every time this component is rendered, a new "myHandler" function is created. Is there a better way to create event handlers in stateless components that can still access the component properties?

Applying handlers to elements in function components should generally just look like this:
const f = props => <button onClick={props.onClick}></button>
If you need to do anything much more complex it's a sign that either a) the component shouldn't be stateless (use a class, or hooks), or b) you should be creating the handler in an outer stateful container component.
As an aside, and undermining my first point slightly, unless the component is in a particularly intensively re-rendered part of the app there's no need to worry about creating arrow functions in render().

Using the new React hooks feature it could look something like this:
const HelloWorld = ({ dispatch }) => {
const handleClick = useCallback(() => {
dispatch(something())
})
return <button onClick={handleClick} />
}
useCallback creates a memoised function, meaning a new function will not be regenerated on each render cycle.
https://reactjs.org/docs/hooks-reference.html#usecallback
However, this is still at proposal stage.

How about this way :
const myHandler = (e,props) => props.dispatch(something());
const myComponent = (props) => {
return (
<button onClick={(e) => myHandler(e,props)}>Click Me</button>
);
}

If the handler relies on properties that change, you will have to create the handler each time since you lack a stateful instance on which to cache it. Another alternative which may work would be to memoize the handler based on the input props.
Couple implementation options
lodash._memoize
R.memoize
fast-memoize

Here is my simple favorite products list implemented with react and redux writing in typescript. You can pass all arguments you need in the custom handler and return a new EventHandler which accepts origin event argument. It's MouseEvent in this example.
Isolated functions keep jsx cleaner and prevent from breaking several linting rules. Such as jsx-no-bind, jsx-no-lambda.
import * as React from 'react';
import { DispatchProp, Dispatch, connect } from 'react-redux';
import { removeFavorite } from './../../actions/favorite';
interface ListItemProps {
prod: Product;
handleRemoveFavoriteClick: React.EventHandler<React.MouseEvent<HTMLButtonElement>>;
}
const ListItem: React.StatelessComponent<ListItemProps> = (props) => {
const {
prod,
handleRemoveFavoriteClick
} = props;
return (
<li>
<a href={prod.url} target="_blank">
{prod.title}
</a>
<button type="button" onClick={handleRemoveFavoriteClick}>×</button>
</li>
);
};
const handleRemoveFavoriteClick = (prod: Product, dispatch: Dispatch<any>) =>
(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
dispatch(removeFavorite(prod));
};
interface FavoriteListProps {
prods: Product[];
}
const FavoriteList: React.StatelessComponent<FavoriteListProps & DispatchProp<any>> = (props) => {
const {
prods,
dispatch
} = props;
return (
<ul>
{prods.map((prod, index) => <ListItem prod={prod} key={index} handleRemoveFavoriteClick={handleRemoveFavoriteClick(prod, dispatch)} />)}
</ul>
);
};
export default connect()(FavoriteList);
Here is the javascript snippet if you are not familiar with typescript:
import * as React from 'react';
import { DispatchProp, Dispatch, connect } from 'react-redux';
import { removeFavorite } from './../../actions/favorite';
const ListItem = (props) => {
const {
prod,
handleRemoveFavoriteClick
} = props;
return (
<li>
<a href={prod.url} target="_blank">
{prod.title}
</a>
<button type="button" onClick={handleRemoveFavoriteClick}>×</button>
</li>
);
};
const handleRemoveFavoriteClick = (prod, dispatch) =>
(e) => {
e.preventDefault();
dispatch(removeFavorite(prod));
};
const FavoriteList = (props) => {
const {
prods,
dispatch
} = props;
return (
<ul>
{prods.map((prod, index) => <ListItem prod={prod} key={index} handleRemoveFavoriteClick={handleRemoveFavoriteClick(prod, dispatch)} />)}
</ul>
);
};
export default connect()(FavoriteList);

solution one mapPropsToHandler and event.target.
functions are objects in js so its possible to attach them properties.
function onChange() { console.log(onChange.list) }
function Input(props) {
onChange.list = props.list;
return <input onChange={onChange}/>
}
this function only bind once a property to a function.
export function mapPropsToHandler(handler, props) {
for (let property in props) {
if (props.hasOwnProperty(property)) {
if(!handler.hasOwnProperty(property)) {
handler[property] = props[property];
}
}
}
}
I do get my props just like this.
export function InputCell({query_name, search, loader}) {
mapPropsToHandler(onChange, {list, query_name, search, loader});
return (
<input onChange={onChange}/>
);
}
function onChange() {
let {query_name, search, loader} = onChange;
console.log(search)
}
this example combined both event.target and mapPropsToHandler. its better to attach functions to handlers only not numbers or strings. number and strings could be passed with help of DOM attribute like
<select data-id={id}/>
rather than mapPropsToHandler
import React, {PropTypes} from "react";
import swagger from "../../../swagger/index";
import {sync} from "../../../functions/sync";
import {getToken} from "../../../redux/helpers";
import {mapPropsToHandler} from "../../../functions/mapPropsToHandler";
function edit(event) {
let {translator} = edit;
const id = event.target.attributes.getNamedItem('data-id').value;
sync(function*() {
yield (new swagger.BillingApi())
.billingListStatusIdPut(id, getToken(), {
payloadData: {"admin_status": translator(event.target.value)}
});
});
}
export default function ChangeBillingStatus({translator, status, id}) {
mapPropsToHandler(edit, {translator});
return (
<select key={Math.random()} className="form-control input-sm" name="status" defaultValue={status}
onChange={edit} data-id={id}>
<option data-tokens="accepted" value="accepted">{translator('accepted')}</option>
<option data-tokens="pending" value="pending">{translator('pending')}</option>
<option data-tokens="rejected" value="rejected">{translator('rejected')}</option>
</select>
)
}
solution two. event delegation
see solution one. we can remove event handler from input and put it to its parent that holds other inputs too and by the help delegation technique we can be use event.traget and mapPropsToHandler function again.

Like for a stateless component, just add a function -
function addName(){
console.log("name is added")
}
and it is called in the return as onChange={addName}

If you only have a few functions in you props that you are worried about you can do this:
let _dispatch = () => {};
const myHandler = (e) => _dispatch(something());
const myComponent = (props) => {
if (!_dispatch)
_dispatch = props.dispatch;
return (
<button onClick={myHandler}>Click Me</button>
);
}
If it gets much more complicated, I usually just go back to having a class component.

After continuous effort finally worked for me.
//..src/components/atoms/TestForm/index.tsx
import * as React from 'react';
export interface TestProps {
name?: string;
}
export interface TestFormProps {
model: TestProps;
inputTextType?:string;
errorCommon?: string;
onInputTextChange: React.ChangeEventHandler<HTMLInputElement>;
onInputButtonClick: React.MouseEventHandler<HTMLInputElement>;
onButtonClick: React.MouseEventHandler<HTMLButtonElement>;
}
export const TestForm: React.SFC<TestFormProps> = (props) => {
const {model, inputTextType, onInputTextChange, onInputButtonClick, onButtonClick, errorCommon} = props;
return (
<div>
<form>
<table>
<tr>
<td>
<div className="alert alert-danger">{errorCommon}</div>
</td>
</tr>
<tr>
<td>
<input
name="name"
type={inputTextType}
className="form-control"
value={model.name}
onChange={onInputTextChange}/>
</td>
</tr>
<tr>
<td>
<input
type="button"
className="form-control"
value="Input Button Click"
onClick={onInputButtonClick} />
</td>
</tr>
<tr>
<td>
<button
type="submit"
value='Click'
className="btn btn-primary"
onClick={onButtonClick}>
Button Click
</button>
</td>
</tr>
</table>
</form>
</div>
);
}
TestForm.defaultProps ={
inputTextType: "text"
}
//========================================================//
//..src/components/atoms/index.tsx
export * from './TestForm';
//========================================================//
//../src/components/testpage/index.tsx
import * as React from 'react';
import { TestForm, TestProps } from '#c2/component-library';
export default class extends React.Component<{}, {model: TestProps, errorCommon: string}> {
state = {
model: {
name: ""
},
errorCommon: ""
};
onInputTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const field = event.target.name;
const model = this.state.model;
model[field] = event.target.value;
return this.setState({model: model});
};
onInputButtonClick = (event: React.MouseEvent<HTMLInputElement>) => {
event.preventDefault();
if(this.validation())
{
alert("Hello "+ this.state.model.name + " from InputButtonClick.");
}
};
onButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if(this.validation())
{
alert("Hello "+ this.state.model.name+ " from ButtonClick.");
}
};
validation = () => {
this.setState({
errorCommon: ""
});
var errorCommonMsg = "";
if(!this.state.model.name || !this.state.model.name.length) {
errorCommonMsg+= "Name: *";
}
if(errorCommonMsg.length){
this.setState({ errorCommon: errorCommonMsg });
return false;
}
return true;
};
render() {
return (
<TestForm model={this.state.model}
onInputTextChange={this.onInputTextChange}
onInputButtonClick={this.onInputButtonClick}
onButtonClick={this.onButtonClick}
errorCommon={this.state.errorCommon} />
);
}
}
//========================================================//
//../src/components/home2/index.tsx
import * as React from 'react';
import TestPage from '../TestPage/index';
export const Home2: React.SFC = () => (
<div>
<h1>Home Page Test</h1>
<TestPage />
</div>
);
Note: for text box filed binding "name" attribute & "property name" (e.g: model.name) should be same then only "onInputTextChange" will work.
"onInputTextChange" logic can be modified by your code.

How about something like this:
let __memo = null;
const myHandler = props => {
if (!__memo) __memo = e => props.dispatch(something());
return __memo;
}
const myComponent = props => {
return (
<button onClick={myHandler(props)}>Click Me</button>
);
}
but really this is overkill if you don't need to pass the onClick to lower/inner components, like in the example.

Related

React.js updates some part of DOM nodes when I'm deleting only one element

As I know React creates a new Virtual DOM and compares it with previous one, then it updates Browser DOM with least number of changes possible without rendering the entire DOM again. (in short)
In React documentation I have also read how key should work.
for demonstrating this, I have created todo app, but when I'm deleting one element, all previous elements are re-rendered again (except if I'm not deleting recently added element)
Here is screenshot:
(In Chrome developer tool paint flashing is active for showing renders)
My questions:
Why do previous elements re-render again?
Why key could not fix this problem?
Here is the entire code:
TodoApp.jsx
import React, { useState } from 'react';
import Input from './Input';
import List from './List';
const TodoApp = () => {
const [inputText, setInputText] = useState('');
const [todos, setTodos] = useState([]);
const onSubmitChange = e => {
e.preventDefault();
setTodos([
...todos,
{ text: inputText, completed: false, id: Math.random() * 1000 },
]);
setInputText('');
};
const onChangeEvent = e => {
setInputText(e.target.value);
};
return (
<div>
{todos.map(todo => {
return (
<List todos={todos} setTodos={setTodos} todo={todo} key={todo.id} />
);
})}
<Input
onSubmit={onSubmitChange}
onChange={onChangeEvent}
inputText={inputText}
/>
</div>
);
};
export default TodoApp;
List.jsx
import React from 'react';
import "../todolist/css/TodoApp.css"
const List = ({ todo, todos, setTodos }) => {
const deleteHandle = () => {
setTodos(todos.filter(el => el.id !== todo.id));
};
const completeHandle = () => {
setTodos(
todos.map(el => {
if (el.id === todo.id) {
return { ...el, completed: !el.completed };
}
return el;
})
);
};
return (
<div className={`${todo.completed ? 'completed' : ''}`}>
<div>{todo.text}</div>
<div className="btns">
<button onClick={deleteHandle} className="btn btn-delete">
Delete
</button>
<button onClick={completeHandle} className="btn btn-complete">
complete
</button>
</div>
</div>
);
};
export default List;
Input.jsx
import React from 'react';
const Input = ({ onSubmit, onChange, inputText }) => {
return (
<form onSubmit={onSubmit}>
<input
onChange={onChange}
value={inputText}
type="text"
name="myInput"
autoComplete="off"
/>
</form>
);
};
export default Input;

How to send values React Hook(props)

I made My code.
When Click the button A, appear AAA.
Or Click the button B, appear BBB, Click the button C, appear CCC.
// Main > RightMain.js
import React, { useState } from 'react'
function RightMain() {
const [screen, setScreen] = useState('');
const A = () => {
setScreen('A')
}
const B = () => {
setScreen('B')
}
const C = () => {
setScreen('C')
}
return (
<div>
<button onClick={A}>A</button>
<button onClick={B}>B</button>
<button onClick={C}>C</button>
{screen === 'A' && <div>AAA</div>}
{screen === 'B' && <div>BBB</div>}
{screen === 'C' && <div>CCC</div>}
</div>
)
}
export default RightMain
And I wanna separate My Code(RightMain.js).
When I Click the Button on the RightMain.js.
The Result appear's on the Formations.js like the image below.
But I don kno how to bring value(RightMain.js's screen) to the Formations.js.
// Main > LeftMain.js
import React from 'react'
import RadioBtn from './LeftMain/RadioBtn';
import Formation from './LeftMain/Formation';
function LeftMain() {
return (
<div>
<div>
<RadioBtn />
</div>
<div>
<Formation />
</div>
</div>
)
}
export default LeftMain
//Main > LeftMain > Formation.js
import React, { useState } from 'react'
import RightMain from '../RightMain';
function Formation() {
return (
<div>
</div>
)
}
export default Formation
Thx
If I understand correctly, LeftMain and RightMain are sibilings, and Formation is a child of LeftMain.
One possible approach is to use Context API.
Something like this should work:
// Define the default value
// or return null and take that into consideration when using "useContext"
export const MyCurrentScreenContext = React.createContext({
setScreen: () => void 0,
screen: ''
});
export const MyCurrentScreenProvider = props => {
const [screen, setScreen] = useState('');
const value = useMemo(() => ({ screen, setScreen }), [screen, setScreen]);
return (
<MyCurrentScreenContext.Provider value={value}>
{props.children}
</MyCurrentScreenContext.Provider>
);
}
const Main = () => {
...
return (
<MyCurrentScreenProvider>
<LeftMain />
<RightMain />
...
</MyCurrentScreenProvider>
);
}
const RightMain() {
const { setScreen } = useContext(MyCurrentScreenContext);
....
};
const Formation() {
const { screen } = useContext(MyCurrentScreenContext);
....
};
Read more about context api at the official docs
From what I understand, you want to pass the values down to the child components. If that is correct then you could pass them as parameters when calling it and using props to receive them inside the child component. Something like this.
<div>
<RadioBtn randomVal="value" />
</div>

InvalidValueError: not an instance of HTMLInputElement in React project

I'm trying to add in my React project a google autocomplete feature to my location input but I get this error: InvalidValueError: not an instance of HTMLInputElement.
I guess when the event fires I get the error but I cannot figure out where it comes from.
Here's my code
Search.js
import React, { useState, useContext } from "react";
import DisplaySearchBar from "../layout/DisplaySearchBar";
import RestContext from "../context/restaurant/restContext";
import AlertContext from "../context/alert/alertContext";
const Search = () => {
const restContext = useContext(RestContext);
const alertContext = useContext(AlertContext);
const [where, setWhere] = useState("");
const [what, setWhat] = useState("");
const [sortBy, setSortBy] = useState("best_match");
const [city, setCity] = useState("");
const sortByOptions = {
"Best Match": "best_match",
"Highest Rated": "rating",
"Most Reviewed": "review_count",
};
// give active class to option selected
const getSortByClass = (sortByOption) => {
if (sortBy === sortByOption) {
return "active";
} else {
return "";
}
};
// set the state of a sorting option
const handleSortByChange = (sortByOption) => {
setSortBy(sortByOption);
};
//handle input changes
const handleChange = (e) => {
if (e.target.name === "what") {
setWhat(e.target.value);
} else if (e.target.name === "where") {
setWhere(e.target.value);
}
};
const onSubmit = (e) => {
e.preventDefault();
if (where && what) {
restContext.getRestaurants({ where, what, sortBy });
setWhere("");
setWhat("");
setSortBy("best_match");
} else {
alertContext.setAlert("Please insert somethiing", "error");
}
};
// displays sort options
const renderSortByOptions = () => {
return Object.keys(sortByOptions).map((sortByOption) => {
let sortByOptionValue = sortByOptions[sortByOption];
return (
<li
className={getSortByClass(sortByOptionValue)}
key={sortByOptionValue}
onClick={() => handleSortByChange(sortByOptionValue)}
>
{sortByOption}
</li>
);
});
};
// google suggestion
const handleScriptLoad = () => {
const handlePlaceSelect = () => {
// Extract City From Address Object
const addressObject = autocomplete.getPlace();
const address = addressObject.address_components;
// Check if address is valid
if (address) {
// Set State
setCity(address[0].long_name);
}
};
const options = {
types: ["(cities)"],
}; // To disable any eslint 'google not defined' errors
// Initialize Google Autocomplete
/*global google*/ let autocomplete = new google.maps.places.Autocomplete(
document.getElementById("autocomplete"),
options
);
// address.
autocomplete.setFields(["address_components", "formatted_address"]);
// Fire Event when a suggested name is selected
autocomplete.addListener("place_changed", handlePlaceSelect);
};
return (
<DisplaySearchBar
onSubmit={onSubmit}
handleChange={handleChange}
renderSortByOptions={renderSortByOptions}
where={where}
what={what}
handleScriptLoad={handleScriptLoad}
/>
);
};
export default Search;
DisplaySearch.js
import React, { useContext } from "react";
import PropTypes from "prop-types";
import RestContext from "../context/restaurant/restContext";
//Import React Script Libraray to load Google object
import Script from "react-load-script";
const DisplaySearchBar = ({
renderSortByOptions,
onSubmit,
where,
handleChange,
what,
handleScriptLoad,
}) => {
const restContext = useContext(RestContext);
const { restaurants, clearSearch } = restContext;
const googleUrl = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_API_KEY}&libraries=places`;
return (
<div className="searchBar">
<h1>Where are you going to eat tonigth?</h1>
<div className="searchBar-sort-options">
<ul>{renderSortByOptions()}</ul>
</div>
<form onSubmit={onSubmit} className="searchBar-form">
<div className="searchBar-input">
<Script url={googleUrl} onLoad={handleScriptLoad} />
<input
type="text"
name="where"
placeholder="Where do you want to eat?"
value={where}
onChange={handleChange}
/>
<input
type="text"
name="what"
placeholder="What do you want to eat?"
onChange={handleChange}
value={what}
/>
</div>
<div className="searchBar-submit">
<input
className="myButton button"
type="submit"
name="submit"
value="Search"
></input>
{restaurants.length > 0 && (
<button className="clearButton button" onClick={clearSearch}>
Clear
</button>
)}
</div>
</form>
</div>
);
};
DisplaySearchBar.propTypes = {
renderSortByOptions: PropTypes.func.isRequired,
where: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired,
what: PropTypes.string.isRequired,
handleScriptLoad: PropTypes.func.isRequired,
};
export default DisplaySearchBar;
Thanks for your help
Your code is quiet complex. You might be able to save some lines and make your component leaner with one of the npm packages out there. I would suggest you to give https://github.com/hibiken/react-places-autocomplete a shot.
For performance improvements also consider using React's useMemo and useCallback in functional Components https://reactjs.org/docs/hooks-reference.html#usememo
I found the issue.
I didn't give the id "autocomplete" to my input therefore when the event was firing it couldn't reach it.

Generating Search suggestions in React?

I am looking to generate search suggestions that match data collected, like so:
As you type in you get suggestions:
I am referencing some of the tutorial work from WesBos:
https://github.com/wesbos/JavaScript30/blob/master/06%20-%20Type%20Ahead/index-FINISHED.html
I've got the data logging in the console but now I am unsure how to get it to render. Below are my components (My thoughts were to generate the divs as a loop in App.js and pass the props to Match.js which I would eventually import but I am not sure if I am approaching this wrong):
App.js
import React, { Component } from 'react';
import { Form, Button } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
const my_data = require('./data/test.json')
class App extends Component {
constructor(props) {
super(props);
this.state = {
links: [],
selectedLink:null,
userLocation: {},
searchInput: "",
showMatches: false,
matches: []
};
}
componentDidMount() {
fetch('https://data.cityofnewyork.us/resource/s4kf-3yrf.json')
.then(res=> res.json())
.then(res=>
//console.log(json)
this.setState({links:res})
);
}
render() {
const handleInputChange = (event) => {
event.preventDefault()
this.setState({searchInput: event.target.value })
//console.log(event.target.value)
}
const handleSubmit = (event) => {
event.preventDefault()
const data = this.state
displayMatches();
}
const findMatches = (wordToMatch, my_obj) => {
return my_obj.filter(place => {
// here we need to figure out the matches
const regex = new RegExp(wordToMatch, 'gi');
//console.log(place.street_address.match(regex))
return place.street_address.match(regex)
});
}
const displayMatches =() => {
const matchArray = findMatches(this.state.searchInput, this.state.links);
matchArray.map(place => {
console.log(place.street_address);
this.setState({matches:place})
this.setState({showMatches:true})
});
}
return (
<div>
<Form style = {{width: "75%"}} onSubmit = {handleSubmit}>
<Form.Group controlId="formSearch">
<Form.Control
type="text"
name = "my_search"
placeholder="Search for a Link Near you..."
onChange = {handleInputChange} />
</Form.Group>
<Button variant="primary" type="submit">
Search
</Button>
</Form>
<div>
{`How can I generate the console logged values as dynammic suggestions?`}
</div>
</div>
);
}
}
export default App;
Match.js
import React from 'react';
const match = ( props ) => {
return (
<div className="Matches">
<p>{`data is passed: ${props.address}`}</p>
</div>
)
};
export default match;
Appreciate the help.
Answers - Using Suggestions below
App.js
import React, { Component } from 'react';
import { Form, Button, ListGroup } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import Match from './Match'
const my_data = require('./data/test.json')
class App extends Component {
state = {
links: [],
selectedLink:null,
userLocation: {},
searchInput: "",
showMatches: false,
matches: [],
searchLink:[]
}
componentDidMount() {
fetch('https://data.cityofnewyork.us/resource/s4kf-3yrf.json')
.then(res=> res.json())
.then(res=>
//console.log(json)
this.setState({links:res})
);
}
handleInputChange = (event) => {
event.preventDefault()
this.setState({searchInput: event.target.value })
console.log(event.target.value)
}
handleSubmit = (event) => {
event.preventDefault()
this.displayMatches();
}
findMatches = (wordToMatch, my_obj) => {
return my_obj.filter(place => {
// here we need to figure out the matches
const regex = new RegExp(wordToMatch, 'gi');
//console.log(place.street_address.match(regex))
return place.street_address.match(regex)
});
}
displayMatches =() => {
const matchArray = this.findMatches(this.state.searchInput, this.state.links);
const newStateMatches = matchArray.map(place => {
console.log(place.street_address);
return place
});
this.setState({matches:newStateMatches})
this.setState({showMatches:true})
}
alertClicked =(event) => {
//alert('you clicked an item in the group')
const data = event.target
console.log('clicked this data:', data)
this.setState({searchLink: event.target})
console.log(this.state.searchLink)
}
render() {
return (
<div>
<input
placeholder="Search for a Link Near you..."
onChange = {this.handleInputChange}
value = {this.state.searchInput}
/>
<Button onClick={this.handleSubmit}>
Search
</Button>
<ListGroup defaultActiveKey="#link1">
{
this.state.matches.map(match => {
return <Match
address={match.street_address}
alertClicked={this.alertClicked}/>
})
}
</ListGroup>
</div>
);
}
}
export default App;
Match.js
import React from 'react';
import { ListGroup } from 'react-bootstrap';
const match = ( props ) => {
return (
<ListGroup.Item
className="Matches"
action onClick={props.alertClicked}>
<p>{`${props.address}`}</p>
</ListGroup.Item>
)
};
export default match;
I think your initial instinct as to how to do this is correct :
get the matches
store them in state
map over the state and render one component per match, passing the relevant data as props
To answer your question exactly, mapping over state to render component usually looks something like this :
<div>
{
matches.map(match => {
return <Match address={match.address} name={match.name} />
})
}
</div>
You can also destructure properties like this :
<div>
{
matches.map(({address, name}) => {
return <Match address={address} name={name} />
})
}
</div>
Also, another minor observation: you notice I called the component Match with a capital M. It is a convention in React and other component based libraries that components' names are always capitalized, not only in the file name but also in the code.
First move all your method definitions outside of your render function (you'll need to update const and add this.
in your display matches you should be building a newstate array then setState with the new array once built
i do not use react bootstrap but it did not appear that your submit button was within the form therefor was not submitting the form.
Make sure react components are capitalized (match component should be Match)
I passed the whole 'place' down to the Match component via place prop:
<Match place={place} />
if you want to access the address like you did you would need to pass each individual value from the place down to the Match component like:
<Match address={place.address} />
(also if you are only initializing state before first render you can do so outside of the constructor)
I simplified the return statement to just use a plain input and button tag for simplicity but you can probably get going from here
Working Snippet:
const Match = ( props ) => {
return (
<div className="Matches">
<p>{`data is passed: ${props.place.street_address}`}</p>
</div>
)
};
class SomeComponent extends React.Component{
state = {
links: [],
selectedLink:null,
userLocation: {},
searchInput: "",
showMatches: false,
matches: []
}
componentDidMount() {
fetch('https://data.cityofnewyork.us/resource/s4kf-3yrf.json')
.then(res=> res.json())
.then(res=>
//console.log(json)
this.setState({links:res})
);
}
handleInputChange = (event) => {
event.preventDefault()
this.setState({searchInput: event.target.value })
//console.log(event.target.value)
}
handleSubmit = (event) => {
event.preventDefault()
this.displayMatches();
}
findMatches = (wordToMatch, my_obj) => {
return my_obj.filter(place => {
// here we need to figure out the matches
const regex = new RegExp(wordToMatch, 'gi');
//console.log(place.street_address.match(regex))
return place.street_address.match(regex)
});
}
displayMatches =() => {
const matchArray = this.findMatches(this.state.searchInput, this.state.links);
const newStateMatches = matchArray.map(place => {
console.log(place.street_address);
return place
});
this.setState({matches:newStateMatches})
this.setState({showMatches:true})
}
render() {
return (
<div>
<input
placeholder="Search for a Link Near you..."
onChange = {this.handleInputChange}
value = {this.state.searchInput}
/>
<button onClick={this.handleSubmit}>
Search
</button>
{this.state.matches.map((place)=>{
return <Match place={place} />
})}
</div>
);
}
}
ReactDOM.render(
<SomeComponent />,
document.getElementById("react")
);
<div id='react'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

React: Adding event.preventDefault() Method Into Functional Component

Currently I'm trying to avoid refreshing page by adding a preventDefault() call into the onClick handler of a functional component (BookList defined in bookList.js). I know I can make it with from class component to functional. However, is there any way to call preventDefault() in the onClick event handler in BookList?
Here is my sample code:
BookListElement.js
import React from 'react';
import { connect } from 'react-redux';
import BookList from '../components/bookList';
import { deleteBook } from '../store/actions/projectActions';
const BookListElement = ({books, deleteBook}) => {
if(!books.length) {
return (
<div>
No Books
</div>
)
}
return (
<div>
{Array.isArray(books) ? books.map(book => {
return (
<BookList book={book} deleteBook={deleteBook} key={book._id} />
);
}): <h1>something wrong.</h1>}
</div>
);
};
const mapStateToProps = state => {
return {
books: state.books
};
};
const mapDispatchToProps = dispatch => {
return {
deleteBook: _id => {
dispatch(deleteBook(_id));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(BookListElement);
bookList.js
import React, { Component } from 'react';
const styles = {
borderBottom: '2px solid #eee',
background: '#fafafa',
margin: '.75rem auto',
padding: '.6rem 1rem',
maxWidth: '500px',
borderRadius: '7px'
};
const BookList = ({ book: { author, publication, publisher, _id }, deleteBook }) => {
return (
<form>
<div className="collection-item" style={styles} key={_id}>
<h2>{author}</h2>
<p>{publication}</p>
<p>{publisher}</p>
<button className="btn waves-effect waves-light" onClick={() => {deleteBook(_id)}}>
<i className="large material-icons">delete_forever</i>
</button>
</div>
</form>
);
};
export default BookList;
action.js
export const deleteBookSuccess = _id => {
return {
type: DELETE_BOOK,
payload: {
_id
}
}
};
export const deleteBook = _id => {
return (dispatch) => {
return axios.delete(`${apiUrl}/${_id}`)
.then(response => {
dispatch(deleteBookSuccess(response.data))
})
.catch(error => {
throw(error);
});
};
};
reducer.js
case DELETE_BOOK:
let afterDelete = state.filter(book => {
return book._id !== action.payload._id
});
return afterDelete;
If you don't want a button to trigger form submission, add type="button" attribute to the button element.
By default a button submits the form (has type set to submit).
Setting type="button" signifies that it has no default behavior.
<form>
<button type="button">type button doesn't trigger refresh</button>
<button>no type triggers refresh</button>
</form>
The preventDefault method needs to be called on an event. However in the way you are setting up your onClick handler, the event is not passed to your handler.
Here is how you can fix this issue (in bookList.js):
import React from 'react';
const BookList = ({ book: { author, publication, publisher, _id }, deleteBook }) => {
const handleClick = event => {
event.preventDefault();
deleteBook(_id);
}
return (
<form>
<div>
<h2>{author}</h2>
<p>{publication}</p>
<p>{publisher}</p>
<button onClick={ handleClick }>
<i>delete_forever</i>
</button>
</div>
</form>
);
};
So onClick will pass the event (by default) then just call preventDefault on that event, and then call deleteBook.
I made a simple illustration (component) where one click event triggers the reload, and the other doesn't. If this doesn't work for you, the problem is another place, meaning in a different component(pieces of code you haven't provided). I hope it helps, if not maybe it will help someone in the future :)
class Example extends React.Component {
constructor(props) {
super(props);
this.preventDefault = this.preventDefault.bind(this);
this.dontPrevent = this.dontPrevent.bind(this);
}
// handler recieves the `e` event object
dontPrevent() {
alert('This page will reload :)');
}
preventDefault(e) {
alert('Page will NOT reload');
e.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.preventDefault}>
<div>
<h3>NO Reload Example</h3>
<p>
<button>Submit</button>
</p>
</div>
</form>
<hr/>
<form>
<div>
<h3>Reload Example</h3>
<p>
<button onClick={this.dontPrevent}>Submit</button>
</p>
</div>
</form>
</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Categories

Resources