how to send id to refs(forwardrefs) to children components - javascript

I have 4 buttons and submit button, upon clicking on submit button, i need to add effects or button focus to 4 buttons based on pattern like [2,4,3,1], the buttons should animate like this pattern upon submit button click.
Here's what i have tried, I am unable to send id using refs. how do i send id to refs or how do i animate buttons based on refs or any other on how to add effects to children components.
constructor(props) {
super(props);
this.state = {
values: [
{ id: 1, color: "blue" },
{ id: 2, color: "red" },
{ id: 3, color: "green" },
{ id: 4, color: "yellow" }
]
};
this.myRef = React.createRef();
}
getvalue = (id, ref) => {
console.log(ref);
};
in render ` const { values } = this.state;`
<div className="col-md-12">
{values.map(value => (
<Card
ref={this.myRef}
key={value.id}
value={value}
id={value.id}
onbtnclick={() => this.getvalue(value.id)}
/>
))}
</div>
child component
const Card = React.forwardRef((props, ref) => {
return (
<button
ref={ref}
key={props.id}
className="btn newcard m-2 active"
aria-pressed="true"
onClick={() => this.props.onbtnclick(props.id, ref)}
style={{ background: `${props.value.color}` }}
/>
);
});
const ref = React.createRef;

i create a Sand Box for you here the Example https://codesandbox.io/s/xenodochial-flower-cl52b
u need to use useImperativeHandle of React Hooks plus Forward Refs
/// -------------
here is the refactor Sand Box you mentioned in Comments
https://codesandbox.io/s/amazing-germain-ww7yi

Related

How to Create 2D Grid by Repeating a Component certain times in a Loop in reactJS?

