ReactJS Onclick not working for generated elements - javascript

I am generating a list of elements with:
LeftPanel.js
if (this.state.trader_or_team == 'trader') {
itemList = users.map(u => <User key={u.id} user={u}
selected={this.props.selected_trader && this.props.selected_trader.id == u.id}
onClick={this.props.handleTraderSelection.bind(this, u)}
/>);
} else {
itemList = teams.map(t => <Team key={t.id} team={t}
selected={this.props.selected_team && this.props.selected_team.id == t.id}
onClick={this.props.handleTeamSelection.bind(this, t)}
/>)
}
handleTeamSelection/handleTraderSelection are in the parent component:
TargetManager.js
handleTraderSelection(selected_trader) {
console.log('test')
this.setState({
selected_trader
});
}
handleTeamSelection(selected_team) {
this.setState({
selected_team
});
}
They are passed down as props:
<LeftPanel
handleTraderSelection={::this.handleTraderSelection}
handleTeamSelection={::this.handleTeamSelection}
/>
And rendered:
LeftPanel.js
return(
<div className="item-container">
{itemList}
</div>
)
When I click on any of the elements nothing happen at all. What is going wrong?
It appears to break when I bind something to it, in the render method of LeftPanel.js:
render() {
this.props.handleTraderSelection()
...
Works,
render() {
this.props.handleTraderSelection.bind(this)
...
Does not work.

Try binding the handleTraderSelection and handleTeamSelection function like
handleTraderSelection = (selected_trader) => {
console.log('test')
this.setState({
selected_trader
});
}
handleTeamSelection = (selected_team) => {
this.setState({
selected_team
});
}
I have been following the conventional method to call the parent function like
handleTraderSelection = (value) => {
this.props.handleTraderSelection(value);
}
handleTeamSelection= (value) => {
this.props.handleTeamSelection(value);
}
if (this.state.trader_or_team == 'trader') {
itemList = users.map(u => <User key={u.id} user={u}
selected={this.props.selected_trader && this.props.selected_trader.id == u.id}
onClick={this.handleTraderSelection.bind(this, u)}
/>);
} else {
itemList = teams.map(t => <Team key={t.id} team={t}
selected={this.props.selected_team && this.props.selected_team.id == t.id}
onClick={this.handleTeamSelection.bind(this, t)}
/>)
}
and it works for me well.

I was missing my onClick for my elements...:
export default class User extends Component {
render() {
return (
<div onClick={this.props.onClick}>
...

Related

Function is not being called as it should in React onClick()

Basically, i created a button to call a function when the button is clicked, it's working, the console.log() shows the message "Working", but the script inside it, it's not being shown on the browser. I have no idea what's wrong, could someone help me? I'm very new in Software Engineer and started learning React a few days ago, so i dont know too much about it.
This is the entire code:
class QuizGame extends React.Component {
constructor() {
super();
this.state = {
questions: Questions
}
}
Game() {
const { questions } = this.state;
const item = questions[Math.floor(Math.random() * questions.length)];
const items = [item.a, item.b, item.c, item.d];
console.log('Working');
return (
<div>
<GameParagraph value={item.ques} />
{
items.map(quest => (
<div key={quest}>
<div>
<GameButton
value={quest}
onClick={() => {
if(item.ans === quest) {
return console.log('Working');
} else {
return console.log('not working')
}
}}
/>
</div>
</div>
))
}
</div>
)
}
render() {
return (
<StartGame
onClick={() => this.Game()}
/>
);
}
}
And that's the button to call the Game():
class StartGame extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>Start</button>
);
}
}
You can try like this:
class QuizGame extends React.Component {
constructor() {
super();
this.state = {
questions: Questions,
hasGameStarted: false
}
}
Game() {
const { questions } = this.state;
const item = questions[Math.floor(Math.random() * questions.length)];
const items = [item.a, item.b, item.c, item.d];
console.log('Working');
return (
<div>
<GameParagraph value={item.ques} />
{
items.map(quest => (
<div key={quest}>
<div>
<GameButton
value={quest}
onClick={() => {
if(item.ans === quest) {
return console.log('Working');
} else {
return console.log('not working')
}
}}
/>
</div>
</div>
))
}
</div>
)
}
startGameClicked() {
this.setState({hasGameStarted: true});
}
render() {
return (
<StartGame
onClick={this.startGameClicked}
/>
{this.state.hasGameStarted ? this.Game() : null}
);
}
Make sure you are binding the onClick event in StartGame component properly.
You need to refactor your code. Returning JSX from an event handler (which is what your function Game is) won't cause your component to render.
Instead, the "React" way to handle this is to provide some state variable that can be updated by the super class to cause a render. i.e.
class QuizGame extends React.Component {
constructor() {
super();
this.state = {
started: false,
questions: Questions
}
}
startGame() { // JavaScript functions should be camelCase
this.setState({started: true});
}
render() {
if (this.state.started) {
return const { questions } = this.state;
const item = questions[Math.floor(Math.random() * questions.length)];
const items = [item.a, item.b, item.c, item.d];
return (
<div>
<GameParagraph value={item.ques} />
{items.map(quest => (
<div key={quest}>
<div>
<GameButton
value={quest}
onClick={() => {
if(item.ans === quest) {
return console.log('Working');
} else {
return console.log('not working')
}
}}/>
</div>
</div>
))}
</div>);
} else {
return <StartGame onClick={() => this.startGame()} />;
}
}
}

How pass data from the child component (the child has its own state) to the parent?

Expected effect: click button -> call function setEditing() -> call function item() inside setEditing() -> this.state.isEditing changes to true -> in parent this.state.isEdit changes to true. When I call the item () function, the value of isEditing does not change
App
class App extends React.Component {
constructor() {
super();
this.state = {
isEdit = false;
};
}
handleSomething = (value) => {
this.setState(prevState => {
return {
isEdit: value
};
});
}
render() {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
handleSomething={this.handleSomething}
/>
)
}
</ul>
</div>
);
}
}
Todo
class Todo extends Component {
state = {
isEditing: false
}
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
})
this.item();
}
item = () => {
const { isEditing} = this.state;
this.props.handleSomething(isEditing);
}
render() {
return (
<button onClick={() => this.setEditing()}>Edit</button>
)
}
}
You'll need to call this.item after the state was changed, something like
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
}, this.item)
}
Also, if you want to derive a new state form the old one, you'll have to use something like this:
setEditing = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}), this.item)
}
Try basing your state change on the previous state, and call parent function in a callback :
setEditing = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}), this.item)
}
Because as written in the React doc :
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
(https://reactjs.org/docs/react-component.html#setstate)
class Todo extends React.Component {
state = {
isEditing: false
}
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
},this.item())
}
item = () => {
const { isEditing} = this.state;
this.props.handleSomething(isEditing);
}
render() {
return (
<button onClick={() => this.setEditing()}>
Edit
</button>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
isEdit : false,
todos : [
"test 1",
"test 2"
]
};
}
handleSomething = (value) => {
this.setState(prevState => {
return {
isEdit: value
};
});
}
render() {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
handleSomething={this.handleSomething}
/>
)
}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<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>
<div id="app"></div>

