I want to change the state of a Nav.Toggle in react-bootstrap. c
Currently, I have got it to open when clicked but I would like it to close i.e. this.setState({ expanded: false}) when clicked if the current state is "expanded".
My onClick handler looks as such:
onClick={() => (this.state.expanded ? false : this.setState({ expanded: "expanded" }))}
How do I make it say else if this.state.expanded ? "expanded" : this.setState({ expanded: false })?
I assume I should move this logic above the render method so bonus points if you can show me how to do it under this line as well:
constructor(props) {
super(props);
this.state = {
expanded: false
};
}
handleNavToggle => ???
Thanks!
setState has a second form you can use if you want the state to depend on the previous state.
I suppose this is something like what you're after:
constructor(props) {
super(props);
this.state = {
expanded: false
};
}
toggleExpanded = () => {
this.setState((prevState) => ({
expanded: !prevState.expanded ? 'expanded' : false
}));
};
render() {
return (
<Nav.Toggle
onClick={this.toggleExpanded}
/>
);
}
However, I'd recommend you to stick to one type. Either boolean or string. With a boolean, the setState callback would simply be as (prevState) => ({ expanded: !prevState.expanded })
So if it's a boolean initially, you can just put exclamation mark in front of it.
onClick={() => this.setState(() => ({expanded: !this.state.expanded}))}
this way it will become true or false back and forth.
Edit: Don't forget to return as an object so you need open to normal paranthesis before curly braces.
Related
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 am trying to build a chat application with the functionality of input field which can be used as filter for chat_groups array which is in the state as chat_groups. Here is how my code looks:
constructor(props) {
super(props);
this.state = {
currUserId: "--id--",
chats: [],
chat_groups: [],
users: [],
};
}
.
.
.
<input
className="chat-group__search__input"
placeholder="Search for group..."
onChange={(ev) => {
console.log(ev.currentTarget.value);
var thatState = this.state;
thatState.chat_groups = thatState.chat_groups.map(
(gp) => {
gp["visible"] = gp.group_name
.toLowerCase()
.includes(ev.currentTarget.value);
return gp;
}
);
// getting correct state in thatState variable
this.setState(thatState);
}}
/>
// getting old state in setState callback and componentDidUpdate lifecycle
The weird problem is I am getting the correct value in thatState variable before setting state. But after setState function is called, if I try to check the state in setState callback or componentDidUpdate lifecycle, I am getting the old state only.
I tried that for keydown and change events also. So, seems to be less of an issue of event as well.
I would like to know if some issue in the code is evident or there is something that I can do to debug the issue.
Edit: After changes, my current onChange looks as below, but the issue is still there; the setState function does not seem to change the state as I can see only the old state in componentDidUpdate lifecycle and setState callback.
onChange={(ev) => {
console.log(ev.currentTarget.value);
let chat_groups = this.state.chat_groups.map((gp) => ({
...gp,
visible: gp.group_name
.toLowerCase()
.includes(ev.currentTarget.value),
}));
console.log(
"Before",
chat_groups.map((gp) => gp.visible)
);
this.setState({ chat_groups: chat_groups });
}}
The problem is that you are mutating the state.
When you do var thatState = this.state; the reference is still the same for both the objects. So automatically when you update thatState.chat_groups you are updating/mutating state as well.
Change your onChange method to like below
onChange = ev => {
console.log(ev.currentTarget.value);
let { chat_groups } = this.state;
chat_groups = chat_groups.map(gp => ({
...gp,
visible: gp.group_name.toLowerCase().includes(ev.currentTarget.value)
}));
this.setState(state => ({
...state,
chat_groups
}));
};
//Other code
//....
//....
<input
className="chat-group__search__input"
placeholder="Search for group..."
onChange={this.onChange} />
I suspect there's one problem while checking the group_name with the input value i.e., you are converting the group_name to lower case using gp.group_name.toLowerCase() but the input value you are not converting to lower case. This could be one issue why the visible attribute value is not getting updated. So in the below snippet I have converted the input value also to lower case while comparing.
Here, below is a runnable snippet with your requirement. Doing console.log in the setState's callback function and the state is getting updated.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currUserId: "--id--",
chats: [],
chat_groups: [{
group_name: "Group One"
}, {
group_name: "Group Two"
}],
users: []
}
}
onChange = ev => {
console.log(ev.currentTarget.value);
let {
chat_groups
} = this.state;
chat_groups = chat_groups.map(gp => ({
...gp,
visible: gp.group_name.toLowerCase().includes(ev.currentTarget.value.toLowerCase())
}));
this.setState(state => ({
...state,
chat_groups
}), () => { console.log(this.state.chat_groups); });
};
render() {
return <input
placeholder="Search for group..."
onChange={this.onChange} />
}
}
ReactDOM.render(<App />, document.getElementById("react"));
.as-console-wrapper {
max-height: 100% !important;
}
<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="react"></div>
No, don't do this var thatState = this.state it's just an object it will easily get mutate and you will not get the update as for react state change never occured.
instead do this var { chat_groups } = this.state and then use it further and inlast set the state this.setState({ chat_groups: chat_groups }) also try to avoid the mutation as much as possible means copy the values of chat_groups also
Seems like you are trying to manipulate state directly which is a big no in React.
onChange={(ev) => {
this.setState({
chat_groups: this.state.chat_groups.map((gp) => {
gp["visible"] = gp.group_name
.toLowerCase()
.includes(ev.currentTarget.value);
return gp;
})
});
}}
State should only be updated using the setState method.
You are mutating the state directly in your code above - this isn't recommended. You would get unexpected results and it's not predictable.
This is how you should do it - create a new updated object and pass it to the setState.
onChange={(ev) => {
console.log(ev.currentTarget.value);
const updatedChatGroups = this.state.chat_groups.map((gp) => {
const visible = gp.group_name.toLowerCase().includes(ev.currentTarget.value);
return {
...gp,
visible,
};
});
// Update the modified object using this.setState().
this.setState({ chat_groups: updatedChatGroups });
}}
Read More
There are two types of switch status in my project. One is default and the other is generated from API.When the item is changed toggle switch on/off won't work.
constructor(props) {
super(props);
this.state = {
switch_status: [true, false],
items: [{title:toyota}, {title:bmw}]
}
}
There is a function, Which get data from API and set into items:
changeItems = () => {
this.setState({ items: [{title:toyota, switch_status: true},
{title:porche, switch_status: true},
{title:bmw, switch_status: false}]
});
}
on/off not working, When Items changed:
//Switch on/off function
handleChange = (event, id) => {
const isChecked = event;
this.setState(
({switch_status}) => ({
switch_status: {
...switch_status,
[id]: isChecked,
}
})
);
}
//Loop Items
this.state.items.map((item, index) => (
<Switch
className="custom-switch custom-switch-primary"
checked={this.state.switch_status[index]}
id={index}
onChange={event => handleChange(event, index)}
/>
))
There is nothing wrong in your state handling logic really but your componentDidUpdate() is getting called infinite times because the check inside is not working and it overwrites your toggle state even when you don't need to.
Change you componentDidUpdate() to:
componentDidUpdate(previousProps, previousState) {
if (
JSON.stringify(previousProps.mediaTypes.items) !==
JSON.stringify(this.props.mediaTypes.items)
) {
this.dataListRender();
this.setState({
customMediaTypesItems: this.props.mediaTypes.items.custom_media_types
});
}
}
First of all; you are passing a new reference to a component as prop on every render and that causes needless DOM updates
Second is that you initialise the state with a different structure than when you are setting state. I assume that
{
items: [
{ title: toyota, switch_status: true },
{ title: porche, switch_status: true },
{ title: bmw, switch_status: false }
];
}
Is your actual state because you use that to render. You can do the following:
const Switch = React.memo(
//use React.memo to create pure component
function Switch({ label, checked, toggle, id }) {
console.log("rendering:", label);
// prop={new reference} is not a problem here
// this won't re render if props didn't
// change because it's a pure component
// if any of the props change then this needs to re render
return (
<label>
{label}
<input
type="checkbox"
checked={checked}
onChange={() => toggle(id)}
/>
</label>
);
}
);
class App extends React.PureComponent {
state = {
items: [
{ title: "toyota", switch_status: true },
{ title: "porche", switch_status: true },
{ title: "bmw", switch_status: false }
]
};
toggle = _index =>
this.setState({
items: this.state.items.map((item, index) =>
_index === index // only toggle the item at this index
? { ...item, switch_status: !item.switch_status }
: item // do not change the item
)
});
render() {
//please note that using index is not a good idea if you
// change the order of the state.items, add or remove some item(s)
// if you plan to do that then give each item a unique id
return (
<div>
{this.state.items.map((item, index) => (
<Switch
label={item.title}
checked={item.switch_status}
toggle={this.toggle}
id={index}
key={index}
/>
))}
</div>
);
}
}
//render app
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I believe there is an issue while getting the checked state.
In your current implementation, you have written const isChecked = event; in the handleChange method which will always be true since the event object is always available.
It should be const isChecked = event.target.checked; for it to set the toggled checkbox state correctly.
I have one parent component that holds state of clicking : if file is clicked or not.
Files come from child component.
I know that I can use props and call function from parent, but doing that, I get this.props.handleStateChange is not a function
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
clickable: false
};
this.handleStateChange = this.handleStateChange.bind(this);
}
handleStateChange = (val) => {
this.setState({ clickable: val })
}
render() {
return (
<Child handleStateChange={this.handleStateChange} />
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
clickable: false
};
}
handleClick = () => {
this.state.clickable ? this.setState({ clickable: false }) :
this.setState({ clickable: true });
this.props.handleStateChange(this.state.clickable)
}
render() {
return (
<div className={this.state.clickable ? 'clickable' : null}
>
<img className="item" src={file} alt="file" onClick=
{this.handleClick} />
</div>
);
}
}
Any ideas what am I missing there?
Here everything is working fine, no errors.
If the code you add in your question isn't the real code you are working with, maybe try checking for typos.
Probably you are passing the prop with the wrong/different name.
Some tips that aren't related to the question
Instead of
this.state.clickable
? this.setState({ clickable: false })
: this.setState({ clickable: true });
You should do
this.setState(prevState => ({clickable: !prevState.clickable}))
setState is asynchronous, so using your newly set state immediately after isn't guaranteed to work. Instead, try this for handleClick:
handleClick = () => {
this.setState(prevState => {
this.props.handleStateChange({ !prevState.clickable });
return { clickable: !prevState.clickable };
})
}
That said, you're maintaining the same state in the parent and child. Probably better to set it in the parent (from the child) and pass it to the child as a prop.
Also also, since you're using an arrow function, you don't need to bind any of your functions in the constructor.
I want to dynamically add Components, after clicking the "add" button.
For that, I created an array that consists of all the components, and add them on click.
My problem is, that it only renders one component, even though it consists of several ones.
My code looks like this:
class QuestionBlock extends React.Component {
constructor(props) {
super(props);
this.state = {answersArray: []};
}
addPossibleAnswer() {
this.state.answersArray.push(
<PossibleAnswers id={this.state.answersArray.length + 1}/>
)
this.forceUpdate();
}
componentWillMount() {
this.state.answersArray.push(
<PossibleAnswers id={this.state.answersArray.length + 1}/>
)
}
render() {
console.log(this.state.answersArray) // Grows after adding componenets, but they are not rendered.
return (
<div>
{this.state.answersArray}
<AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
</div>
);
}
}
If you see what I did wrong, I'd be really glad if you could help me out!
Instead of mutating state directly and adding JSX to it, you can instead keep raw data in your state and derive the JSX from that in the render method instead.
Example
class QuestionBlock extends React.Component {
state = { answers: 1 };
addPossibleAnswer = () => {
this.setState(({ answers }) => ({ answers: answers + 1 }));
};
render() {
return (
<div>
{Array.from({ length: this.state.answers }, (_, index) => (
<PossibleAnswers key={index} id={index} />
))}
<AddPossibleAnswer addPossibleAnswer={this.addPossibleAnswer} />
</div>
);
}
}
You don't interact with state like you do. Never mutate the state field. You need to use this.setState:
this.setState(prevState => ({answersArray: prevState.answersArray.concat([
<PossibleAnswers id={prevState.answersArray.length + 1}])}));
Having said that, it is also strange that you store components in state. Usually, you would store data and create the components based on the data in the render method.
You are directly pushing elements to the array without setState so the component won't re-render
Also avoid using tthis.forceUpdate() as much as you can in your application because this is not recommended much
You need to change your code like below. The recommended approach for dealing with arrays in react is using previous state and push to an array
addPossibleAnswer() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
componentWillMount() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
Also keep in mind that componentWillMount life cycle method is deprecated in react 16. So move the code to componentDidMount instead
Here is the corrected code
class QuestionBlock extends React.Component {
constructor(props) {
super(props);
this.state = {answersArray: []};
}
addPossibleAnswer() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
componentDidMount() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
render() {
const { answersArray } = this.state;
return (
<div>
{answersArray}
<AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
</div>
);
}
}