ReactJS - Set unique state for dynamically generated items - javascript

I am fetching some data from the server to populate a list of items, and each item got a onClick event binded to the items id, that changes the UI to be disabled when clicked.
My problem is that the UI changes to disabled perfectly on the first click, but when I go on to click on the next item it resets the first on, so there is only one button disabled at a time. How do I make it so I can disable all the items I want, without resetting the previous ones?
Here is my component:
class Video extends Component {
constructor () {
super()
this.state = {
isDisabled: false
}
}
handleClick(frag, voted, event){
event.preventDefault()
this.setState({
isDisabled: {
[frag]: true
}
})
}
Snippet of what I return in the UI that changes the disabled button
<button onClick={this.handleClick.bind(this, frags.id, frags.voted)} disabled={this.state.isDisabled[frags.id]} className="rating-heart-2">
<i className="fa fa-heart" aria-hidden="true"></i>
</button>
I would really appreciate all tips!

It seems that when you call setState, you are overriding the previous value of the isDisabled.
You can do something like this:
handleClick(frag, voted, event){
event.preventDefault()
this.setState({
isDisabled: {
...this.state.isDisabled,
[frag]: true
}
})
}

The code you provided is a bit confusing because in the jsx you have this.state.hasRated to disable the button and in the handleClick you have a isDisabled object.
I followed the jsx approach and I add the frag id the hasRated object with the value true to disable a button each it is clicked.
You can run the following snippet to see the output of the code:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
frags: [],
hasRated: {}
};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
setTimeout(() => {
this.setState({
frags: [{
id: 1,
voted: false
}, {
id: 2,
voted: false
}, {
id: 3,
voted: false
}]
});
}, 500);
}
handleClick(id, voted) {
return (event) => {
this.setState({
hasRated: {
...this.state.hasRated,
[id]: true
}
});
}
}
render() {
const items = this.state.frags.map(frag => ( <
button
key={frag.id}
onClick = {
this.handleClick(frag.id, frag.voted)
}
disabled = {
this.state.hasRated[frag.id]
}
className = "rating-heart-2" >
Button <
/button>
));
return (
<div>
{items}
</div>
);
}
}
ReactDOM.render( < Example / > , document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="container"></div>

Related

Creating an element using .map function in Javascript

I'm struggling while creating an element that is passed by the .map function. Basically, I want my webpage to create a div element with some date in it when a button is clicked for that I'm using a .map function but it isn't working out.
const handleSubmit = (e) => {
e.preventDefault();
const data = {title:`${title}`, desc:`${desc}`, date:`${date}`};
data.map(userinfo =>{
return(<div>
<h1>{userinfo.title}</h1>
</div>)
})
console.log(data);
}
In reactJS, if we want to display our data in HTML webpage we usually do that in the render funciton.
We can use userInfo variable in the state object.
The userInfo data is hardcoded for demonstration purposes but you can also populate the userInfo variable either using API or in any other way you like.
Moreover, showUserInfo is another variable (initially false) that would render the data once it is set to true
this.state = {
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
showUserInfo: false
}
On a click event we can set showUserInfo to true using setState function.
more on setState function via this link ->
https://medium.com/#baphemot/understanding-reactjs-setstate-a4640451865b
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
In the render function, if showUserInfo is false then userInfo.map is never going to render unless showUserInfo is set to true which we do using a click listener that is associated with our function handleSubmit.
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
Overall the result looks a something like this.
export default class App extends React.Component {
constructor() {
super();
this.state = {
showUserInfo: false,
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
}
}
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
}

ReactJS How to use Refs on components rendered dinamically by another render function to focus elements?

I have a class component that Renders a list of elements and I need to focus them when an event occurs.
Here is an example code
class page extends React.Component {
state = {
items: [array of objects]
}
renderList = () => {
return this.state.items.map(i => <button>{i.somekey}</button>)
}
focusElement = (someitem) => {
//Focus some item rendered by renderList()
}
render(){
return(
<div>
{this.renderList()}
<button onClick={() => focusElement(thatElement)}>
</div>
)
}
}
I know that I need to use refs but I tried several ways to do that and I couldn't set those refs properly.
Can someone help me?
you should use the createRefmethod of each button that you would like to focus, also you have to pass this ref to the focusElement method that you have created:
const myList = [
{ id: 0, label: "label0" },
{ id: 1, label: "label1" },
{ id: 2, label: "label2" },
{ id: 3, label: "label3" },
{ id: 4, label: "label4" },
{ id: 5, label: "label5" }
];
export default class App extends React.Component {
state = {
items: myList,
//This is the list of refs that will help you pick any item that ou want to focus
myButtonsRef: myList.map(i => React.createRef(i.label))
};
// Here you create a ref for each button
renderList = () => {
return this.state.items.map(i => (
<button key={i.id} ref={this.state.myButtonsRef[i.id]}>
{i.label}
</button>
));
};
//Here you pass the ref as an argument and just focus it
focusElement = item => {
item.current.focus();
};
render() {
return (
<div>
{this.renderList()}
<button
onClick={() => {
//Here you are able to focus any item that you want based on the ref in the state
this.focusElement(this.state.myButtonsRef[0]);
}}
>
Focus the item 0
</button>
</div>
);
}
}
Here is a sandbox if you want to play with the code

Rendering the navigation list from an array based on different label on toggle mode

I have a header component where I need to render three buttons, so every three buttons have three props. One is the class name, click handler and text.
So out of three buttons, two buttons act as a toggle button, so based on the click the text should change.
See the below code:
class App extends Component(){
state = {
navigationList: [{
text: 'Signout',
onClickHandler: this.signoutHandler,
customClassName: 'buttonStyle'
}, {
text: this.state.isStudents ? 'Students' : 'Teachers',
onClickHandler: this.viewMode,
customClassName: 'buttonStyle'
}, {
text: this.state.activeWay ? 'Active On' : 'Active Hidden',
onClickHandler: this.activeWay,
customClassName: 'buttonStyle'
}]
}
signoutHandler = () => {
// some functionality
}
viewMode = () => {
this.setState({
isStudents: !this.state.isStudents
})
}
activeWay = () => {
this.setState({
activeWay: !this.state.activeWay
})
}
render(){
return (
<Header navigationList={this.state.navigationList}/>
)
}
}
const Header = ({navigationList}) => {
return (
<>
{navigationList && navigationList.map(({text, onClickHandler, customClassName}) => {
return(
<button
onClick={onClickHandler}
className={customClassName}
>
{text}
</button>
)
})}
</>
)
}
The other way is I can pass all the props one by one and instead of an array I can write three button elements render it, but I am thinking to have an array and render using a map.
So which method is better, the problem that I am facing is if use the array. map render
the approach I need to set the initial value as a variable outside and how can I set the state.
And I am getting the onClick method is undefined, is it because the function is not attached to the state navigation list array.
Update
I declared the functions above the state so it was able to call the function.
So in JS, before the state is declared in the memory the functions should be hoisted isn't.
class App extends React.Component {
constructor(props){
super();
this.state = {
isStudents:false,
activeWay:false,
}
}
createList(){
return [{
text: 'Signout',
onClickHandler: this.signoutHandler.bind(this),
customClassName: 'buttonStyle'
}, {
text: this.state.isStudents ? 'Students' : 'Teachers',
onClickHandler: this.viewMode.bind(this),
customClassName: 'buttonStyle'
}, {
text: this.state.activeWay ? 'Active On' : 'Active Hidden',
onClickHandler: this.activeWay.bind(this),
customClassName: 'buttonStyle'
}];
}
signoutHandler(){
}
viewMode(){
this.setState({
isStudents: !this.state.isStudents
})
}
activeWay(){
this.setState({
activeWay: !this.state.activeWay
})
}
render(){
return (
<div>
<div>ddd</div>
<Header navigationList={this.createList()} />
</div>
)
}
}
const Header = ({navigationList}) => {
console.log(navigationList);
return (
<div>
{navigationList && navigationList.map(({text, onClickHandler, customClassName}) => {
return(
<button
onClick={onClickHandler}
className={customClassName}
>
{text}
</button>
)
})}
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#app"))
https://jsfiddle.net/luk17/en9h1bpr/
Ok I will try to explain, If you see you are using function expressions in your class and as far as hoisting is concerned in JavaScript, functions expressions are not hoisted in JS only function declarations are hoisted, function expressions are treated as variables in JS.
Now for your case you don't have to shift your functions above the state, you can simply use constructor for initializing state as
constructor(props) {
super(props);
this.state = {
isStudents: false,
activeWay: false,
navigationList: [
{
text: "Signout",
onClickHandler: this.signoutHandler,
customClassName: "buttonStyle"
},
{
text: "Teachers",
onClickHandler: this.viewMode,
customClassName: "buttonStyle"
},
{
text: "Active Hidden",
onClickHandler: this.activeWay,
customClassName: "buttonStyle"
}
]
};
}
Now you will have your handlers available as it is
Sandbox with some modification just to show
EDIT:
You can have default text for buttons and change it when clicking,
Sandbox updated
Hope it helps

pass props to dynamic child component

I am trying to create a multiple step form. I have created the form where in each step, corresponding form is rendered dynamically. But I have no idea on how should I pass props to those component so that when returning back, the state gets preserved. I have created a sandbox of it in codesandbox and here it is
https://codesandbox.io/s/8xzm2mxol2
The rendering of form is done the following way
{this.props.steps[this.state.componentState].component}
If the component is rendered as below which is static way, the code would be something like this but I want the dynamic way
if(this.state.componentState === 1) {
<Form1 props={props} />
}
The code is
import React from 'react';
import './fullscreenForm.css';
class MultipleForm extends React.PureComponent {
constructor(props) {
super(props);
this.hidden = {
display: "none"
};
this.state = {
email: 'steve#apple.com',
fname: 'Steve',
lname: 'Jobs',
open: true,
step: 1,
showPreviousBtn: false,
showNextBtn: true,
componentState: 0,
navState: this.getNavStates(0, this.props.steps.length)
};
}
getNavStates(indx, length) {
let styles = [];
for (let i = 0; i < length; i++) {
if (i < indx) {
styles.push("done");
} else if (i === indx) {
styles.push("doing");
} else {
styles.push("todo");
}
}
return { current: indx, styles: styles };
}
checkNavState(currentStep) {
if (currentStep > 0 && currentStep < this.props.steps.length) {
this.setState({
showPreviousBtn: true,
showNextBtn: true
});
} else if (currentStep === 0) {
this.setState({
showPreviousBtn: false,
showNextBtn: true
});
} else {
this.setState({
showPreviousBtn: true,
showNextBtn: false
});
}
}
setNavState(next) {
this.setState({
navState: this.getNavStates(next, this.props.steps.length)
});
if (next < this.props.steps.length) {
this.setState({ componentState: next });
}
this.checkNavState(next);
}
next = () => {
this.setNavState(this.state.componentState + 1);
};
previous = () => {
if (this.state.componentState > 0) {
this.setNavState(this.state.componentState - 1);
}
};
render() {
return (
<div className="parent-container">
<div className="form-block">
{this.props.steps[this.state.componentState].component}
</div>
<div
className="actions"
style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'flex-end'}}
>
<button
style={this.state.showPreviousBtn ? {} : this.hidden}
className="btn-prev"
onClick={this.previous}
>
Back
</button>
<button
style={this.state.showNextBtn ? {} : this.hidden}
className="btn-next"
onClick={this.next}
>
Continue
</button>
</div>
</div>
);
}
}
export default MultipleForm;
I wanted it in best practice way.
You need to save the values of your form for all the step inputs. Now since on every step you are changing the form component, so you cannot put those values in corresponding form component. Therefore you have to put those values in the parent container (i.e. MultipleForm). Now as you are maintaining a state of values of your child component in parent container, therefore you will have to put some kind of mechanism so that whenever there is any change in input in child component, it should update the corresponding state in parent container. For that you can pass a change handler function to you child component. So your form component should look something like this
<div className="fullscreen-form">
<div className="custom-field">
<label className="custom-label fs-anim-upper" for="email">
What's your email address?
</label>
<input
className="fs-anim-lower"
id="email"
name="email"
type="email"
onChange={this.props.handleChange} // Whenver the input changes then call the parent container's handleChange function so that it can update it's state accordingly
value={this.props.value} // Updated value passed from parent container
placeholder="steve#apple.com"
/>
</div>
</div>
And you will render your form something like this
<Form1 handleChange={this.handleChange} value={this.state.email} />
Here's a working solution of your code:: Code

Go to previous state in reactjs

I'm building a component which proceeds according to the selections of the users. I have completed it successfully but facing some issues when trying to implement a back button to go back.
My code is like follows.
class ReportMainCat extends Component {
constructor(props) {
super(props);
this.state = {
postType: null,
}
this.changeType = this.changeType.bind(this);
this.report_next = this.report_next.bind(this);
};
report_next() {
if (this.state.postType == null) {
return <ReportFirst changeType={this.changeType}/>
}
else if (this.state.postType === 'sexual') {
return <ReportXContent changeType={this.changeType}/>
} else if (this.state.postType === 'selfharm') {
return <ReportThreatContent changeType={this.changeType}/>
}
}
changeType = (postType) => {
this.setState({postType})
this.setState({
showMainReportCats: false,
})
}
render() {
return (
<div className="top_of_overlay">
<div className="section_container text_align_center padding_10px">
<a className="">Report Category</a>
{this.report_next()}
</div>
</div>
)
}
}
I'm binding the postType value as follows.
class ReportXContent extends Component {
constructor(props) {
super(props);
this.state = {
postType: '',
}
};
textType(postType) {
this.props.changeType(postType);
}
render() {
return (
<div className="text_align_left">
<div>
<div className="width_100 margin_bottom10px">
<input type="checkbox" ref="nudity" onClick={this.textType.bind(this,'nudity')}/>
<a>Nudity or Pornography</a>
</div>
<div className="width_100 margin_bottom10px">
<input type="checkbox" ref="minor" onClick={this.textType.bind(this,'minor')}/>
<a>Includes Minors</a>
</div>
</div>
<ReportButtons/>
</div>
)
}
}
My back button
<div>
<button className="float_right margin_left5px" onClick={this.props.back_report}>Back</button>
</div>
So basically what i'm trying to do is this.
Ex: If the user selects postType as sexual it will return the ReportXContent component. How can i return to the first page when the user clicks the back button.
Thanks.
You could implement the back button click handler like this in the ReportMainCat component:
handleBackClick() {
this.setState({ postType: null });
}
, and that would show the ReportFirst view again.
If you don't want the first view, but the last view, simply change your changeType implementation to save lastPostType to state like this:
changeType = (postType) => {
this.setState({
lastPostType: this.state.postType,
postType,
showMainReportCats: false,
});
}
Edit
If you want full history of changes - let's say if you want to implement a full back button history - you can simply rename lastPostType to postTypeHistory and implement it like a stack (like the browser history is):
changeType = (postType) => {
this.setState({
postTypeHistory: [...this.state.postTypeHistory, this.state.postType],
postType,
showMainReportCats: false,
});
}
handleBackClick() {
const { postTypeHistory } = this.state;
const postType = postTypeHistory.pop();
this.setState({
postType,
postTypeHistory,
});
}

Categories