I'm trying to access a parent method from a child to show a modal on screen and I'm getting the error: This.props.toggleModal is not a function. I'm passing the method down to the child so it can be called and using the correct state (I think). The button does call it's own method which in turn calls the parent. The modal component sits inside App.js.
App.js
class App extends Component {
constructor() {
super()
this.state = {
isOpen: false
}
}
toggleModal = () => {
this.setState({
isOpen: !this.state.isOpen
});
console.log('Open');
}
render() {
return (
<div className="App">
<Modal toggleModal={this.toggleModal} show={this.state.isOpen}
onClose={this.toggleModal}>
Here's some content for the modal
</Modal>
<div className="container">
<Header/>
<main>
<Route path="/users"
children={({ match, ...rest }) => (
<TransitionGroup component={firstChild}>
{match && <UserList {...rest} />}
</TransitionGroup>
)}/>
...
</main>
<Footer />
</div>
</div>
);
}
}
SearchBar.js - (located inside the user page)
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = {
type: this.props.type,
value: ''
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.type !== this.props.type) {
this.setState({ type: nextProps.type });
}
};
handleClick = (e) => {
e.preventDefault();
console.log("Clicked!!!");
this.props.toggleModal();
};
handleChange = e => {
console.log(this.state.type);
this.setState({ value: e.target.value });
};
render () {
const isUser = this.state.type;
let rightContent = null;
if (isUser === "tour" || isUser === "venue") {
rightContent =
<div className="column">
<div className="float-right"><button className="add-new" onClick={this.handleClick}>Add New</button></div>
</div>
} else {
rightContent =
<div className="column">
<div className="float-right">
<div className="results-block">
<b>0</b>/<small>292</small>
</div>
</div>
</div>
}
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}
value={this.state.value} />
</label>
</fieldset>
</form>
</div>
{rightContent}
</div>
)
}
}
export default SearchBar;
Check IF you getting toggleModal as props in your User Page Compoenent. If yes then pass it explicitly like to SearchBar
<SearchBar toggleModal = {this.props.toggleModal } /> // plus your rest of the props
You have to bind toggleModal with this in constructor so that use it as this.toggleModal.
EX.
this.toggleModal = this.toggleModal.bind(this);
check this ReactJs doc for more info.
https://reactjs.org/docs/handling-events.html
Related
I am writing todo app. There are main files in my directory now:
App (rendering main page with header and buttons)
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { triggerText: 'Create a task' };
}
propTypes = {
triggerText: PropTypes.string.isRequired,
handleSubmit: PropTypes.object.isRequired,
};
render() {
const { triggerText } = this.state;
const { handleSubmit } = this.props;
return (
<div className="App">
<header className="App-header">
<h1>To Do List</h1>
<div id="tasksList">
<span className="tasks active">Tasks</span>
</div>
<div id="categoriesList">
<span className="categories">Categories</span>
</div>
<div>
<Container triggerText={triggerText} onSubmit={handleSubmit} /> // creates modal dialog and uses TodoForm
</div>
</header>
<div id="container" className="container">
<TodoBox tasks={[]}/>
</div>
</div>
);
}
}
TodoForm (create a form)
export default class TodoForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: '', tasks: [] };
}
propTypes = {
handleSubmit: PropTypes.object.isRequired,
}
handleRemove = (currentTaskId) => (e) => {
e.preventDefault();
const { tasks } = this.state;
this.setState({ tasks: tasks.filter(({ id }) => id !== currentTaskId) });
};
handleChange = (e) => {
e.preventDefault();
this.setState({ value: e.target.value });
}
handleSubmit = (e) => {
e.preventDefault();
const { value, tasks } = this.state;
const newTask = { id: uniqueId(), text: value };
this.setState({ value: '', tasks: [newTask, ...tasks] });
}
render() {
const { value } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="text"><strong>Create a task</strong></label>
<input
type="text"
onChange={this.handleChange}
value={value}
required
className="form-control"
id="text"
placeholder="I am going..."
/>
</div>
<div className="form-group">
<button type="submit" className="form-control btn btn-primary">Add</button>
</div>
</form>
);
}
}
TodoBox (generating list of tasks)
class Item extends React.Component {
propTypes = {
onRemove: PropTypes.object.isRequired,
task: PropTypes.string.isRequired,
};
render() {
const { task, onRemove } = this.props;
return (
<div className="row">
<div>
<button type="button" className="btn btn-primary" onClick={onRemove}>-</button>
</div>
<div className="col-10">{task.text}</div>
</div>
);
}
}
export default class TodoBox extends React.Component {
constructor(props) {
super(props);
}
propTypes = {
tasks: PropTypes.string.isRequired,
}
render() {
const { tasks } = this.props;
return (
<div className="item">
{tasks.map((task) => (
<div key={task.id}>
<Item task={task} onRemove={this.handleRemove} />
<hr />
</div>
))}
</div>
);
}
}
And the question is: how I can pass the state from TodoForm to TodoBox in App (it is initialize as an empty array now). I want to output tasks at the bottom of the same page in container after header element.
You can create a function (addTodo) in App component and pass it down to the TodoForm component. In TodoForm component you can invoke the addTodo function from props and send the todoValue as arguments props.addTodo(todoValue). In addTodo function in App component you can update the todoValue to state. Once you update the state it will re-render the App component, then the TodoBox component will call with the updated todoValue value.
Note: But it is not best practice. The best practice is to use React Context
So I have stated learning react and tried to make a project that renders data from an api. I have 2 components, a Search bar and a component that renders the weather.
What I'm trying to do is to get the value from the search bar and concatenate into the api string. I have tried doing this by settings a prop but I am unable accessing it in the weather component.
My questions is: How can I access the search value in a different component
/components/Search.js
class Search extends Component {
state = {
title: '',
};
onChange = (e) => {
this.setState({ title: e.target.value });
};
onSubmit = (e) => {
// e.preventDefault();
this.props.searchValue(this.state.title);
this.setState({ title: '' });
};
render() {
return (
<Mui.Container>
<CssBaseline />
<form
onSubmit={this.onSubmit}
autoComplete='off'
>
<Mui.Input
placeholder='enter place'
value={this.state.title}
onChange={this.onChange}
/>
</form>
</Mui.Container>
);
}
}
Search.propTypes = {
searchValue: PropTypes.func,
};
/components/Weather.js
class Weather extends Component {
state = {
videos: [],
};
componentDidMount = () => {
axios
.get(
'<weather api here>'
)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
I assume there will be a parent component for <Weather /> and <Search />?
If yes then the parent component can have state, and you pass your setState function into the search component, and then you pass the current state into the weather component.
<Weather searchValue="current state from parent" />
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
videos: []
};
}
componentDidMount = () => {
axios
.get(`URL?${this.props.searchValue}`)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
I want to do a real time searching in React. How can I lift the message up from child to parent? Or how can I pass a parent handler to children through props to handle the event?
parent class:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: activities,
filteredActivities: activities,
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
filterActivity = searchText => {
return this.state.activities
.filter(activity => {
if(activity.content.toLowerCase().includes(
searchText.toLowerCase())
){
return true;
}
return false;
});
}
handleSearchChange = event => {
this.setState({
filteredActivities: this.filterActivity(event.target.value)
});
};
render() {
const filteredActivities = this.props.filteredActivities;
return(
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} />
<SearchBar onChange={this.handleSearchChange} />
<Content activities={this.state.filteredActivities} />
</div>
</div>
);
}
}
Child class:
class SearchBar extends React.Component {
onChangeHandler = event => {
console.log(event.target.value);
}
render() {
return (
<div className="search-bar" >
<input type="text" onChange={this.onChangeHandler} />
</div>
);
}
}
I want to pass the event.target.value to handleSearchChange. if I put the code of class Searchbar to class App, I can perform a real time searching very good. But I can't put them into two components. I want to make them into two components. Thanks a lot.
Should be as simple as this:-
Child class:
class SearchBar extends React.Component {
onChangeHandler = event => {
this.props.inputChanged(event.target.value);
}
render() {
return (
<div className="search-bar" >
<input type="text" onChange={this.onChangeHandler} />
</div>
);
}
}
Parent class:
handleSearchChange = inputValue => {
this.setState({
filteredActivities: this.filterActivity(inputValue)
});
};
JSX of parent class:
<SearchBar inputChanged={this.handleSearchChange} />
since you're already passing the function's reference as a prop you can access it using this.props.propName and call it.
class SearchBar extends React.Component {
/* onChangeHandler = event => {
console.log(event.target.value);
} */
render() {
const { onChange } = this.props;
return (
<div className="search-bar" >
<input type="text" onChange={onChange} />
</div>
);
}
}
You can try this, as you already took event as a parameter in parent class for handleSearchChange method
I was writing a component with the code given as follows, which after rendering looks like:
I have used antd components to render the fields. The problem that I am facing is that I am neither able to select from the select box nor write in the input field as shown below. I have a feeling that I am using React's key inappropriately for mocFields in the render method which is obtained from getMOCField.
import React, { Component } from "react";
import { Button, Icon, Select, Form, Input } from "antd";
const FormItem = Form.Item;
const Option = Select.Option;
import { FormComponentProps } from "antd/lib/form/Form";
type state = {
mocFields: JSX.Element[]
};
export class MOC extends Component<FormComponentProps, state> {
constructor(props) {
super(props);
this.state = {
mocFields: []
};
this.addMOCField = this.addMOCField.bind(this);
this.removeMOCField = this.removeMOCField.bind(this);
}
componentDidMount() {}
componentWillReceiveProps(nextProps) {}
removeMOCField(key, event: React.MouseEvent<HTMLElement>) {
event.preventDefault();
const { mocFields } = this.state;
mocFields.splice(key, 1);
this.setState({
mocFields
});
}
getMOCFieldFooter() {
return (
<div className="d-flex justify-content-between small">
<div className="inline-block">
<Button
type="primary"
shape="circle"
icon="plus"
ghost
size="small"
className="d-font mr-1"
onClick={this.addMOCField}
/>
<div
className="text-primary pointer d-font inline-block letter-spacing-1"
onClick={this.addMOCField}
>
Add another
</div>
<div className="d-font inline-block letter-spacing-1">or </div>
<div className="text-primary pointer d-font inline-block letter-spacing-1">
Create a new MOC
</div>
</div>
</div>
);
}
getMOCField(key) {
const { getFieldDecorator } = this.props.form;
return (
<div className="d-flex justify-content-between">
<div className="inline-block">
<FormItem label="Select MOC">
{getFieldDecorator(`selected_moc[${key}]`, {
rules: [
{
required: true,
message: "Please select moc"
}
]
})(
<Select>
<Option value={"A"}>A</Option>
<Option value={"B"}>B</Option>
</Select>
)}
</FormItem>
</div>
<div className="inline-block">
<FormItem label="Recovery (%)">
{getFieldDecorator(`recovery_percentage[${key}]`, {
rules: [
{
required: true,
message: "Please input the recovery percentage"
}
]
})(<Input type="number" step="0.000001" />)}
</FormItem>
</div>
<div className="inline-block pointer">
<span>
<Icon type="close" onClick={this.removeMOCField.bind(this, key)} />
</span>
</div>
</div>
);
}
addMOCField(event: React.MouseEvent<HTMLElement>) {
event.preventDefault();
const { mocFields } = this.state;
const MOCField = this.getMOCField(mocFields.length);
mocFields.push(MOCField);
this.setState({
mocFields
});
}
getAddMOCButton() {
return (
<div className="d-flex w-100 mt-3">
<Button
type="primary"
ghost
className="w-100"
onClick={this.addMOCField}
>
<Icon type="plus-circle" />
Add MOC
</Button>
</div>
);
}
render() {
const { mocFields } = this.state;
const mocButton = this.getAddMOCButton();
const toRender =
mocFields.length > 0 ? (
<div className="w-100 p-2 gray-background br-25">
{mocFields.map((f, index) => (
<div key={index}>{f}</div>
))}
{this.getMOCFieldFooter()}
</div>
) : (
mocButton
);
return toRender;
}
}
What could be the reason for this? What am I doing incorrectly? Currently the above component renders as follows:
If the number of fields in mocFields is zero, then a button to add new fields is rendered.
After the button is pressed, mocField is populated with the select box and input field as shown above. The key of the div is decided during the render method.
It seems that the listeners doesn't work once they are stored in the array. I've tried to inline the call to getMOCField in the render function and it works. Here is what I've changed to get it work:
class MOC extends Component {
// ...
addMOCField(event) {
event.preventDefault();
const { mocFields } = this.state;
// We only keep inside the state an array of number
// each one of them represent a section of fields.
const lastFieldId = mocFields[mocFields.length - 1] || 0;
const nextFieldId = lastFieldId + 1;
this.setState({
mocFields: mocFields.concat(nextFieldId),
});
}
removeMOCField(key, event) {
event.preventDefault();
this.setState(prevState => ({
mocFields: prevState.mocFields.filter(field => field !== key)
}));
}
render() {
const { mocFields } = this.state;
const mocButton = this.getAddMOCButton();
const toRender =
mocFields.length > 0 ? (
<div className="w-100 p-2 gray-background br-25">
{/* {mocFields.map((f, index) => (
<div key={index}>{f}</div>
))} */}
{mocFields.map(fieldIndex => (
<div key={fieldIndex}>{this.getMOCField(fieldIndex)}</div>
))}
{this.getMOCFieldFooter()}
</div>
) : (
mocButton
);
return toRender;
}
}
I am trying to create dynamic form, so I pass some jsx elements to a child component as a property. Even though the state is being updated, the updated state is not passed to the element. Below is the code:
This is the child component which maps over the passed controls and outputs them.
class Form extends Component {
render() {
return (
<div>
{this.props.controls.map((c, i) => {
return <React.Fragment key={i}>{c}</React.Fragment>;
})}
</div>
);
}
}
This is the App that calls the child component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
this.controls = [
<input
type="text"
onChange={this.onChangeUsername}
value={this.state.username}
/>
];
}
componentDidUpdate() {
console.log(this.state);
}
render() {
return (
<div className="App">
<div className="app__group">
<h1>This is not working</h1>
<Form controls={this.controls} />
</div>
<div className="app__group">
<h1>This is working</h1>
<input
type="text"
onChange={this.onChangePassword}
value={this.state.password}
/>
</div>
</div>
);
}
onChangeUsername = e => {
console.log('onChangeUsername', e.target.value);
this.setState({ username: e.target.value });
};
onChangePassword = e => {
console.log('onChangePassword');
this.setState({ password: e.target.value });
};
}
As an example of the unexpected behaviour, when an input passed as a property to the child component, I cannot type in the input. The state gets updated but it's is not passed to the child, thus the text does not show.
On the other hand, a standard input element works, I can type and see the output.
What am I doing wrong?
the problem is that you are trying to make something "fixed" as something dynamic. lets go with a more functional approach and it will refresh each one of the inputs like if they are dynamic.
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
}
componentDidUpdate() {
console.log(this.state);
}
render() {
return (
<div className="App">
<div className="app__group">
<h1>This is not working</h1>
<Form controls={this.controls()} />
</div>
</div>
);
}
controls = () => {
return [<input
type="text"
onChange={this.onChangeUsername}
value={this.state.username}
/>]
}
onChangeUsername = e => {
console.log('onChangeUsername', e.target.value);
this.setState({ username: e.target.value });
};
onChangePassword = e => {
console.log('onChangePassword');
this.setState({ password: e.target.value });
};
}