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
Related
Can anyone help me explain it, because when use functional it work, but not when use class component
Different class component and func component in handle event react.
fyi it take input text search on child and at parent doing handle event and other
child use functional
import React from "react";
const Header = ({searchTitle,onSearch}) => {
return (
<div>
<header>
<h1>Notes</h1>
<div className="search">
<input
type="text"
placeholder="Search..."
value={searchTitle}
onChange={onSearch}
></input>
</div>
</header>
</div>
);
};
export default Header;
child use class component
import React, { Component } from "react";
export class HeaderPage extends Component {
render() {
const searchTitle = this.props.searchTitle;
const onSearch = this.props.onSearchHandler;
return (
<header>
<h1>Notes</h1>
<div className="search">
<input
type="text"
placeholder="Search..."
value={searchTitle}
onChange={onSearch}
></input>
</div>
</header>
);
}
}
export default HeaderPage;
parent class componet
import React, { Component } from "react";
import HeaderPage from "../organisms/HeaderPage";
import MainPage from "../organisms/MainPage";
import FooterPage from "../organisms/FooterPage";
import { getInitialData } from "../../utils/data";
import Header from "../organisms/Header";
export class PageNote extends Component {
constructor(props) {
super(props);
this.state = {
dataNotes: getInitialData(),
dataNotesFiltered: [],
searchTitle: "",
};
//binding
this.onAddNoteHandler = this.onAddNoteHandler.bind(this);
this.onDeleteHandler = this.onDeleteHandler.bind(this);
this.onArchivedHandler = this.onArchivedHandler.bind(this);
this.onSearchHandler = this.onSearchHandler.bind(this);
}
onAddNoteHandler({ title, body }) {
this.setState((prevState) => {
return {
dataNotes: [
...prevState.dataNotes,
{
id: +new Date(),
title,
body,
archived: false,
createdAt: new Date().toLocaleDateString(),
},
],
};
});
}
onDeleteHandler(id) {
const dataNotes = this.state.dataNotes.filter(
(dataNote) => dataNote.id !== id
);
this.setState({ dataNotes });
}
onArchivedHandler(id) {
const dataNotes = this.state.dataNotes.map((note) => {
if (note.id === id) {
return { ...note, archived: !note.archived };
} else {
return note;
}
});
this.setState({ dataNotes });
}
onSearchHandler(event) {
this.setState(() => {
return {
searchTitle: event.target.value,
};
});
}
render() {
console.log(this.state.dataNotes);
console.log(`search ${this.state.searchTitle}`);
const dataNotes = this.state.dataNotes.filter((note) =>
note.title.toLowerCase().includes(this.state.searchTitle.toLowerCase())
);
return (
<>
<HeaderPage
searchTitle={this.state.searchTitle}
onSearch={this.onSearchHandler}
></HeaderPage>
<Header
searchTitle={this.state.searchTitle}
onSearch={this.onSearchHandler}
></Header>
<MainPage
dataNotes={dataNotes}
addNote={this.onAddNoteHandler}
onDelete={this.onDeleteHandler}
onArchive={this.onArchivedHandler}
></MainPage>
<FooterPage></FooterPage>
</>
);
}
}
export default PageNote;
I have two data objects and 3 hierarchical components below, or in sandbox here. The data contain a list of questions, each rendered with input box right below it that allow multiple replies. However, I don't know how to proper update the state of the correct question after typing in a reply to a particular question.
index.js
import React from 'react';
import { render } from 'react-dom';
import QA from './qa';
//parent of qa.js
const questions = [
{id : 1,
question: "where is the origin of chihuahua?"},
{id : 2,
question: "when does the great migration happen in Africa?"}
]
const answers = [
{
id : 1,
id_question : 1,
answer: "Mexico"
},
{
id : 2,
id_question : 1,
answer: "Argentina"
},
{
id : 3,
id_question : 2,
answer: "Apr"
},
{
id : 4,
id_question : 2,
answer: "May"}
]
export default class App extends React.Component {
state = {
q : questions,
a : answers
}
handleSubmit = (val, index) => {
alert('index',index)
this.setState({
...this.state,
a: [...this.state.a, {id_question: index, answer: val}]
});
}
render() {
console.log(this.state)
return (
questions.map((q, index) =>
<QA
key={index}
question={q.question}
onSubmit={this.handleSubmit}
/>
)
)
}
}
render(<App />, document.getElementById('root'));
qa.js
import React from 'react';
import Answer from './answer';
import "./style.css"
//parent of answer.js
export default class QA extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { question } = this.props
const { text } = this.state
return (
<div class='qa-block'>
<div>Question: {question}</div>
<Answer onSubmit={this.props.onSubmit}/>
</div>
)
}
}
and answer.js
import React from 'react';
const styles = {
backgroundColor: 'lightgray',
};
export default class Answer extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { text } = this.state
return (
<div style={styles}>
<h4>Answers</h4>
<input type="text"
value={text}
onInput={(e) => this.setState({ text: e.target.value })} />
<button onClick={() => this.props.onSubmit(this.state.text)}>Send to the parent</button>
</div>
)
}
}
A few newbie questions:
where do I call index such that setState append to state.answer that right question id and increment answer id by 1?
should I have nested answers as a property of question instead?
Thanks for any help!
You can simply pass questionNo as props to qa & ans components, then retrive through the callback like following:
in index.js
render() {
console.log(this.state)
return (
questions.map((q, index) =>
<QA
questionNo={index}
question={q.question}
onSubmit={this.handleSubmit}
/>
)
)
}
in qa.js
render() {
const { question, questionNo } = this.props
const { text } = this.state
return (
<div class='qa-block'>
<div>Question: {question}</div>
<Answer questionNo={questionNo} onSubmit={this.props.onSubmit}/>
</div>
)
}
in answer.js
render() {
const { text } = this.state
return (
<div style={styles}>
<h4>Answers</h4>
<input type="text"
value={text}
onInput={(e) => this.setState({ text: e.target.value })} />
<button onClick={() => this.props.onSubmit(this.state.text, this.props.questionNo)}>Send to the parent</button>
</div>
)
}
after this you will get index of clicked item in index.js
So to identify the question you need to pass the id_question to the submit button, so if you have the parameter then on the callback you will be able to get it.
once you get you can do a find on the answers array of objects and update the userTyped answer.
handleSubmit = (val, text) => {
const typedAnswer = {...this.state.a.find(ans => ans.id_question === val), userTypedAnswer: text};
this.setState({
...this.state,
a: [...this.state.a, typedAnswer]
});
}
code
index.js
import React from 'react';
import { render } from 'react-dom';
import QA from './qa';
//parent of qa.js
const questions = [
{id: 1,
question: "where is the origin of chihuahua?"},
{id: 2,
question: "when does the great migration happen in africa?"}
]
const answers = [
{id_question: 1,
answer: "Mexico"},
{id_question: 1,
answer: "Argentina"},
{id_question: 2,
answer: "Apr"},
{id_question: 2,
answer: "May"}
]
export default class App extends React.Component {
state = {
q : questions,
a : answers
}
handleSubmit = (val, text) => {
const typedAnswer = {...this.state.a.find(ans => ans.id_question === val), userTypedAnswer: text};
this.setState({
...this.state,
a: [...this.state.a, typedAnswer]
});
}
render() {
return (
<>{
questions.map((q, index) =>
<QA
key={index}
question={q}
onSubmit={this.handleSubmit}
/>
)
}
<p>User Typed Answers and questions after submit</p>
{
this.state.a.map(ans => (
ans.userTypedAnswer && <div>
<span>{ans.id_question}</span>: <span>{ans.userTypedAnswer}</span>
</div>
))
}
</>
)
}
}
render(<App />, document.getElementById('root'));
// answer.js
import React from 'react';
const styles = {
backgroundColor: 'lightgray',
};
export default class Answer extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { text } = this.state
const {onSubmit, qid} = this.props
return (
<div style={styles}>
<h4>Answers</h4>
<input type="text"
value={text}
onInput={(e) => this.setState({ text: e.target.value })} />
<button onClick={() => onSubmit(qid, this.state.text)}>Send to the parent</button>
</div>
)
}
}
qa.js
import React from 'react';
import Answer from './answer';
import "./style.css"
//parent of answer.js
export default class QA extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { question: {question, id}, onSubmit } = this.props
const { text } = this.state
return (
<div class='qa-block'>
<div>Question: {question}</div>
<Answer onSubmit={onSubmit} qid={id}/>
</div>
)
}
}
Working example
I am newbie to react, redux-saga, I have a dropdown in page Display, when I select it move to respective component (eg. policy, score), In Component Pages, I have a button Add New, on clicking it will navigate to a page as mentioned in link url , which is a page having cancel button, on cancelling it returns to the Display.js but no dropdown selected,
I would like to keep the state articleMode, when navigating to a page and returning back to same page,
articleMode returns to state -1 instead of selected component Policy or Score
actions.js
export const updateArticleMode = data => {
console.log(data.body);
return {
type: CONSTANTS.UPDATE_ARTICLE_MODE,
data: data.body
};
};
queryReducer.js
import * as CONSTANTS from "../constants/constants";
const initialState = {
articleMode: ""
}
case CONSTANTS.UPDATE_ARTICLE_MODE: {
return {
...state,
articleMode: data.mode
};
}
export default queryReducer;
constants.js
export const UPDATE_ARTICLE_MODE = "UPDATE_ARTICLE_MODE";
Display.js
import React from "react";
import { connect } from "react-redux";
import Policy from "../policy";
import Score from "./../score";
import { updateArticleMode } from "../../../../actions/actions";
const articleMode = [
{ name: "Select", id: "-1" },
{ name: "Score", id: "Score" },
{ name: "Policy", id: "Policy" }
]
class Display extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
articleMode: "-1"
};
}
componentWillMount = () => {
this.setState({ articleMode: this.props.queryData.articleMode || "-1" });
};
_getComponent = () => {
const { articleMode } = this.state;
if (articleMode === "Policy") {
return <DisplayPolicy></DisplayPolicy>;
}
if (articleMode === "Score") {
return <DisplayScore></DisplayScore>;
}
}
render() {
return (
<React.Fragment>
<select name="example"
className="simpleSearchSelect1"
value={this.state.articleMode}
onChange={event => {
this.setState({ articleMode: event.target.value });
this.props.dispatch(
updateArticleMode({ body: { mode: event.target.value } })
);
}}
style={{ marginLeft: "2px" }}
>
{articleMode.length != 0 &&
articleMode.map((option, index) => {
const { name, id } = option;
return (
<option key={index} value={id}>
{name}
</option>
);
})}
</select>
{this.state.articleMode === "-1"
? this._renderNoData()
: this._getComponent()}
</React.Fragment>
)}
const mapStateToProps = state => {
return {
queryData: state.queryData
};
};
export default connect(mapStateToProps)(Display);
}
DisplayPolicy.js
import React from "react";
class DisplayPrivacyPolicy extends React.Component {
constructor(props) {
super(props);
}<Link
to={{
pathname: "/ui/privacy-policy/addNew",
state: {
language: "en"
}
}}
>
<button className="page-header-btn icon_btn display-inline">
<img
title="edit"
className="tableImage"
src={`${process.env.PUBLIC_URL}/assets/icons/ic_addstore.svg`}
/>
{`Add New`}
</button>
</Link>
AddNew.js
<Link
to =pathname: "/ui/display",
className="btn btn-themes btn-rounded btn-sec link-sec-btn"
>
Cancel
</Link>
Im having issues with react todo list.
When submitted the list item appears fine.
I should then be able to delete this be clicking on the item,however nothing happens. As soon as i try to add another item the pages refreshes and all items are removed?
console log just shows
[object object]
App
import React, { Component } from 'react';
import './App.css';
import TodoItem from './TodoItem';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItems = this.addItems.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
addItems(e) {
if (this._inputElement !== '') {
let newItem = {
text: this._inputElement.value,
key: Date.now()
};
this.setState(prevState => {
return {
items: prevState.items.concat(newItem)
};
});
}
this._inputElement.value = '';
e.preventDefault();
}
deleteItem(key) {
console.log('key is' + key);
console.log('itesm as' + this.state.items);
var filteredItems = this.state.items.filter(function(item) {
return item.key !== key;
});
this.setState = {
items: filteredItems
};
}
render() {
return (
<div className="app">
<h2> things to do</h2>
<div className="form-inline">
<div className="header">
<form onSubmit={this.addItems}>
<input
ref={a => (this._inputElement = a)}
placeholder="enter task"
/>
<button type="submit">add</button>
</form>
</div>
</div>
<TodoItem entries={this.state.items} delete={this.deleteItem} />
</div>
);
}
}
export default App;
todoItem
import React, { Component } from 'react';
//search bar
class TodoItem extends Component {
constructor(props) {
super(props);
this.createTasks = this.createTasks.bind(this);
}
createTasks(item) {
return (
<li onClick={() => this.delete(item.key)} key={item.key}>
{item.text}
</li>
);
}
delete(key) {
console.log('key is ' + key);
this.props.delete(key);
}
render() {
let todoEntries = this.props.entries;
let listItems = todoEntries.map(this.createTasks);
return <ul className="theList">{listItems}</ul>;
}
}
export default TodoItem;
You are assigning to setState instead of using it as a method that it is
Change
this.setState = {
items: filteredItems
};
to
this.setState({
items: filteredItems
});
And that is also the reason it will reload the app, as you have overwritten the setState method you should be getting an error that setState is not a function and it would crash the app.
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} />
));
}