Jest + React JS testing component

I am just getting acquainted with testing and I have a little problem. I'm trying to test the functions of a component that change state and call functions of a document. In the documentation of Jest and Enzyme, I did not find the right example. Here is a sample code:
class EditableText extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isEditing: false,
currentValue: null
};
this.textAreaRef = React.createRef();
}
onClick = () => {
if (!this.state.isEditing) {
this.setState({ isEditing: true });
setTimeout(() => {
this.textAreaRef.current.focus();
}, 100);
document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('click', this.onOutsideClick);
}
};
onOutsideClick = e => {
if (e.target.value !== this.state.currentValue) {
this.closeEditMode();
}
};
closeEditMode() {
this.setState({ isEditing: false });
document.removeEventListener('keydown', this.onKeyDown);
document.removeEventListener('click', this.onOutsideClick);
}
onKeyDown = e => {
if (e.code === 'Enter') {
this.onUpdateValue();
} else if (e.code === 'Escape') {
this.closeEditMode();
}
};
onUpdateValue = () => {
this.props.onSubmit(this.state.currentValue || this.props.value);
this.closeEditMode();
};
render() {
const { isEditing, currentValue } = this.state;
const { value } = this.props;
return isEditing ? (
<TextArea
className="editable-text__textarea"
ref={this.textAreaRef}
onClick={this.onClick}
value={currentValue === null ? value || '' : currentValue}
onChange={(_e, { value }) => this.setState({ currentValue: value })}
/>
) : (
<div className="editable-text__readonly" onClick={this.onClick}>
<div>{this.props.children}</div>
<div className="editable-text__icon-container">
<Icon name="edit" className="editable-text__icon" />
</div>
</div>
);
}
}
How to test functions, that receive and change state and calls document.addEventListener? Please, help.
UPDATE > Tests i already wrote:
describe('tests of component', () => {
test('Component renders correctly with isEditing=false', () => {
const component = renderer
.create(<EditableText children="I'm text in EditableText" />)
.toJSON();
expect(component).toMatchSnapshot();
});
test('Component changes to isEditing=true when clicked on it', () => {
const component = renderer
.create(<EditableText />);
let tree = component.toJSON();
tree.props.onClick();
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('tests of logic', () => {
test.only('OnClick should change state and add event listeners', ()=> {
const state = { isEditing: false, currentValue: null }
global.document.addEventListener = jest.fn();
EditableText.onClick(() => {
expect(global.document.addEventListener).toHaveBeenCalled();
});;
});
});

display child accordion only when parent accordion is opened

I am developing an accordion which is nested in character. I mean, an accordion can have its child accordion as well. Right now, a simple accordion has been build up. The toggling part works either. However, there is a problem. Child accordion is shown without opening the parent accordion. How can i show child accordion only when parent accordion is clicked and hide it when clicked again?
Here is what i have done
class Accordion extends React.Component {
constructor(props) {
super(props);
let state = { activeSections: {} };
React.Children.toArray(props.children).forEach(child => {
if (child) {
state.activeSections[child.props.name] = !!child.props.defaultOpen;
}
});
this.state = state;
}
get isControlled() {
return typeof this.props.onToggle === "function";
}
expandAll = () => {
if (this.isControlled) {
this.props.expandAll();
} else {
let { activeSections } = this.state;
Object.keys(activeSections).forEach(k => (activeSections[k] = true));
this.setState({ activeSections });
}
};
collapseAll = () => {
if (this.isControlled) {
this.props.collapseAll();
} else {
let { activeSections } = this.state;
Object.keys(activeSections).forEach(k => (activeSections[k] = false));
this.setState({ activeSections });
}
};
onToggle = name => {
if (this.isControlled) {
this.props.onToggle(name);
} else {
let activeSections = this.state.activeSections;
this.setState({
activeSections: { ...activeSections, [name]: !activeSections[name] }
});
}
};
componentWillReceiveProps(nextProps) {
let { activeSections } = this.state;
React.Children.toArray(nextProps.children)
.filter(c => c)
.forEach(child => {
if (activeSections[child.props.name] == null) {
activeSections[child.props.name] = !!child.props.defaultOpen;
}
});
this.setState({ activeSections });
}
render() {
let { activeSections } = this.state;
let children = React.Children.toArray(this.props.children);
// if (!children.find(c => c && c.type === AccordionHeader)) {
// children = [<AccordionHeader />, ...children];
// }
console.log("children", children);
return (
<div>
{children.map(child => {
if (!child) {
return child;
} else if (child.type === AccordionItem) {
return React.cloneElement(child, {
expanded: this.isControlled
? child.props.expanded
: activeSections[child.props.name],
onToggle: this.onToggle
});
} else {
return child;
}
})}
</div>
);
}
}
export default Accordion;
class AccordionItem extends React.Component {
render() {
let {
expanded,
caption,
onToggle,
name,
children,
render,
...rest
} = this.props;
return render ? (
render({ onToggle: onToggle.bind(null, name), expanded })
) : (
<styled.AccordionItem style={{ margin: 10 }}>
<styled.AccordionHeader
onClick={() => onToggle(name)}
active={expanded}
>
{caption}
</styled.AccordionHeader>
<styled.AccordionBody active={rest.defaultOpen || expanded}>
{children && (
<styled.AccordionBodyContent>
{children}
</styled.AccordionBodyContent>
)}
</styled.AccordionBody>
</styled.AccordionItem>
);
}
}
export default AccordionItem;
I have create a working example in the sandbox and here it is
https://codesandbox.io/s/z25j8q2v4m

Show and Hide specific component in React from a loop

I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}

Categories

Resources