I'm trying to set the display of my ReactJS component dynamically.
Basically the game is the following : the user push a button, then the value of the affiliate state is set to true. Allowing to handle the displaying of the buttons.
However my state doesn't changes when I push a button, despite I log it after the change would have occurred. However I have set my state. I wonder what going wrong.
Here my tiny snippet, easy testable and reproducible :
https://codesandbox.io/s/21963yy01y
Here my snippet.js :
export default class App extends React.Component {
state = {
displaySelection: false,
displayCreate: false,
displayUpdate: false,
displayDelete: false,
}
viewSelection = (e) => {
e.preventDefault();
Object.keys(this.state).map((key, index) => {
// console.log("key value: ", key)
console.log("target name: ", e.target.name)
if (key === e.target.name) {
console.log(e.target.name, " set to true =)")
return this.setState({ [e.target.name]: true }, console.log("display state: ", this.state))
} else {
this.setState({ [e.target.name]: false })
}
});
}
render() {
return (
<div className="App">
<button onClick={this.viewSelection}> Launch the Object.keys function =) splay</button>
<div >
<button name="displayCreate" onClick={this.viewSelection}> Create </button>
<button name="displayUpdate" onClick={this.viewSelection}> Update </button>
<button name="displayDelete" onClick={this.viewSelection}> Delete </button>
<button name="displaySelection" onClick={this.viewSelection}> O </button>
</div>
</div>
);
}
}
Why when I push a button the state of this button doesn't change ?
Any hint would be great,
thanks
Found a flaw in your logic. In your else statement in your viewSelection function, you have:
else {
this.setState({ [e.target.name]: false });
}
So in every iteration of the loop, you are setting the target that was clicked to false in state. You can solve that by changing e.target.name to key, like so:
else {
this.setState({ [key]: false });
}
So that way you're only changing the key that isn't the current target. But this is still inefficient because you're still running setState 4 times (1 for each key in state). One more efficient way to achieve what you're looking for is to have an object (essentially a copy of what's in state) with the keys set to false by default. Then take the target from the click and set that to true, like so:
viewSelection = e => {
e.preventDefault();
let newValues = {
displaySelection: false,
displayCreate: false,
displayUpdate: false,
displayDelete: false
};
newValues[e.target.name] = true;
this.setState(newValues);
}
Related
I am stuck trying to figure out how to update my button in real time. I have read through quite a few forums/threads (React form onChange->setState one step behind) on how to fix the issue but haven't been able to apply it to my solution to get the button to dynamically update on the correct action, it is 1 behind.
constructor(props: EditRole | Readonly<EditRole>) {
super(props);
this.state = {
originalName: '',
name: '',
sealId: '',
rolesSearchPrefix: '',
permittedRoles: '',
sealIdError: '',
oldSealId: '',
oldRolesSearchPrefix: '',
oldPermittedRoles: '',
valueUpdated: false
};
this.saveRole = this.saveRole.bind(this);
this.handleChangeRoles = this.handleChangeRoles.bind(this);
}
componentDidMount() {
this.getRole();
}
getRole() {
store.getRole(this.props.match.params.name).then((res: any) => {
const { name, sealId, rolesSearchPrefix, permittedRoles } = res.data;
this.setState({ originalName: name, sealId, oldSealId: sealId, rolesSearchPrefix, oldRolesSearchPrefix: rolesSearchPrefix, permittedRoles, oldPermittedRoles: permittedRoles });
});
}
handleChangeRoles = (e: Event) => {
this.setState({ ...this.state, permittedRoles: (e.target as HTMLInputElement).value }, () => {
(e.target as HTMLInputElement).value === this.state.oldPermittedRoles ? this.setState({ ...this.state, valueUpdated: false }) : this.setState({ ...this.state, valueUpdated: true });
});
}
As you can see in the code above, I am getting my role and I set the values in the state. This is all working. Then when I get to my form:
<form>
<input
id="roles"
label="Roles"
value={this.state.permittedRoles}
// onChange={this.handChangeRoles}
// onChange={(e) => this.setState({ ...this.state, permittedRoles: (e.target as HTMLInputElement).value, valueUpdated: true })}
onChange={(e) => this.handleChangeRoles(e)}
multiline={true}>
</MdsTextInput>
{backButton} {this.state.valueUpdated ? nextButton : disabledButton}
</form>
The update is happening correctly, characters are updating the value however the button status is not changing until I click off the form. So if I'm in the input field, value is "test" the button is disabled. I type "a" ("testa") and noting changes until I click out of the input field, which it then appropriately enables the button. I want it to dynamically update. I have tried a few different things (you can see a few commented out onChanges) and tried a few different ways with layering the setState (tried to set the disable/enable first before the value, tried using 2 buttons (current above) to show/hide instead of setting the "disable" property to T/F). None of the solutions so far updated immediately, I've had to wait until I click out, giving me the appropriate results. Does anyone know what my issue is?
I appreciate your time.
you don't need to set the state after setting the value.
since you don't update your oldPermittedRoles and you have your permittedRoles value on your onChange event you also don't need to updated state value because you are going to update it together. One setstate is enough, you can achieve it like this:
handleRoles = (e: React.ChangeEvent<HTMLInputElement>) => {
const { target } = e;
this.setState((prevState) => ({
...prevState,
permittedRoles: target.value,
valueUpdated: prevState.oldPermittedRoles !== target.value ? true : false
}))
}
When you update permitted roles it will also update button state
I created a state for isWatched where if isWatched === true, then I want the button to say "Watched" and if it's false, then the button says "Watch" I want to be able to toggle on each button and conditionally render the name of button upon click. However, though the state is changing upon click, the name of the button is not. I believe it's because the button needs to be defined in the return statement to be able to update but I defined all my logic upon a setState condition in the handleSearch method so I'm not sure how to fix this without having to refactor everything. I also have an issue where I need to isolate each button because if I change the state, all the buttons get changed.
import React from 'react';
import movies from './movieData';
const movieTitles = movies.map(movie => movie.title);
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
searchQuery: '',
searchedMovies: [],
isMovieFound: null,
isWatched: false
}
}
handleQuery = e => {
this.setState({
searchQuery: e.target.value
});
}
handleWatched = () => {
this.setState(prevState => ({
isWatched: !prevState.isWatched
}));
}
handleSearch = () => {
if (movieTitles.filter(movie => movie.toLowerCase()
.includes(this.state.searchQuery.toLowerCase())).length === 0) {
this.setState({ isMovieFound: false, searchedMovies: [], searchQuery: '' });
} else {
this.setState({
searchedMovies: movieTitles.filter(movie =>
movie.toLowerCase().includes(this.state.searchQuery.toLowerCase()))
.map(movie => <li key={movie}>{movie}<button className="watchBtn" onClick={this.handleWatched}>{this.state.isWatched ? 'Watched' : 'Watch'}</button></li>),
isMovieFound: true,
searchQuery: ''
});
}
}
render() {
return (
<div>
<input
type="text"
placeholder="Search for a movie.."
name="searchQuery"
value={this.state.searchQuery}
onChange={this.handleQuery}
/>
<button
onClick={this.handleSearch}
>
Search
</button>
<br />
<ul>
{this.state.searchedMovies}
</ul>
{this.state.isMovieFound === false && <span>No Movie Found</span>}
</div>
);
}
}
export default SearchBar;
Yes. You are right. You have rendered searchedMovies on click of Search button. I dont understand why do u store UI elements in state. Thats a bad practice. Try to just store the filtered movies in your store and write rendering logic in render method itself like below:
handleSearch = () => {
if (movieTitles.filter(movie => movie.toLowerCase()
.includes(this.state.searchQuery.toLowerCase())).length === 0) {
this.setState({ isMovieFound: false, searchedMovies: [], searchQuery: '' });
} else {
this.setState({
searchedMovies: movieTitles.filter(movie => movie.toLowerCase().includes(this.state.searchQuery.toLowerCase())),
isMovieFound: true,
searchQuery: ''
});
}
and add below code in render function
<ul>
{this.state.searchedMovies.map(movie => <li key={movie}>{movie}<button className="watchBtn" onClick={this.handleWatched}>{this.state.isWatched ? 'Watched' : 'Watch'}</button></li>)}
</ul>
I think I found a react bug?
I have 2 functions to show my modal, first I have a modal state like this:
state = {
modal: {
requestPopUp: {
visible: false,
},
},
};
and 2 functions to hide/show it
// Helper Methods
handleOpenModal = name => {
const modal = { ...this.state.modal };
modal[name].visible = true;
this.setState({ modal });
};
handleCloseModal = name => {
const modal = { ...this.state.modal };
modal[name].visible = false;
this.setState({ modal });
};
handleOpenModal works fine, however, handleCloseModal does not, it runs but doesnt alter the state.
modal[name].visible = false; This line specifically does nothing, I logged (modal,name) after it and it stays true and not false, how can I fix this??
Edit: I call my functions here:
<div className="card request p-3" onClick={() => this.handleOpenModal("requestPopUp")}>
Which works fine
and here
<Modal show={modal.requestPopUp.visible} onHide={() => this.handleCloseModal("requestPopUp")}>
Which also calls the function properly but it's never set as false as per the function logic.
My react version is "react": "^16.12.0"
Try to avoid mutating the object props directly e.g (obj[prop] = value) since its antI-pattern ... Use destructering as my example below:
On a different note, no need to write the code twice, you can reuse the same function, and pass an extra param to define if u wanna close/open:
handleModalClick = (name, visible) => {
this.setState({
modal: {
...this.state.modal,
[name]: {...this.state.modal[name], visible }
}
})
}
I am trying to pass theExpert.email value to another child component whenever i click on send questions button.
This is the constructor of the Parent class
constructor() {
super();
this.state = {
textbox: '',
category: '',
exp: '',
hide: false
};
this.hideButton = this.hideButton.bind(this);
}
The follow function hides the button once it is clicked and saves the value of the theExpert.email in exp, (value is passed correctly since console.log prints it)
hideButton (value) {
this.setState({ hide: true });
this.setState({
exp: value
});
console.log(value)
}
And here is the button that once I click on it it passes the value to hideButton
<div>
{!this.state.hide ? (
<button onClick={() => this.hideButton(theExpert.email)}>Ask Me!</button>
) : null}
</div>
Now what i want to do is once i click on Send Questions button i get redirected to the child component Questions.js and pass the value of theExpert.email to that component
The button Send Questions:
<div>
<p>
{(this.state.hide) && (
<Link to="/questions">
<button
style={{ background: "green", color: "white" }}
>
Send Question
</button>
</Link>
)}
</p>
</div>
How can i retrieve value in the child class to create a post request using it
I think the issue us due to react being lazy. Basically you are just setting hide to true and re-rendering your component and all of its children but never setting exp the expected value.
So instead of
hideButton (value) {
this.setState({ hide: true });
this.setState({
exp: value
});
console.log(value)
}
try
hideButton (value) {
this.setState(prevState=>{
prevState= {
...prevState
hide: true ,
exp: value
}
return prevState
});
}
I have a json file that is an array that stores another field that contains another array that I want. I have an ajax request that stores the pitching field into a state array pitchers I have two buttons that when clicked will pass a value equal to the team_flag attribute in the json file.
<button className="btn" onClick={this.handleClick.bind(this, 'home')}>{homeTeamName}</button>
<button className="btn" onClick={this.handleClick.bind(this, 'away')}>{awayTeamName}</button>
And the method:
handleClick: function(teamFlag) {
// setState of pitchers to whichever team is clicked (home, away)
// this.setState({ pitchers: this.state.pitchers.teamFlag})???
console.log(teamFlag);
}
How do I set the pitchers state so that it will take the pitcher array that corresponds to the team_flag that was clicked? (i.e. If I click on homeTeam it will store the pitcher array that is under the "team_flag": "home") Below is the json file
"pitching":[
{
"pitcher":[
{"name": "Billy", "hand": "right"}
],
"team_flag":"away",
},
{
"pitcher":[
{"name": "Joe", "hand": "right"}
],
"team_flag":"home",
}
],
Assuming you have the entire "pitching" array in the state under the pitching key you could something like:
var pitchers = this.state.pitching.find(function(team) {
return team.team_flag === teamFlag;
});
this.setState({ pitchers: pitchers.pitcher });
If it is only for presentation purposes I wouldn't save the result in the state, instead I'd save the teamFlag and call the mentioned code in the render method.
React is smart enough to know that if teamFlag or the pitching array changes then it needs to re-render.
EDIT:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true
}
}
componentWillMount() {
// make your ajax request
// and when the request is finished
this.setState({ loading: true });
}
handleClick(ev, flag) {
this.setState({ flag: flag });
}
render() {
let homeTeamName = 'Home';
let awayTeamName = 'Away';
let selectedFlag = this.state.flag;
let pitchers = this.state.pitching.find(function(team) {
return team.team_flag === selectedFlag;
});
return (
<div>
{!this.state.loading ?
<div>
<button className="btn" onClick={this.handleClick.bind(this, 'home')}>{homeTeamName}</button>
<button className="btn" onClick={this.handleClick.bind(this, 'away')}>{awayTeamName}</button>
</div>:
null
}
// render using the pitchers array
</div>
);
}
};