I am Working on Movie Ticket Booking Website. I Want to make a Grid Layout of 4x7. So What i Thought is i would Create a button Component and repeat it in Loop Several Times.
Pseudo Code:
for(var i=0;i<4;i++){
for(var j=0;j<7;j++){
button Component();
}
newline Component();
}
But this type of thing is not supported in reactjs. So What Can i Do for Implementation of above thing? Also When a button is clicked i want to change its color for that i have given ID to button Component so i can do it by DOM Manipulation but how to do that using UseState?
EDIT: I am done with array part but what about Color Change now? I Tried DOM but it returns NULL
CODE:
const items=[];
for(let i=1;i<=20;i++){
let style={
backgroundColor:"White"
};
items.push(<button className="btn btn-danger" onClick={()=>changeColor(i)} style={style} id={"button"+i}/>);
}
function changeColor(index) {
document.getElementById("index").style.backgroundColor="Green";
}
This Thing returns NULL i Do not know why
Using direct DOM manipulation is not recommended, you should instead leverage the reactive render cycle that React provides.
Here is a snippet which declares a Button component that handles its own internal state as an example. Mutiple Buttons are rendered inside a map() in the parent, and each button then controls its own active state.
const { useState } = React;
function App() {
return (
<div>
{[1,2,3].map(n =>(
<Button key={n} label={'Button' + n} />
))}
</div>
)
}
function Button({label}) {
const [active, setActive] = useState(false);
const handleClick = (e) => {
setActive(a => !a);
};
return (
<button
type='button'
className={active ? 'active' : ''}
onClick={handleClick}
>
{label}
</button>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
.active {
background-color: tomato;
}
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id='root'></div>
But generally buttons will be used to interact directly with the parent's state, in which case the click handler and state logic will be declared in the parent, with relevant properties being passed down to the children.
const { useState } = React;
function App() {
const [buttons, setButtons] = useState([
{id: 1, label: 'Button 1', active: false},
{id: 2, label: 'Button 2', active: false},
{id: 3, label: 'Button 3', active: false}]);
const handleClick = (buttonId) => {
setButtons(buttons => buttons.map(b =>
b.id === buttonId
? {...b, active: !b.active}
: b));
};
return (
<div>
{buttons.map(b =>(
<Button key={b.id} id={b.id} label={b.label} onClick={handleClick} active={b.active} />
))}
</div>
)
}
function Button({label, id, onClick, active}) {
return (
<button
type='button'
onClick={() => onClick(id)}
className={active ? 'active' : ''}
>
{label}
</button>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
.active {
background-color: tomato;
}
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id='root'></div>

React JS How to change font color in element other than clicked button onClick?

So I have managed to change the background color of a button using setState() within that button. However, I am trying to use that button to change the font color of list elements within the same component.
Using setState() only lets me change the element I am clicking. I've tried querySelecting the class of the other elements, but using left.setState() is not a valid function.
How can I change the CSS properties of an element using an onClick function of a button?
import React, { Component } from 'react';
import firebase from 'firebase';
import { firebaseConfig } from './connection';
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
let messageRef = firebase.database().ref('messages');
class LandingPage extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
message: '',
list: [],
font: "black",
color: "blue"
}
}
// onChange = () => {
// if (this.state.color == 'blue'){
// this.setState({ color: 'green' });
// }
// else {
// this.setState({ color: 'blue' });
// }
// }
onChange = () => {
var left = document.querySelectorAll(".left");
if (this.state.color === 'black'){
this.setState({ color: 'grey' });
}
else {
this.setState({ color: 'black' });
}
}
render() {
return <div className='container'>
{/* title */}
<div className='titleDiv'>
<h1>React Message App</h1>
</div>
{/* messages will be listed here */}
<div className='messagesDiv' id='messagesDivId'>
<ul>
{/* List array is mapped through*/}
{this.state.list.map(item => {
return (
<li className={(item.name === this.state.name ? 'right' : 'left')}
style={{ color: this.state.font }}
key={item.id}
id={item.id}>
{item.name}: {item.message}
</li>
)
})}
</ul>
</div>
{/*think, delete options*/}
<button className='button think' style={{ backgroundColor: this.state.color }} onClick={this.onChange}>Think...</button>
<button className='button delete'>Delete last message</button>
</div>
}
}
export default LandingPage;
It is the 'think' button which should be clicked to change the list elements with a 'left' or 'right' class name. Please advise...
You messed up some variable names and misunderstood how React works.
First, you can't query and HTML element and execute setState because this is a React function. This function is not accessible from within the HTML document.
Second, your first approach with changing a state variable with the button click and mapping this variable to the color of the list elements is correct, but you mixed up the names:
This is your onChangeMethod:
onChange = () => {
if (this.state.color == 'blue'){
this.setState({ color: 'green' });
}
else {
this.setState({ color: 'blue' });
}
}
Here you are mapping the state variable to the color property:
<li className={(item.name === this.state.name ? 'right' : 'left')}
style={{ color: this.state.font }}
key={item.id}
id={item.id}>
{item.name}: {item.message}
</li>
You are setting state.color in theonChange function, but you are referencing state.font in you list element, instead change style to the following:
style={{ color: this.state.color }}
You need to do the binding to the onChange method. You can do it in the constructor method like this:
constructor(props) {
super(props);
this.state = {
name: '',
message: '',
list: [],
font: "black",
color: "blue"
}
this.onChange = this.onChange.bind(this)
}
import React, { Component } from "react";
class LandingPage extends Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: "1",
message: "Hello World 1"
},
{
id: "2",
message: "Hello World 2"
},
{
id: "3",
message: "Hello World 3"
}
],
color: "red"
};
this.onChange = this.onChange.bind(this);
}
onChange = () => {
if (this.state.color == "red") {
this.setState({ color: "green" });
} else {
this.setState({ color: "red" });
}
};
render() {
return (
<div className="container">
<div className="titleDiv">
<h1>React Message App</h1>
</div>
<div className="messagesDiv" id="messagesDivId">
<ul>
{this.state.list.map(item => {
return (
<li
style={{ color: this.state.color }}
key={item.id}
id={item.id}
>
{item.message}
</li>
);
})}
</ul>
</div>
<button className="button think" onClick={this.onChange}>
Change Color
</button>
</div>
);
}
}
export default LandingPage;
Check whether this is what you want?
if you want to try inline..
<button className='button think' style={{ backgroundColor: this.state.color }} onClick={()=>{this.state.this.state.color == 'blue'?this.setState({ color: 'green' }):this.setState({ color: 'blue' })}}>Think...</button>

