I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}
Related
I have a React component with an input field.
I want to update the value of the input field when a button is clicked, I can confirm that the value changes when I inspect element but it doesn't display in the input field. Below is a sample code to just to give an idea.
class InputField {
constructor(props) {
super(props)
}
state = {
userInput: ''
}
}
onClick = () => {
this.setState({
userInput: 'Test'
})
}
render() {
return ( <input value={this.state.userInput} name="sampleInput" />
<button onClick={this.onClick}> Click me </button>
)
}
Fix syntax
your code is ok, just little order.
I add the whole component
import React, { Component } from 'react';
class InputField extends Component {
constructor(props) {
super(props)
}
state = {
userInput: ''
}
onClick = () => {
this.setState({
userInput: 'Test'
})
}
render() {
return (
<div>
<input value={this.state.userInput} name="sampleInput" />
<button onClick={this.onClick}>Click me</button>
</div>
)
}
}
export default InputField;
I just removed syntax error in your example and it worked for me.
import React from 'react';
export default class InputField extends React.Component {
constructor(props) {
super(props)
this.state = {
userInput: ''
}
}
onClick = () => {
this.setState({
userInput: 'Test'
})
}
render() {
return (
<div>
<input value={this.state.userInput} name="sampleInput"/>
<button
onClick = {this.onClick}
>
Click me
</button>
</div>
)
}
}
One approach would be to implement this as a functional component via hooks. You could for instance use the state hook to store and render the userInput data as shown below:
import React from "react";
/* Declare functional InputField component */
function InputField () {
/* Define local state hook to store the "user input" data */
const [userInput, setUserInput] = React.useState("");
const onClick = (e) => {
/* Prevent button click's default behavior */
e.preventDefault();
/* Call the state's "setter" method to update "userInput" state */
setUserInput('Test')
}
/* Render both input and button in a <> fragment */
return (<>
<input value={this.state.userInput} name="sampleInput"/>
<button onClick={onClick}>Click me</button>
</>)
}
To use this component, simply render it as:
<InputField />
I just fix your syntax errors and it run no any error
class InputField extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
};
}
onClick = () => {
this.setState({
userInput: 'Test',
});
};
render() {
return (
<div>
<input value={this.state.userInput} name="sampleInput" />
<button onClick={this.onClick}>Click me</button>
</div>
);
}
}
I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)
There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<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>
There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;
I do sorting on reactjs, I can’t understand how to redraw all child components so that only one selected remains active, I can update the current one, but the others do not change. Here is the code for an example. Can anyone help / explain how to do it right?
nodejs, webpack, last reactjs
App.js
import React, { Component } from "react";
import Parent from "./Parent";
class App extends Component {
render() {
return(
<Parent />
)
}
}
export default App;
Parent.js
import React, { Component } from "react";
import Child from "./Child";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
popularity: {"sorting": "desc", "active": true},
rating: {"sorting": "desc", "active": false},
reviews_count: {"sorting": "desc", "active": false},
};
}
updateFilters = () => {
// ??
};
render() {
return (
<div>
<Child type="popularity" sorting={this.state.popularity.sorting} active={this.state.popularity.active} updateFilters={this.updateFilters} />
<Child type="rating" sorting={this.state.rating.sorting} active={this.state.rating.active} updateFilters={this.updateFilters} />
<Child type="reviews_count" sorting={this.state.reviews_count.sorting} active={this.state.reviews_count.active} updateFilters={this.updateFilters} />
</div>
)
}
}
export default Parent;
Child.js
import React, { Component } from "react";
class Child extends Component {
handleClick = () => {
this.props.updateFilters();
};
render() {
let activeStr = "";
if (this.props.active) {
activeStr = "active"
} else {
activeStr = "inactive";
}
return(
<div onClick={() => this.handleClick}>
{this.props.type} {activeStr} {this.props.sorting}
</div>
);
}
}
export default Child;
Assuming you are trying to set the active flag for a clicked Type to true and also set all the other types to false.
<div onClick={() => this.handleClick}> this isn't correct, as you aren't invoking the function. This could be corrected to:
<div onClick={() => this.handleClick()}>
Then you can update handleClick to pass the Type:
handleClick = () => {
this.props.updateFilters(this.props.type);
};
OR
You could ignore that handleClick and call the prop function:
<div onClick={() => this.props.updateFilters(this.props.type)}>
Once you have passed the Type back into the updateFilters, we can simply iterate over the previous State Properties, setting all Types' Active Flag to false. However, if the Key matches the Type which was clicked, we set it to true.
updateFilters = type => {
this.setState(prevState => {
return Object.keys(prevState).reduce(
(result, key) => ({
...result,
[key]: { ...prevState[key], active: key === type }
}),
{}
);
});
};
Your Child component could be heavily refactored into a Pure Functional Component, making it a lot simpler:
const Child = ({ type, active, updateFilters, sorting }) => (
<div onClick={() => updateFilters(type)}>
{type} {active ? "active" : "inactive"} {sorting}
</div>
);
Work solution:
https://codesandbox.io/s/4j83nry569
I have a model called questions which has a properties senderID, id, expID, description. What i am trying to do is once the user clicks on any of the questions in the list.The senderID of the question which was clicked gets passed to another component called AnswerTemplate. How can i do that
This is the render method
` render() {
const { id: accountID } = this.props.account;
const question = this.state.questions.filter(({ expID }) => expID === accountID);
return (
<div>
<h1>Answer the questions here!</h1>
<ul>
{ question.map(({ id, description, senderID }) => (
<li key={ id }>
<a href ={ '/temp' }>{description}</a>
</li>
))}
</ul>
</div>
);
}
}`
and this is the full code:
import React, { Component } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import AccountActions from '../../../../../Redux/AccountRedux';
class AnswerQuestions extends Component {
constructor() {
super();
this.state = {
questions: []
};
}
componentWillMount() {
this.getQuestions();
}
getQuestions() {
axios.get('http://localhost:3001/api/questions')
.then(response => {
this.setState({ questions: response.data }, () => {
console.log(this.state);
});
});
}
render() {
const { id: accountID } = this.props.account;
const question = this.state.questions.filter(({ expID }) => expID === accountID);
return (
<div>
<h1>Answer the questions here!</h1>
<ul>
{ question.map(({ id, description, senderID }) => (
<li key={ id }>
<a href ={ '/temp' }>{description}</a>
</li>
))}
</ul>
</div>
);
}
}
const mapStateToProps = store => {
return {
account: store.account.data,
fetching: store.account.fetching
};
};
const mapDispatchToProps = dispatch => {
return {
get: () => dispatch(AccountActions.accountGet())
};
};
AnswerQuestions.propTypes = {
get: PropTypes.func,
fetching: PropTypes.bool,
account: PropTypes.object,
question: PropTypes.object
};
export default connect(mapStateToProps, mapDispatchToProps)(AnswerQuestions);
How familiar are you with Redux ?
Since you are using React and Redux, you could call an action setQuestionID(_id) for example,
and have a Reducer that stores this ID in the global state.
Then you connect your AnswerTemplate.js Component, getting the question id mapped to the props, something like this:
const mapStateToProps = store => {
return {
question_id: store.question.id,
};
};
Basically you store the question id in the global state.
Would that solve your issue ?
You may want to handle your links with router like React Router.
If you are calling the component from inside AnswerQuestions you can do something like:
<AnswerTemplate somethingToPass={passThis}/>
And inside your AnswerTemplate component you can use this.props.somethingToPass.
Hope that helps.
--- edit after comments ---
If you have a parent component for both classes e.g. AnswerParent you need to use AnswerParent's state to handle the data. For example:
class AnswerParent extends Component {
constructor(props) {
super(props);
this.state = {
senderID = '';
}
}
render() {
return (
<AnswerQuestions onClick={(senderID) => this.setState(senderID)} />
<AnswerTemplate senderID={this.state.senderID} />
)
}
}
And then inside AnswerQuestions
<li key={ id }>
<a onClick={() => this.props.onClick(senderID)} href ={ '/temp' }>{description}</a>
</li>
I try to map an array and put click event on the array items. I know it's a bit different because of how JavaScript handles functions but I can't make it work. I get the error: Cannot read property 'saveInStorage' of undefined. Can anyone tell me what I'm doing wrong? Thanks in advance! Here is my code:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
this is undefined in renderUser()
You need to bind this for renderUser() in your constructor.
Also, you are calling saveInStorage() every time the component is rendered, not just onClick, so you'll need to use an arrow function in renderUser
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this);
this.renderUser = this.renderUser.bind(this);
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
Instead of binding you can also use an arrow function (per mersocarlin's answer). The only reason an arrow function will also work is because "An arrow function does not have its own this; the this value of the enclosing execution context is used" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The enclosing execution in your case is your render, where this is defined.
You need to make two changes to your code which are outlined below.
You are invoking the function when the component is rendered. To fix this update this line to the following
<p key={i} onClick={() => this.saveInStorage(user)}>
This means that the function will only be invoked when you click on the item.
You also need to bind the renderUser in your constructor or else use an arrow function.
this.renderUser = this.renderUser.bind(this);
See working example here.
Your onClick event handler is wrong.
Simply change it to:
onClick={() => this.saveInStorage(user)}
Don't forget to also bind renderUser in your constructor.
Alternatively, you can choose arrow function approach as they work the same as with bind:
class Gebruikers extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
}
}
saveInStorage = (e) => {
alert("test")
}
renderUser = (user, i) => {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
{user.name}
</p>
)
}
render() {
return (
<div>{this.state.users.map(this.renderUser)}</div>
);
}
}
ReactDOM.render(
<Gebruikers />,
document.getElementById('root')
)
<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="root"></div>
Paul Fitzgeralds answer is the correct one, although I'd like to propose a different way of handling this, without all the binding issues.
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
}
saveInStorage = (e) => {
console.log("test");
};
render() {
return (
<div>
{this.state.users.map((user, i) => {
return (<p key={i} onClick={() => this.saveInStorage(user)}>f</p>);
})}
</div>
);
}
}
With saveInStorage = (e) => {}; you are binding the saveInStorage function to the this context of your class. When invoking saveInStorage you'll always have the (at least I guess so in this case) desired this context.
The renderUser function is basically redundant. If you return one line of JSX, you can easily do this inside your render function. I think it improves readability, since all your JSX is in one function.
You are not sending the parameters to this.renderUser
this.state.users.map((user, i) => this.renderUser(user, i))
Also your onClick function should be slightly changed. Here's the full code changed:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map((user, i) => this.renderUser(user, i))
}
</div>
);
}
}