I have a React app like:
Main.js-
import React, { Component } from 'react';
import _ from 'underscore';
import ApplicationsButtons from '../components/ApplicationsButtons';
let applications_url = 'http://127.0.0.1:8889/api/applications'
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {applications: [], selected_app: 1};
this.updateSelectedApp = this.updateSelectedApp.bind(this);
}
componentDidMount() {
let self = this;
$.ajax({
url: applications_url,
method: 'GET',
success: function(data) {
console.log(data);
let objects = data.objects;
let apps = objects.map(function(object) {
return {name: object.name, id: object.id};
});
console.log(apps);
self.setState({applications: apps});
}
});
}
updateSelectedApp(id) {
this.setState({selected_app: id});
}
render() {
return (
<div>
{this.state.selected_app}
<ApplicationsButtons apps={this.state.applications} />
</div>
);
}
}
ApplicationsButtons.js-
import React, { Component } from 'react';
export default class ApplicationsButtons extends Component {
render() {
var buttons = null;
let apps = this.props.apps;
let clickHandler = this.props.clickHandler;
if (apps.length > 0) {
buttons = apps.map(function(app) {
return (<button key={app.id}>{app.name} - {app.id}</button>);
// return (<button onClick={clickHandler.apply(null, app.id)} key={app.id}>{app.name} - {app.id}</button>);
});
}
return (
<div>
{buttons}
</div>
);
}
}
I want to pass an onClick to the buttons that will change the currently selected app. Somehow, I just got my first infinite loop in React ("setState has just ran 20000 times"). Apparently, when I tried to pass the event handler to be called on click, I told it to keep calling it.
The onClick function should change state.selected_app for the Main component, based on the id for the button that was clicked.
You are not passing the handler as prop.
Here's what you should do:
render() {
return (
<div>
{this.state.selected_app}
<ApplicationsButtons
apps={this.state.applications}
handleClick={this.updateSelectedApp}
/>
</div>
);
}
And in ApplicationButtons:
render() {
var buttons = null;
let apps = this.props.apps;
let clickHandler = this.props.handleClick;
if (apps.length > 0) {
buttons = apps.map(app =>
<button key={app.id} onClick={() => clickHandler(app.id)}>{app.name} - {app.id}</button>);
);
}
return (
<div>
{buttons}
</div>
);
}
Related
I'm working on an app that keeps track of salespeople's availability based on being either "Available" or "With Client".
Here's the bug I'm having. I'll use an example:
2 salespeople have been added to the app. The order in which they have been added to the app seems to matter in a way I don't expect to. For example, if the first salesperson I've added is James and the second is Rick, If I click on the button next to Rick that reads "Helped A Customer", James will now populate both the "Available" and the "With Client" tables, and Rick will have disappeared.
However if I click on them in a certain order, it works fine. For example, in the same situation as the example above, if I click on James' "Helped A Customer" first, then Rick's "Helped A Customer", then James' "No Longer With Customer", then Rick's "No Longer With Customer", it behaves as expected.
Here's the github project, you can clone it and try it out yourselves:
https://github.com/jackson-lenhart/salesperson-queue
I'll post what I think is the most relevant code here as well:
main.js:
import React from "react";
import { render } from "react-dom";
import shortid from "shortid";
import deepCopy from "deep-copy";
import AddForm from "./add-form";
import Available from "./available";
import WithClient from "./with-client";
class Main extends React.Component {
constructor() {
super();
this.state = {
queue: {
available: [],
withClient: [],
unavailable: []
},
currName: ""
};
this.addToQueue = this.addToQueue.bind(this);
this.removeFromQueue = this.removeFromQueue.bind(this);
this.handleInput = this.handleInput.bind(this);
this.move = this.move.bind(this);
}
addToQueue(name) {
let newQueue = deepCopy(this.state.queue);
newQueue.available = this.state.queue.available.concat({
name,
id: shortid.generate()
});
this.setState({
queue: newQueue
});
}
removeFromQueue(id) {
let newQueue = deepCopy(this.state.queue);
for (let k in this.state.queue) {
newQueue[k] = this.state.queue[k].filter(x =>
x.id !== id
);
}
this.setState({
queue: newQueue
});
}
move(id, from, to) {
this.setState(prevState => {
let newQueue = deepCopy(prevState.queue);
let temp = newQueue[from].find(x => x.id === id);
newQueue[from] = prevState.queue[from].filter(x =>
x.id !== id
);
newQueue[to] = prevState.queue[to].concat(temp);
return {
queue: newQueue
};
});
}
handleInput(event) {
this.setState({
currName: event.target.value
});
}
render() {
return (
<div>
<AddForm
addToQueue={this.addToQueue}
handleInput={this.handleInput}
currName={this.state.currName}
/>
<Available
available={this.state.queue.available}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
<WithClient
withClient={this.state.queue.withClient}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
</div>
);
}
}
render(
<Main />,
document.body
);
add-form.js:
import React from "react";
class AddForm extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.addToQueue(this.props.currName);
}
render() {
return (
<div>
<input
type="text"
onChange={this.props.handleInput}
/>
<button onClick={this.clickWrapper}>
<strong>Add To Queue</strong>
</button>
</div>
);
}
}
export default AddForm;
available.js:
import React from "react";
import Salesperson from "./salesperson";
class Available extends React.Component {
render() {
const style = {
item: {
padding: "10px"
},
available: {
padding: "20px"
}
};
let available;
this.props.available.length === 0 ?
available = (
<p>None available.</p>
) : available = this.props.available.map(x =>
<div key={x.id} style={style.item}>
<Salesperson
key={x.id}
id={x.id}
name={x.name}
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
parent={"available"}
/>
</div>
);
return (
<div style={style.available}>
<h1>Available</h1>
{available}
</div>
);
}
}
export default Available;
salesperson.js:
import React from "react";
import DeleteButton from "./delete-button";
import HelpedButton from "./helped-button";
import NlwcButton from "./nlwc-button";
class Salesperson extends React.Component {
render() {
const style = {
name: {
padding: "10px"
},
button: {
padding: "5px"
}
};
let moveButton;
switch(this.props.parent) {
case "available":
moveButton = (
<HelpedButton
move={this.props.move}
id={this.props.id}
style={style.button}
/>
);
break;
case "withClient":
moveButton = (
<NlwcButton
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
id={this.props.id}
style={style.button}
/>
);
break;
default:
console.error("Invalid parent:", this.props.parent);
}
return (
<div>
<span style={style.name}>{this.props.name}</span>
{moveButton}
<DeleteButton
removeFromQueue={this.props.removeFromQueue}
name={this.props.name}
id={this.props.id}
style={style.button}
/>
</div>
);
}
}
export default Salesperson;
helped-button.js:
import React from "react";
class HelpedButton extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.move(this.props.id, "available", "withClient");
}
render() {
return (
<span style={this.props.style}>
<button onClick={this.clickWrapper}>
<strong>Helped A Customer</strong>
</button>
</span>
);
}
}
export default HelpedButton;
This was just a typo on my part. No longer an issue. Here's the commit that fixed the typo:
https://github.com/jackson-lenhart/salesperson-queue/commit/b86271a20ac8b700bec1e15e001b0c6ef57adb8b
I am near the end of creating my application.
So it is for banks accounts where they ask you to give the first letter of your password, then for example fourth, etc.
I'm tired of counting on my own so I created this app.
But there is the last bug that I don't know how to fix.
So when I press "1" I get "1 - H", and then when I press "4" I want to get:
"1 - H" (clicked before)
"4 - X" (clicked just now)
but instead, I get:
"4 - X" (clicked just now)
"4 - X" (clicked just now)
So it is caused by the way handleResults() function works inside my Input component, but for now it is my only concept how to approach this...
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import './style.css';
import Buttons from '../Buttons';
import Results from '../Results';
class Input extends Component {
constructor(props) {
super(props);
this.state = {
password: 'Hh9Xzke2ayzcEUPHuIfS',
selectedButtons: [],
};
this.handleButtonSelectTwo = this.handleButtonSelectTwo.bind(this);
}
handleInputChange(pass) {
this.setState({ password: pass });
}
handleButtonSelectTwo(selected) {
this.setState({
selectedButtons: [...this.state.selectedButtons, selected],
});
}
handleResults() {
return this.state.selectedButtons.map(el => (
<Results key={el} appState={this.state} />
));
}
render() {
return (
<div>
<div className="Input-textfield">
<TextField
hintText="Paste your password here to begin"
value={this.state.password}
onChange={event => this.handleInputChange(event.target.value)}
/>
</div>
<div>
<Buttons
handleButtonSelectOne={this.handleButtonSelectTwo}
array={this.state.password.length}
/>
{this.handleResults()}
</div>
</div>
);
}
}
export default Input;
and here is Results component code:
import React, { Component } from 'react';
import _ from 'lodash';
import Avatar from 'material-ui/Avatar';
import List from 'material-ui/List/List';
import ListItem from 'material-ui/List/ListItem';
import './style.css';
const style = {
avatarList: {
position: 'relative',
left: -40,
},
avatarSecond: {
position: 'relative',
top: -40,
left: 40,
},
};
class Results extends Component {
resultsEngine(arg) {
const { selectedButtons, password } = this.props.appState;
const passwordArray = password.split('').map(el => el);
const lastSelectedButton = _.last(selectedButtons);
const passwordString = passwordArray[_.last(selectedButtons) - 1];
if (arg === 0) {
return lastSelectedButton;
}
if (arg === 1) {
return passwordString;
}
return null;
}
render() {
if (this.props.appState.selectedButtons.length > 0) {
return (
<div className="test">
<List style={style.avatarList}>
<ListItem
disabled
leftAvatar={<Avatar>{this.resultsEngine(0)}</Avatar>}
/>
<ListItem
style={style.avatarSecond}
disabled
leftAvatar={<Avatar>{this.resultsEngine(1)}</Avatar>}
/>
</List>
</div>
);
}
return <div />;
}
}
export default Results;
Anyone has an idea how should I change my code inside handleResults() function to achieve my goal? Any help with solving that problem will be much appreciated.
Buttons component code:
import React from 'react';
import OneButton from '../OneButton';
const Buttons = props => {
const arrayFromInput = props.array;
const buttonsArray = [];
for (let i = 1; i <= arrayFromInput; i++) {
buttonsArray.push(i);
}
const handleButtonSelectZero = props.handleButtonSelectOne;
const allButtons = buttonsArray.map(el => (
<OneButton key={el} el={el} onClick={handleButtonSelectZero} />
));
if (arrayFromInput > 0) {
return <div>{allButtons}</div>;
}
return <div />;
};
export default Buttons;
And OneButton code:
import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
const style = {
button: {
margin: 2,
padding: 0,
minWidth: 1,
},
};
class OneButton extends Component {
constructor() {
super();
this.state = { disabled: false };
}
handleClick() {
this.setState({ disabled: !this.state.disabled });
this.props.onClick(this.props.el);
}
render() {
return (
<RaisedButton
disabled={this.state.disabled}
key={this.props.el}
label={this.props.el}
style={style.button}
onClick={() => this.handleClick()}
/>
);
}
}
export default OneButton;
In your resultsEngine function in the Results component you are specifying that you always want the _.last(selectedButtons) to be used. This is what it is doing, hence you always see the last button clicked. What you actually want is the index of that iteration to show.
const lastSelectedButton = selectedButtons[this.props.index];
const passwordString = passwordArray[selectedButtons[this.props.index]];
To get an index you have to create and pass one in, so create it when you map over the selected Buttons in the handleResults function in your Input component.
handleResults() {
return this.state.selectedButtons.map((el, index) => (
<Results key={el} appState={this.state} index={index} />
));
}
I need to always intercept when React unmounts a Component, no matter if that is a Functional or Class based component.
Here is my case:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
class Comp extends React.Component {
render() {
return (<h1>Hello World</h1>);
}
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
Now, as you can easily see, I am able to hook on every time <Comp /> gets unmounted. That is just what I need.
Things will dramatically change when that <Comp /> is a functional component:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
function Comp() {
return (<h1>Hello World</h1>);
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
So, my question is:
How can I hook on functional components?
I can change approach (or use React internal Apis), I just need to always intercept changes on a component passed as arguments for observe.
You can't. Functional components don't have lifecycles (yet).
Instead of messing with the functional component directly, why don't you just wrap the functional component in a class with a HOC. You could use recompose toClass for this.
function observe(component) => {
const classComponent = toClass(component):
const p = classComponent.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return classComponent;
}
Or just copy the code from here.
This question already has answers here:
Why is setState in reactjs Async instead of Sync?
(8 answers)
Closed 5 years ago.
I have an app like:
Main.js-
import React, { Component } from 'react';
import _ from 'underscore';
import { pick_attributes } from '../utils/general';
import ApplicationsButtons from '../components/ApplicationsButtons';
import Roles from '../components/Roles';
let applications_url = 'http://127.0.0.1:8889/api/applications'
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {
applications: [],
selected_app_id: 1,
roles: []
};
this.updateSelectedApp = this.updateSelectedApp.bind(this);
this.updateApplicationData = this.updateApplicationData.bind(this);
this.loadAppData = this.loadAppData.bind(this);
this.getSelectedApplicationData = this.getSelectedApplicationData.bind(this);
this.setRoles = this.setRoles.bind(this);
}
componentDidMount() {
this.loadAppData();
}
// componentDidUpdate() {
// this.updateApplicationData();
// }
updateApplicationData() {
this.setRoles();
}
loadAppData() {
let self = this;
$.ajax({
url: applications_url,
method: 'GET',
success: function(data) {
let objects = data.objects;
self.setState({applications_data: objects});
let apps_data = pick_attributes(objects, 'name', 'id');
self.setState({applications: apps_data});
self.updateApplicationData();
}
});
}
getSelectedApplicationData() {
let selected_app_id = this.state.selected_app_id;
let objects = this.state.applications_data;
for (let i = 0; i < objects.length; i++) {
let object = objects[i];
if (object.id == selected_app_id) {
return object
}
}
}
setRoles() {
let selected_app_id = this.state.selected_app_id;
let selected_app_object = this.getSelectedApplicationData();
let roles_data = selected_app_object.role_list;
let roles = pick_attributes(roles_data, 'name', 'id');
this.setState({roles});
}
updateSelectedApp(id) {
this.setState({selected_app_id: id});
}
render() {
return (
<div>
{this.state.selected_app_id}
<ApplicationsButtons
apps={this.state.applications}
clickHandler={this.updateSelectedApp}/>
<Roles roles={this.state.roles} />
</div>
);
}
}
ApplicationsButtons.js-
import React, { Component } from 'react';
export default class ApplicationsButtons extends Component {
render() {
var buttons = null;
let apps = this.props.apps;
let clickHandler = this.props.clickHandler;
if (apps.length > 0) {
buttons = apps.map(function(app) {
return (
<button
onClick={() => clickHandler(app.id)}
key={app.id}>
{app.name} - {app.id}
</button>
);
});
}
return (
<div>
{buttons}
</div>
);
}
}
Roles.js-
import React, { Component } from 'react';
export default class Roles extends Component {
render() {
var roles_li_elements = null;
let roles = this.props.roles;
console.log(roles);
if (roles.length > 0) {
roles_li_elements = roles.map(function(role) {
console.log(role);
return (
<li key={role.id}>
{role.name}
</li>
);
});
}
return (
<div>
<h4>Roles:</h4>
<ul>
{roles_li_elements}
</ul>
</div>
);
}
}
I want the Roles to update when the user clicks a button that picks a new app. Right now, clicking the buttons does update state.selected_app_id, but I need setRoles() to be called each time selected_app_id changes. I tried throwing it in the onClick:
updateSelectedApp(id) {
this.setState({selected_app_id: id});
this.setRoles();
}
for some reason that only changed the roles after clicking each button twice.
componentDidUpdate() {
this.updateApplicationData();
}
causes state to update forever in an infinite loop. You aren't supposed to update state inside componentWillUpdate.
updateSelectedApp(id) {
this.setState({selected_app_id: id}, () => {
this.setRoles();
});
}
I am building an isomorphic React app. The workflow I am currently working through is :
User navigates to the /questions route which makes the API call server side and loads the data on the page. This calls the renderData() function like it should and loads all the questions for the user to see.
User clicks add button to add new question and a modal pops up for a user to enter in the form fields and create a new question.
With every change in the modal, the renderData() function is getting called (which it shouldn't). When the user clicks the Create Question button, the renderData() function is also getting called is throwing an error because the state changes.
I can't pinpoint why the renderData() function is getting called every single time anything happens in the modal. Any ideas as to why this is happening and how to avoid it?
Main component :
import React, { Component, PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './QuestionsPage.scss';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Loader from 'react-loader';
import QuestionItem from '../../components/QuestionsPage/QuestionItem';
import FloatButton from '../../components/UI/FloatButton';
import AddQuestionModal from '../../components/QuestionsPage/AddQuestionModal';
const title = 'Questions';
class QuestionsPage extends Component {
constructor(props) {
super(props);
this.state = QuestionStore.getState();
this.onChange = this.onChange.bind(this);
this.openModal = this.openModal.bind(this);
this.closeMOdal = this.closeModal.bind(this);
}
static contextTypes = {
onSetTitle: PropTypes.func.isRequired,
};
componentWillMount() {
this.context.onSetTitle(title);
QuestionStore.listen(this.onChange);
}
componentWillUnmount() {
QuestionStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
openModal = () => {
this.setState({ modalIsOpen: true});
}
closeModal = () => {
this.setState({ modalIsOpen: false});
}
createQuestion = () => {
const date = new Date();
const q = this.state.question;
q.createdAt = date;
this.setState({ question : q });
QuestionStore.createQuestion(this.state.question);
}
textChange = (val) => {
const q = this.state.question;
q.text = val;
this.setState({ question : q });
}
answerChange = (val) => {
const q = this.state.question;
q.answer = val;
this.setState({ question : q });
}
tagChange = (val) => {
const q = this.state.question;
q.tag = val;
this.setState({ question : q });
}
companyChange = (val) => {
const q = this.state.question;
q.company = val;
this.setState({ question : q });
}
renderData() {
return this.state.data.map((data) => {
return (
<QuestionItem key={data.id} data={data} />
)
})
}
render() {
return (
<div className={s.root}>
<div className={s.container}>
<h1>{title}</h1>
<div>
<Loader loaded={this.state.loaded} />
<FloatButton openModal={this.openModal}/>
<AddQuestionModal
open = {this.state.modalIsOpen}
close = {this.closeModal}
createQuestion = {this.createQuestion}
changeText = {this.textChange}
changeAnswer = {this.answerChange}
changeTag = {this.tagChange}
changeCompany = {this.companyChange}
/>
{ this.renderData() }
</div>
</div>
</div>
);
}
}
export default withStyles(QuestionsPage, s);
Modal Component :
import React, { Component, PropTypes } from 'react';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Modal from 'react-modal';
import TextInput from '../UI/TextInput';
import Button from '../UI/Button';
class AddQuestionModal extends Component {
createQuestion = () => {
this.props.createQuestion();
}
closeModal = () => {
this.props.close();
}
changeText = (val) => {
this.props.changeText(val);
}
changeAnswer = (val) => {
this.props.changeAnswer(val);
}
changeTag = (val) => {
this.props.changeTag(val);
}
changeCompany = (val) => {
this.props.changeCompany(val);
}
render() {
return (
<Modal
isOpen={this.props.open}
onRequestClose={this.closeModal} >
<TextInput
hintText="Question"
change={this.changeText} />
<TextInput
hintText="Answer"
change={this.changeAnswer} />
<TextInput
hintText="Tag"
change={this.changeTag} />
<TextInput
hintText="Company"
change={this.changeCompany} />
<Button label="Create Question" onSubmit={this.createQuestion} disabled={false}/>
<Button label="Cancel" onSubmit={this.closeModal} disabled={false}/>
</Modal>
);
}
}
export default AddQuestionModal;
On click of
It's happening because every change causes you to call the setState method and change the state of the main component. React will call the render function for a component every time it detects its state changing.
The onChange event on your inputs are bound to methods on your main component
Each method calls setState
This triggers a call to render
This triggers a call to renderData
React allows you to change this by overriding a shouldComponentUpdate function. By default, this function always returns true, which will cause the render method to be called. You can change it so that only certain changes to the state trigger a redirect by comparing the new state with the old state.