React - changing the background of a single span class not working

I am new to React so my apologies if the question, or the thing I am trying to achieve is just weird (and please do tell if there is a better / more logic way to do this).
I am using the List Fabric React component in my React application, which is based on the ListGridExample component which is found here:
https://developer.microsoft.com/en-us/fabric#/components/list
I have set it up but I can't seem to accomplish the following:
When a span class (which is actually an item) in the List component is clicked, I want to change it's background color, to do this I have followed the instructions in the following post:
https://forum.freecodecamp.org/t/react-js-i-need-a-button-color-to-change-onclick-but-cannot-determine-how-to-properly-set-and-change-state-for-that-component/45168
This is a fairly simple example but this changes all my grid cells / span classes to the color blue instead of only the clicked one. Is there a way I can make just the clicked span class change it's background?
The Initial state:
The state after clicking one span class (which is wrong):
Implementation code (ommitted some unecesary code):
class UrenBoekenGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
bgColor: 'red'
}
}
render() {
return (
<FocusZone>
<List
items={[
{
key: '#test1',
name: 'test1',
},
{
name: 'test2',
key: '#test2',
},
{
name: 'test3',
key: '#test3',
},
{
name: 'test4',
key: '#test4',
},
..... up to 32 items
]}
onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
changeColor(item){
this.setState({bgColor: 'blue'});
console.log('clicked item == ' + item.name)
}
_onRenderCell = (item, index) => {
return (
<div
className="ms-ListGridExample-tile"
data-is-focusable={true}
style={{
width: 100 / this._columnCount + '%',
height: this._rowHeight * 1.5,
float: 'left'
}}
>
<div className="ms-ListGridExample-sizer">
<div className="msListGridExample-padder">
{/* The span class with the click event: */}
<span className="ms-ListGridExample-label" onClick={this.changeColor.bind(this, item)} style={{backgroundColor:this.state.bgColor}}>{`item ${index}`}</span>
<span className="urenboeken-bottom"></span>
</div>
</div>
</div>
);
};
}
I now have attached the click event to the span class itself but I would think it is way more logic to have the click event on the item(s) (array) itself, however I could not find a way to achieve this either.
----UPDATE----
#peetya answer seems the way to go since #Mario Santini answer just updates a single cell, if another cell is clicked then the previous one returns back to normal and loses it's color.
So what I did is adding the items array to the state and adding the bgColor property to them:
this.state = {
items: [
{
key: '#test1',
name: 'test1',
bgColor: 'blue',
},
{
name: 'test2',
key: '#test2',
bgColor: 'blue',
},
{
name: 'test3',
key: '#test3',
bgColor: 'blue',
},
{
name: 'test4',
key: '#test4',
bgColor: 'blue',
},
],
}
Now in my List rendering I have set the items to the state items array and added the onClick event in the _onRenderCell function:
render() {
return (
<FocusZone>
<List
items={this.state.items}
getItemCountForPage={this._getItemCountForPage}
getPageHeight={this._getPageHeight}
renderedWindowsAhead={4}
onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
_onRenderCell = (item, index) => {
return (
<div
className="ms-ListGridExample-tile"
data-is-focusable={true}
style={{
width: 100 / this._columnCount + '%',
height: this._rowHeight * 1.5,
float: 'left'
}}
>
<div className="ms-ListGridExample-sizer">
<div className="msListGridExample-padder">
<span className="ms-ListGridExample-label"
onClick={this.onClick(item.name)}
style={{backgroundColor: item.bgColor}}
>
{`item ${index}`}
</span>
<span className="urenboeken-bottom"></span>
</div>
</div>
</div>
);
};
The problem is that I can't add the onClick event in the _onRenderCell function as this will give the following error:
I want to keep the Fabric List component as it also has functions for rendering / adjusting to screen size, removing the list component entirely and just replacing it with what #peetya suggested works:
render() {
<div>
{this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
</div>
}
But this will also remove the List component functionality with it's responsive functions.
So my last idea was to just replace the items of the List with the entire onClick div and removing the _onRenderCell function itself, but this makes the page blank (can't see the cells at all anymore..):
render() {
return (
<FocusZone>
<List
items={this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
getItemCountForPage={this._getItemCountForPage}
getPageHeight={this._getPageHeight}
renderedWindowsAhead={4}
// onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
I thought that perhaps the css ms-classes / div's should be in there as well because these have the height/width properties but adding them (exactly as in the _onRenderCell function) does not make any difference, the page is still blank.
The problem is that you are storing the background color in the state of the Grid and assign this state to every element of the grid, so if you update the state, it will affect every element. The best would be if you create a separate component for the Grid elements and store their own state inside there or if you want to use only one state then store the items array inside the state and add a new bgColor attribute for them so if you want to change the background color only for one item, you need to call the setEstate for the specific object of the items array.
Here is a small example (I did not tested it):
class UrenBoekenGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
key: '#test1',
name: 'test1',
bgColor: 'blue',
},
],
};
}
onClick(name) {
this.setState(prevState => ({
items: prevState.items.map(item => {
if (item.name === name) {
item.bgColor = 'red';
}
return item;
})
}))
}
render() {
<div>
{this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
</div>
}
}
Actually you are changing the color of all the span elements, as you set for each span the style to the state variable bgColor.
Insteas, you should save the clicked item, and decide the color based on that:
this.state = {
bgColor: 'red',
clickedColor: 'blue
}
In the constructor.
Then in the click handler:
changeColor(item){
this.setState({selected: item.name});
console.log('clicked item == ' + item.name)
}
So in the renderer (I just put the relevant part):
<span ... style={{backgroundColor: (item.name === this.state.selected ? this.state.clickedColor : this.state.bgColor)}}>{`item ${index}`}</span>

React list choosing option

I have an location app which can save name of locations.
I am trying to get each saved location a red border by clicking on it.
What it does is changing the border color of all the categories.
How can I apply that?
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
categories: [],
selectedCategories: [],
hidden: true,
checkboxState: true
};
}
toggle(e) {
this.setState({
checkboxState: !this.state.checkboxState
})
}
onChange = (event) => {
this.setState({ term: event.target.value });
}
addCategory = (event) => {
if (this.state.term === '') {
alert('Please name your category!')
} else {
event.preventDefault();
this.setState({
term: '',
categories: [...this.state.categories, this.state.term]
});
}
}
render() {
return (
<div className="categories">
<h1>Categories</h1>
<div className='actions'>
<button className="delete" onClick={this.deleteCategory}>Delete</button>
<button className="edit" onClick={this.editCategory}>Edit</button>
</div>
<p>To add new category, please enter category name</p>
<form className="App" onSubmit={this.addCategory}>
<input value={this.state.term} onChange={this.onChange} />
<button>Add</button>
</form>
{this.state.categories.map((category, index) =>
<button
key={index}
style={this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' }}
checked={this.state.isChecked}
onClick={this.toggle.bind(this)}>
{category}</button>
)}
</div >
);
}
}
I want to be able to control each selected category seperatly, to be able to delete and edit theme as well.
You can set the state based on index and retrieve the similar way,
Code:
{this.state.categories.map((category, index) =>
<button
key={index}
id={`checkboxState${index}`}
style={!this.state[`checkboxState${index}`] ?
{ borderColor: '' } : { border: '2px solid red' }}
checked={this.state.isChecked}
onClick={this.toggle}>
{category}</button>
)}
You can see how I am checking the state dynamically this.state[`checkboxState${index}`] and also I have assigned an id to it.
In toggle method:
toggle = (e) => {
const id = e.target.id;
this.setState({
[id]: !this.state[id]
})
}
FYI, this is a working code, you can see it
https://codesandbox.io/s/vy3r73jkrl
Let me know if this helps you :)
Here's a really bad example using react. I'd more than likely use this.props.children instead of just cramming them in there. This would allow it to be more dynamic. And instead of using state names we could then just use indexes. But you'll observe, that the parent container decides which child is red by passing a method to each child. On click, the child fires the method from the parent. How you implement it can vary in a million different ways, but the overall idea should work.
class ChildContainer extends React.Component
{
constructor(props)
{
super(props);
}
render() {
let color = this.props.backgroundColor;
return(
<section
className={'child'}
style={{backgroundColor: color}}
onClick={this.props.selectMe}
>
</section>
)
}
}
class Parent extends React.Component
{
constructor(props)
{
super(props)
this.state = {
first : 'Pink',
second : 'Pink',
third : 'Pink',
previous: null
}
this.updateChild = this.updateChild.bind(this);
}
updateChild(name)
{
let {state} = this;
let previous = state.previous;
if(previous)
{
state[previous] = 'Pink';
}
state[name] = 'Red';
state.previous = name;
this.setState(state);
}
render()
{
console.log(this)
return(
<section id={'parent'}>
<ChildContainer
selectMe={() => this.updateChild('first')}
backgroundColor = {this.state.first}
/>
<ChildContainer
selectMe={() => this.updateChild('second')}
backgroundColor = {this.state.second}
/>
<ChildContainer
selectMe={() => this.updateChild('third')}
backgroundColor = {this.state.third}
/>
</section>
)
}
}
class App extends React.Component
{
constructor(props)
{
super(props)
}
render()
{
return(
<section>
<Parent/>
</section>
)
}
}
React.render(<App />, document.getElementById('root'));
You need to track the state of every checkbox, possibly have an array with all currently checked checkboxes.
Then instead of this.state.checkboxState in this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' } you need to check if current category is in the currently checked categories array.
Hope this helps

Uncaught TypeError: Cannot read property 'icon' of null

i have a form for editing the tab. When a edit icon is clicked to edit that tab a form in dialog box appears where the input box has current data in it. But when i hit save without touching the icon field i get an error of Uncaught TypeError: Cannot read property 'icon' of null. If i did not touch the name field and only touch on icon field and hit save button then the tab gets edited. How can i make icon field work too like name field is working ? I mean if i want to only edit name, i can edit the name from name field and save without touching icon field which will save the tab name with edited name and current icon.
How can it be possible?
class EditForm extends Component {
render() {
const { tab } = this.props;
console.log('tab object is', this.props.tab);
const listOfIcon = _.map(this.props.fetchIcon.icons, (singleIcon) => ({
text: singleIcon.name,
id: singleIcon.id,
value: <MenuItem primaryText={singleIcon.name} />
}));
return (
<div>
<form
onSubmit={(e) => {
console.log('auto', e.target.auto);
e.preventDefault();
this.props.editTab(
tab.id,
e.target.text.value,
this.state.icon
);
this.props.closeTabIcon();
}
}
>
<div className="tab-name">
<TextField
hintText={tab.name}
name="text"
defaultValue={tab.name}
hintStyle={{ display: 'none' }}
floatingLabelStyle={{ color: '#1ab394' }}
floatingLabelFocusStyle={{ color: '#1db4c2' }}
underlineStyle={{ borderColor: '#1ab394' }}
/>
</div>
<div className="icon">
<AutoComplete
floatingLabelText={tab.icon}
name="auto"
filter={AutoComplete.noFilter}
openOnFocus
dataSource={listOfIcon}
textFieldStyle={{ borderColor: '#1ab394' }}
className="autocomplete"
onNewRequest={(e) => { this.setState({ icon: e.id }); }}
/>
</div>
<button className="btn">Save</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state);
return {
fetchIcon: state.fetchIcon,
tabs: state.tabs.tabs.map(tab => {
const icons = state.fetchIcon.icons.find(icon => Number(icon.id) === tab.icon);
return {
...tab,
icon: icons && icons.name
};
})
};
};
function mapDispatchToProps(dispatch) {
return bindActionCreators({
editTab,
closeTabIcon
}, dispatch);
}
The state of a componnet is intitated with the null. YOu can set the intital value of state in constrocutor of the class
class EditForm extends Component {
constructor(props) {
super(props)
this.state ={}
}
render() {
const { tab } = this.props;
console.log('tab object is', this.props.tab);
const listOfIcon = _.map(this.props.fetchIcon.icons, (singleIcon) => ({
text: singleIcon.name,
id: singleIcon.id,
value: <MenuItem primaryText={singleIcon.name} />
}));..........
initialize 'input box' with empty value from code behind.

Categories

Resources