I have multiple color inputs that are being displayed with unique colors. They are getting their value from a helper which is a nested Object. When I attempt to update the value, nothing occurs. I logged out the state and saw that all the colors are still an object. I attempted to get the individual color values and use that as the initial state with Object.values(), but there was no success in that.
As a test, I created a new input and state that held a random hex value and it updated without any issue. I'm assuming since I'm still getting back an object in my colorVal state, that I need to somehow get the values of the color object and convert it into a string?
I'm a bit lost and have been working on this for days now.
Component to display inputs
import React, { useState } from 'react'
import ColorPicker from './ColorPicker';
import { colorSelect, colorNames, colors } from '../Theme/colorSections'
import styled from 'styled-components';
function ColorPickerSection() {
const [colorVal, setColorVal] = useState(colors)
const onColorChange = (e) => {
setColorVal(e.target.value)
}
console.log(colorVal);
return (
<div>
{Object.keys(colorSelect).map(groupName => {
return (<div>
<GroupName>{groupName}</GroupName>
{Object.keys(colorSelect[groupName]).map(color => {
return (
<ColorPicker
key={color}
label={color}
value={colorVal[color]}
onChange={onColorChange}
/>
)
})}
</div>)
})}
</div>
)
}
Individual Color Swatch Component
import React from 'react';
import styled from 'styled-components'
function ColorPicker(props) {
return (
<ColorPickerContainer>
<p>{props.label}</p>
<ColorSwatch type="color" value={props.value} onChange={props.onColorChange} />
<HexInput
type="text"
value={props.value}
onChange={props.onColorChange}
/>
</ColorPickerContainer>
);
}
Color Helper
const colorSelect = {
'Line Highlights': {
highlightBackground: '#F7EBC6',
highlightAccent: '#F7D87C'
},
'Inline Code': {
inlineCodeColor: '#DB4C69',
inlineCodeBackground: '#F9F2F4'
},
'Code Blocks': {
blockBackground: '#F8F5EC',
baseColor: '#5C6E74',
selectedColor: '#b3d4fc'
},
'Tokens': {
commentColor: '#93A1A1',
punctuationColor: '#999999',
propertyColor: '#990055',
selectorColor: '#669900',
operatorColor: '#a67f59',
operatorBg: '#FFFFFF',
variableColor: '#ee9900',
functionColor: '#DD4A68',
keywordColor: '#0077aa'
}
}
const colorNames = []
const colors = {}
Object.keys(colorSelect).map(key => {
const group = colorSelect[key]
Object.keys(group).map(color => {
colorNames.push(color)
colors[color] = group[color]
})
})
export { colorSelect, colorNames, colors }
Assuming e.target.value is indeed the new color code in string.
The 'colors' is an object to begin with, so when you get the new color code, you need to update the object property accordingly
const onColorChange = (e, colorValKey) => {
setColorVal({
...colors,
[colorValKey]: e.target.value
})
}
return (
<div>
{Object.keys(colorSelect).map(groupName => {
return (<div>
<GroupName>{groupName}</GroupName>
{Object.keys(colorSelect[groupName]).map(color => {
return (
<ColorPicker
key={color}
label={color}
value={colorVal[color]}
onChange={(e) => onColorChange(e, color)}
/>
)
})}
</div>)
})}
</div>
)
Edit
// We passed onChange={onColorChange}, so props.onColorChange is undefined
<HexInput
type="text"
value={props.value}
onChange={props.onColorChange} />
// Should be
<HexInput
type="text"
value={props.value}
onChange={props.onChange} />
Related
I have my object perfectly updated but it does not render on screen (filteredData / handleOrder function). I tried using callback but also do not render.
Does anyone know what is the possible solution to render the object on the screen (filteredData)?
Here is the code which the object is updated:
setFilteredData(filteredData.sort((a, b) => (
(sort === 'ASC')
? a[column].localeCompare(b[column])
: b[column].localeCompare(a[column])
)));
Full code:
import React, { useContext, useState } from 'react';
import { Button, Form } from 'react-bootstrap';
import MyContext from '../../context/MyContext';
import './style.css';
function FilterByNumericValues() {
const {
filteredData,
order,
functions: { setFilteredData, setOrder },
} = useContext(MyContext);
function handleOrder(event) {
event.preventDefault();
const { column, sort } = order;
setFilteredData(filteredData.sort((a, b) => (
(sort === 'ASC')
? a[column].localeCompare(b[column])
: b[column].localeCompare(a[column])
)));
console.log(filteredData, 'filtered');
}
return (
<Form>
<Form.Group className="mb-3" controlId="order">
<Form.Check
data-testid="column-sort-input-asc"
type="radio"
label="Ascendent"
name="orderOption"
value="ASC"
onChange={ () => setOrder({ ...order, sort: 'ASC' }) }
/>
<Form.Check
data-testid="column-sort-input-desc"
type="radio"
label="Descendent"
name="orderOption"
value="DESC"
onChange={ () => setOrder({ ...order, sort: 'DESC' }) }
/>
<Form.Label>Order By</Form.Label>
<Form.Select
data-testid="column-sort"
name="orderFilter"
onChange={ ({ target }) => setOrder({ ...order, column: target.value }) }
value={ order.column }
>
{[...columnsIn, ...columnsOut].map(({ name }, index) => (
<option key={ index } value={ name }>{ name }</option>
))}
</Form.Select>
<Button
className="filter__order"
data-testid="column-sort-button"
onClick={ (event) => handleOrder(event) }
type="submit"
variant="warning"
>
Sort
</Button>
</Form.Group>
</Form>
);
}
export default FilterByNumericValues;
Arrays are reference values, sort() won't change the reference (doesn't create a new array), so React bails out the state update (search the Docs about "bailing out of state update").
You should pass a sorted copy of your original array to setFilteredData():
[... originalArray].sort()
Moreover, since you new state depends on the previous state, you'd better use the syntax:
setState((previousState) => {
// transform previousState
return newState;
})
I'm implementing a project where
I have a array of 44 object data
When I type a it returns 37 data immediately by onChange()
After type ad it return 20
The Problem is when I return back to a by backspace. It stay on 20.
How can I get back 37 data again.
Code of Root.jsx
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
}
getBadge = (e) => {
console.log(e)
const searched = this.state.data.filter(
item => {
if (e === '') {
return item
} else if (item.title.toLowerCase().includes(e.toLowerCase())) {
console.log(item)
return item
}
}
)
this.setState({ data:searched })
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search getBadge={this.getBadge} />
</>
<div className='container'>
<IconCard data={data} />
</div>
</>
)
}
}
export default Root
state data be like
state={
data:data
}
data
{
"title": "Academia",
"hex": "41454A"
},
{
"title": "Academia",
"hex": "41454A"
}
Code of Search.jsx
import React, { Component } from 'react';
class Search extends Component {
handleChange = (e) => {
this.props.getBadge(e.target.value)
}
render() {
// console.log(this.state.search)
return (
<div className='container pb-3'>
<div className="row">
<div className="col-md-3 align-self-center ">
<input type="text" className="form-control" placeholder="Search by brand..." onChange={this.handleChange} />
</div>
</div>
</div>
)
}
}
export default Search;
I understood your problem. You are mutating the original data whenever the search text is changing. Actually, you should not do that.
Instead,
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
searchText: '',
}
getBadge = (search) => {
console.log(search)
return this.state.data.filter(
item => {
if (item.title.toLowerCase().includes(search.toLowerCase())) {
console.log(item)
return true;
}
return false;
}
)
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search
value={this.state.searchText}
onChange={(value) => this.setState({searchText: value})} />
</>
<div className='container'>
<IconCard data={this.getBatchData(this.state.searchText)} />
</div>
</>
)
}
}
export default Root
Set searchText state in the component
Change the props of the <Search /> component
Update the state when the search updates
Update the getBatchData() as per above code.
Everytime you update the search text, the data will remains same, but the filter will return the results according to search text
In your function getBadge :
const searched = this.state.data.filter(...)
this.setState({ data:searched })
You are replacing the state with the object you found. So if the data object had 44 elements, after a search it will only have the filtered elements. All the other elements are gone.
You should consider filtering from a constant object instead of state.data
I am using react-number-format package inside of my TextField (material-ui). It is having strange behavior and not letting me put more than one symbol at once in the TextField. When I start typing after first click the Field is not focused anymore. I have used the same thing in other projects and it is working fine but here I cannot see from where the problem is coming. Here is the sandbox:
https://codesandbox.io/s/little-cherry-ubcjv?file=/src/App.js
import React,{useState} from 'react'
import { TextField} from "#material-ui/core";
import NumberFormat from "react-number-format";
export default function App() {
const [minAssets, setMinAssets] = useState();
const NumberFormatCustom = (props) => {
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: props.name,
value: values.value,
},
});
}}
thousandSeparator
isNumericString
prefix="$"
/>
);
};
return (
<div className="App">
<TextField
label="Min Assets"
value={minAssets}
onChange={(e) => setMinAssets(e.target.value)}
name="minAssets"
variant="outlined"
id="Minimum-Income-filter"
InputProps={{
inputComponent: NumberFormatCustom,
}}
/>
</div>
);
}
In your case you don't really need the onValueChange prop on the NumberFormat component since you already have an onChange on the TextField component that update the minAssets state.
So you can directly use this minAssets as the value of the prop value from NumberFormat like:
return (
<NumberFormat
{...other}
value={minAssets}
getInputRef={inputRef}
thousandSeparator
isNumericString
prefix="$"
/>
);
I'm still beginner to ReactJS and need to build a dynamic table for my work.
In that table, the user can add new lines and can also remove any existing lines.
The problem is, I don't know how to save the values that are typed into the new fields. My onChange function isn't working, I've done several tests, but I'm not able to save the entered values.
Here's my code I put into codesandbox.
Could you tell me what I'm doing wrong to save the entered values? Thank you in advance.
import React from "react";
import "./styles.css";
import List from "./List/List";
const App = () => {
const [data, setData] = React.useState([
[
{
label: "Property Name",
field: "propertyName",
value: ""
},
{
label: "Value",
field: "value",
value: ""
}
]
]);
const handleOnChange = (field) => (e) => {
setData((prev) => ({
...prev,
[field]: e.target.value
}));
};
const addRow = () => {
setData([
...data,
[
{
label: "Property Name",
field: "propertyName",
value: ""
},
{
label: "Value",
field: "value",
value: ""
}
]
]);
};
const removeRow = (index) => {
const _data = [...data];
_data.splice(index, 1);
setData(_data);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<List
data={data}
addRow={addRow}
removeRow={removeRow}
handleOnChange={handleOnChange}
/>
</div>
);
};
export default App;
import React from "react";
import AddCircleIcon from "#material-ui/icons/AddCircle";
import RemoveCircleIcon from "#material-ui/icons/RemoveCircle";
import TextField from "#material-ui/core/TextField";
import "./styles.scss";
const List = ({ data, handleOnChange, addRow, removeRow }) => {
return (
<div className="container">
{data.map((items, index) => (
<div key={index} className="content">
<div className="content-row">
{items.map((item, index) => (
<TextField
key={index}
label={item.label}
value={item.value}
onChange={handleOnChange(index)}
variant="outlined"
/>
))}
</div>
<div>
<AddCircleIcon onClick={addRow} />
{data.length > 1 && (
<RemoveCircleIcon onClick={() => removeRow(index)} />
)}
</div>
</div>
))}
</div>
);
};
export default List;
Good that you shared the code. There are several issues with your code. I have an updated code placed under this URL,
https://codesandbox.io/s/reverent-mclean-hfyzs
Below are the problems,
Your data structure is an array of arrays and your onchange event doesn't respect that.
You have no property available [name/id] to identify a textbox when you change the value.
I had to add a name property to each textbox and design it like a 2D array so any textbox will have a unique name.
I had to map through the data array and find the node where I have to update the value and set the new value as the new state when any textbox changes.
I have added a console.log while adding a row so you can see the current state.
I'm trying to create a function that renders an array of links and i want to create a text input and a button that adds value from input in the array. I got the links saved in the state in the object that looks like this:
sourceLinks: {
0: "https://www.w3schools.com/html/"
1: "https://www.apachefriends.org/docs/"
2: "https://docs.moodle.org/38/en/Windows_installation_using_XAMPP"
}
I've managed to render the links like this:
renderLinks() {
let sessionLinks = this.state.sessionLinks;
let links = [];
Object.values(sessionLinks).map((link) => {
links.push(<div className="column">
<span>
<InputPreview inputValue={link} classes="w-300" />
</span>
</div>)
})
return links;
}
InputPreview is the component i use for displaying links. I'm tryin to add a text input and a button bellow the rendered links that adds the value to the array, and an icon next to every link that removes it from an array. I'm trying to do it all in one function renderLinks() and then call it in render. I know i have to push and slice items from an array and update the state but i'm strugling cause i just started learning react. Please help :)
You can add and render links with below code.
import React from "react";
class ItemList extends React.Component {
state = {
links: ["item1"],
newItem: ""
};
submit(e, newLink) {
e.preventDefault();
let updatedLinks = this.state.links;
updatedLinks.push(newLink);
this.setState({ links: updatedLinks });
}
render() {
return (
<React.Fragment>
<ul>
{this.state.links?.map((link, i) => (
<li key={i}>
<p>{link}</p>
</li>
))}
</ul>
<form onSubmit={(e) => this.submit(e, this.state.newItem)}>
<input
type="text"
value={this.state.newItem}
onChange={(e) => this.setState({ newItem: e.target.value })}
/>
<button type="submit">ADD</button>
</form>
</React.Fragment>
);
}
}
export default ItemList;
Let me know for further clarificaton.
This is a example with functional components and hooks
import React, { useState } from 'react';
const sourceLinks = [
'https://www.w3schools.com/html/',
'https://www.apachefriends.org/docs/',
'https://docs.moodle.org/38/en/Windows_installation_using_XAMPP',
];
export const ListLinks = () => {
const [links, setLinks] = useState(sourceLinks);
const [newLink, setNewLink] = useState('');
const handleAdd = () => {
setLinks(links => [...links, newLink]);
};
const handleChangeNewLink = e => {
const { value } = e.target;
setNewLink(value);
};
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<input type='text' value={newLink} onChange={handleChangeNewLink} />
<button onClick={handleAdd}>Add</button>
</div>
<br />
{links.map((link, index) => (
<p key={index}>{link}</p>
))}
</div>
);
};
This is the result:
Lastly, read the documentation, managing the state is essential.