I wanted to know whether my onAdd function in <NewItemButton> will get the latest value of text state.
import { useState } from "react";
import {
NewItemFormContainer,
NewItemInput,
NewItemButton
} from "./styles";
type NewItemFormProps = {
onAdd(text: string): void
}
const NewItemForm = (props: NewItemFormProps) => {
const [text, setText] = useState("");
return (
<NewItemFormContainer>
<NewItemInput
value={text}
onChange={(e) => setText(e.target.value)}
/>
<NewItemButton onClick={() => props.onAdd(text)}>
Create
</NewItemButton>
</NewItemFormContainer>
);
}
export default NewItemForm;
If it does not get the latest value, what other ways can you suggest me? One that comes to my mind to use Refs (forwardRef) and send it directly to the html input element and then call props.onAdd with the current value. But the thing is that I am using styled-components and my NewItemInput looks like this
export const NewItemInput = styled.input`
border-radius: 3px;
border: none;
box-shadow: #091e4240 0px 1px 0px 0px;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
width: 100%;
background: #484747;
color: #f1f1f1;
`
So how will do that?
I tried to create a component 'Card' and use it like a container.
the code work well without 'card'.
when I tried to add it the 'Card.css' and expense-item in ExpenseItem.css doesn't work
this is 'ExpenseItem.js':
import Card from './Card';
import ExpenseDate from './ExpenseDate';
import './ExpenseItem.css';
function ExpenseItem(props) {
return (
<Card className='expense-item' >
<ExpenseDate date={props.date} />
<div className='expense-item__description'>
<h2 >{props.title}</h2>
<div className='expense-item__price'>{props.amount} Da </div>
</div>
</Card>
);
}
export default ExpenseItem;
this is 'Card.js':
import './Card.css';
function Card(props){
const classes ='card'+props.className;
return <div className={classes}> {props.children} </div>;
}
export default Card;
and this is 'Card.css':
.card {
border-radius: 12px;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}
and finally this is 'ExpenseItem.css':
.expense-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
margin: 1rem 0;
background-color: #ff0000;
}
You need a space between card and the class name in the classes variable. Because there is no space, the className attribute in the card component is just cardexpense-item.
This is how it should look:
const classes = 'card '+props.className;
By the way, you can also use ES6 template literals feature (it's up to you but I prefer them more):
const classes = `card ${props.className}`;
I created a component "SliderBox" which returns a div with a Material UI Slider, which implements createMuiTheme, and an input component.
The component "SliderBox" applies an onHover method to the parent div which changes the state of the component color. Basically, I turn colorA from red to blue and colorB from blue to red. Unfortunately, when this function is called on one instance of "SliderBox" it applies the color change to every "Slider". The color change is managed internally and is not affected outside of the "SliderBox" component.
Picture A is a view of three "SliderBox" components in their default state.
Picture B is a view of the three "SliderBox" components while the topmost component has the cursor hovering over it. Note that the color change seems to be applied to all three "Slider" components but not their containing component "SliderBox"
Attempted Solutions
I have tried to add unique key values to both the "SliderBox" components which showed no change in behavior.
I have tried to add a unique key value using a title and Math.random() to the child "Slider" component with no changes seen.
I have tried to pass a unique key value into every "SliderBox" component labeled "sliderKey" and then I added the key as "props.sliderKey" which did not show any changes in the erroneous behavior.
I have tried to separate the "Slider" component from the "SliderBox" component so that it returns a "CustomSlider" component that is implemented inside of "SliderBox" instead of "Slider" some change is noticed such as flickering color value changes but all Slider components still share the same values.
I have even added a base "Slider" component to my top tier parent component which implements "SliderBox" and the styles applied to the child of the "SliderBox" component is also applied to the "Slider" component in the parent component of the "SliderBox" component.
Assumptions
Based on my testing, I have came to the assumption that the issue is with the "createMuiTheme" function that is implemented in the "SliderBox" component. I cannot change the "createMuiTheme" as it has a very unique property in the "overrides:MuiSlider:root:'& .MuiSlider-valueLabel':'& *':" backgroundColor and color which allows me to alter the text color of the thumb as seen in pictures A and B. The text color of the thumb is imperative to my website. I cannot have a "Slider" component with a thumb that has white text.
My other assumption is with the component that is returned from the "import { Slider } from '#material-ui/core';" import. One of my beliefs is that "#material-ui/core" is not returning a unique component or they may not be applying unique keys to their components which is causing the color bleed when implementing multiple "Slider" components.
My Personal Shortcomings
I believe that I may be able to correct this erroneous behavior if I implement my Slider inside of a JavaScript class but I have never used "extends component" so I will hope that another user has encountered and solved this very issue before.
Not quite the material UI answer that I wanted ...but I've had a lot of negative experience wiht using Material Ui components so I went and made my own form while waiting in vain for an answer to my question.
My version is understandable and fully customizable ...I even implement css variables which most posts say do not exist.
From now on, I'll post my solid code so anyone can copy and paste and avoid the tyrannical material UI and their lack of customizability.
Note to the code warriors
This is my code for me. If you don't like it, my naming conventions, hook use etc then you are more than free to take it and write your own version. But I would not appreciate your opinions about this. I am sharing my code for others who are suffering with input ranges
Example Images of my component::
SliderBar.jsx::
import React, {useState, useEffect} from "react";
import "./SliderBar.css";
export default function SliderBar(props){
const backgroundColor = props.backgroundColor?props.backgroundColor:"#000";
const textColor = props.textColor?props.textColor:"#fff";
const hoverColor = props.hoverColor?props.hoverColor:"#888";
const initialState = props.value?props.value:0;
const min = props.min?props.min:0;
const max = props.max?props.max:20;
const bubbleClassName = props.bubbleClassName?props.bubbleClassName:("SliderBar-bubble-"+(Math.floor(Math.random()*100000001)));
const bubbleSize = props.bubbleSize?props.bubbleSize:"30px";
const cssValueChanger = {};
const [sliderValue, sliderUpdate] = useState(initialState);
const [textColorValue, textColorUpdate] = useState(textColor);
const [oppositeColorValue, oppositeColorUpdate] = useState(backgroundColor);
const [thumbColorValue, thumbColorUpdate] = useState(hoverColor)
cssValueChanger["--color"] = props.hoverColor?props.hoverColor:thumbColorValue;
useEffect(()=>{
const element = document.querySelector(`.${bubbleClassName}`);
if(element){
const newVal = Number(((props.value?props.value:sliderValue - min) * 100) / (max - min));
element.innerHTML = props.value?props.value:sliderValue;
// Sorta magic numbers based on size of the native UI thumb
element.style.left = `calc(${newVal}% + (${8 - newVal * 0.15}px))`;
}
});
function onChange(event){
sliderUpdate(event.target.value);
if(props.onChange){
props.onChange(event.target.value);
}
}
function onMouseOver(){
if(!props.over){
textColorUpdate(hoverColor);
oppositeColorUpdate(textColor);
thumbColorUpdate(textColor);
}
}
function onMouseOut(){
if(!props.out){
textColorUpdate(textColor);
oppositeColorUpdate(backgroundColor);
thumbColorUpdate(hoverColor);
}
}
return(<div style={props.outerStyle}>
<div onMouseOut={onMouseOut} onMouseOver={onMouseOver} style={{width:"200px", margin:"auto", position:"relative"}}>
<input
type="range"
className="range"
min={min}
max={max}
style={{
...cssValueChanger,
width:"100%",
background: props.textColor?props.textColor:textColorValue,
color: props.backgroundColor?props.backgroundColor:oppositeColorValue,
}}
value={props.value?props.value:sliderValue}
onChange={onChange}
/>
<output
value={props.value?props.value:sliderValue}
className={bubbleClassName}
style={{
background: props.textColor?props.textColor:textColorValue,
color: props.backgroundColor?props.backgroundColor:oppositeColorValue,
position: "absolute",
borderRadius: "100%",
left: "50%",
transform: "translateX(-50%)",
top:"-30px",
lineHeight:bubbleSize,
width:bubbleSize,
height:bubbleSize,
}}
/>
</div>
</div>);
}
SliderBar.css::
input[type="range"] {
-webkit-appearance: none;
-moz-appearance: none;
width: 300px;
height: 5px;
padding: 0;
border-radius: 2px;
outline: none;
cursor: pointer;
}
/*Chrome thumb*/
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
-moz-appearance: none;
-webkit-border-radius: 5px;
/*16x16px adjusted to be same as 14x14px on moz*/
height: 16px;
width: 16px;
border-radius: 5px;
background: var(--color);
border: 1px solid var(--color);
}
/*Mozilla thumb*/
input[type="range"]::-moz-range-thumb {
-webkit-appearance: none;
-moz-appearance: none;
-moz-border-radius: 5px;
height: 14px;
width: 14px;
border-radius: 5px;
background: var(--color);
border: 1px solid var(--color);
}
/*IE & Edge input*/
input[type=range]::-ms-track {
width: 300px;
height: 6px;
/*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
background: transparent;
/*leave room for the larger thumb to overflow with a transparent border */
border-color: transparent;
border-width: 2px 0;
/*remove default tick marks*/
color: transparent;
}
/*IE & Edge thumb*/
input[type=range]::-ms-thumb {
height: 14px;
width: 14px;
border-radius: 5px;
background: #e7e7e7;
border: 1px solid #c5c5c5;
}
/*IE & Edge left side*/
input[type=range]::-ms-fill-lower {
background: #919e4b;
border-radius: 2px;
}
/*IE & Edge right side*/
input[type=range]::-ms-fill-upper {
background: #c5c5c5;
border-radius: 2px;
}
/*IE disable tooltip*/
input[type=range]::-ms-tooltip {
display: none;
}
input[type="text"] {
border: none;
}
SliderBox.jsx::
import React, {useState} from "react";
import SliderBar from "./slider/SliderBar";
import "./SliderBox.css";
/***
<SliderBox
title={"TextValue"}
value={"TextValue"}
onChange={function onChange()}
textColor={"#ffffffff"}
backgroundColor={"#00000000"}
hoverColor={"#ff808080"}
min={0}
max={20}
/>
*/
export default function SliderBox(props){
const initialState = props.value?props.value:0;
const backgroundColor = props.backgroundColor?props.backgroundColor:"#000";
const textColor = props.textColor?props.textColor:"#fff";
const hoverColor = props.hoverColor?props.hoverColor:"#888";
const min = props.min?props.min:0;
const max = props.max?props.max:20;
const [sliderValue, sliderUpdate] = useState(initialState);
const [textColorValue, textColorUpdate] = useState(textColor);
const [oppositeColorValue, oppositeColorUpdate] = useState(backgroundColor);
function sliderChange(event){
sliderUpdate(event);
if(props.onChange!==undefined){
props.onChange(event);
}
}
function inputChange(event){
let value = event.target.value;
value = value.replace(/^0+/, '')
if(value===""){
value="0";
}
else if(!/^\d+$/.test(value)){
return;
}
if(value > max){
value = max;
}else if(value < min){
value = min;
}
sliderUpdate(value);
if(props.onChange!==undefined){
props.onChange(value);
}
}
//selected Or Blur
function onFocusOrMouseOut(){
textColorUpdate(textColor);
oppositeColorUpdate(backgroundColor);
}
function onBlur(){
if(sliderValue.length<1){
sliderUpdate(0);
}
}
//hover
function onMouseOver(){
textColorUpdate(hoverColor);
oppositeColorUpdate(textColor);
}
return(<div
onMouseOver={onMouseOver}
onMouseOut={onFocusOrMouseOut}
onFocus={onFocusOrMouseOut}
onBlur={onBlur}
style={{
width:"200px",
margin:"auto",
display:"table",
height:"50px",
padding:"35px 20px 15px",
border:"1px solid "+textColorValue,
borderRadius:"5px",
position:"relative"
}}
>
<p style={{
position: "absolute",
top: "-20px",
margin: "0px",
backgroundColor: backgroundColor,
padding: "5px",
borderRadius: "5px"
}}>{props.title}</p>
<SliderBar
outerStyle={{padding:"20px", display:"table-cell"}}
value={sliderValue}
min={min}
max={max}
onChange={sliderChange}
textColor={textColorValue}
hoverColor={hoverColor}
backgroundColor={oppositeColorValue}
/>
<div
style={{
display:"table-cell",
verticalAlign:"middle",
width:"50px"
}}
>
<input
value={sliderValue}
onChange={inputChange}
style={{
outline:"none",
color:textColor,
textAlign:"center",
backgroundColor: backgroundColor,
border: "1px solid "+textColorValue,
borderRadius:"5px",
width:"30px",
height:"30px",
"&:hover :selected":{
outline:"none",
color:textColorValue,
backgroundColor: backgroundColor,
border: "1px solid "+textColorValue,
}
}}
/>
</div>
</div>);
}
SliderBox.css
.PrivateValueLabel-label-900{
color: "orange";
}
.MuiSlider-root .MuiSlider-valueLabel *{
color: green;
background-color: red;
}
[class^="PrivateValueLabel-label-"]{
color: red;
}
I'm trying to pass some props when I use my custom component with Styled-component.
This is the container I want to focus on, and I want to change the flex-direction via props:
const InputInnerContainer = styled.View`
width: 100%;
height: 42px;
padding-horizontal: 5px;
flex-direction: ${props => props.right ? "row-reverse": "row"};
align-items: center;
justify-content: space-between;
border-radius: 4px;
border-width: 1px;
background-color: ${inputBackgroundColor};
border: 2px solid ${inputBorderColor};
`;
This is my custom component "Input":
const Input = ({ onChangeText, icon, value, label,iconPosition}) => {
return (
<InputContainer>
{label && <LabelText>{label}</LabelText>}
<InputInnerContainer {...iconPosition}>
<View>{icon && <LabelText>{icon}</LabelText>}</View>
<InputField onChangeText={onChangeText} value={value} />
</InputInnerContainer>
</InputContainer>
);
};
And this is where I call the custom component:
<Input
label="Password"
onChangeText={(text) => onChangeText(text)}
value={value}
icon="HIDE"
iconPosition="right"
/>
The props I want to pass is "iconPosition". But I'm not sure how to pass it into the Styled-component. I'm still relatively new to Styled-component, so any ideas are welcome.
Try without destructuring it. Not this way:
<InputInnerContainer {...iconPosition}/>
But this way:
<InputInnerContainer iconPosition={iconPosition}/>
You also need to update your styled component:
const InputInnerContainer = styled.View`
...
flex-direction: ${props => props.iconPosition === "right" ? "row-reverse" : "row"}
...
`
I have a simple snapshot test with RTL and Jest, any other prop I use beside type and placeholder I don't see it in the resulting snapshot. This is my input component: (I'm using styled component)
const TextField = ({ className, placeholder, onChange, type }) => {
return (
<Styled>
<Styled.input
type={type}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
</Styled>
);
};
This is my test:
it('shall render correctly', () => {
const { asFragment } = render(
<ThemeProvider theme={theme}>
<TextField placeholder="Ramdom text" type="search" onChange={jest.fn()} />
</ThemeProvider>,
);
expect(asFragment()).toMatchSnapshot();
});
The resulting snapshot:
exports[`TextField component shall render correctly 1`] = `
<DocumentFragment>
.c0 {
position: relative;
}
.c1 {
width: 100%;
outline: none;
border: solid 0px;
border-radius: 10em;
background: #f4f5f8;
padding: 8px 8px 8px NaNpx;
font: 400 1.6rem/normal 'Open Sans','Helvetica','Arial',sans-serif;
-webkit-letter-spacing: normal;
-moz-letter-spacing: normal;
-ms-letter-spacing: normal;
letter-spacing: normal;
text-transform: none;
}
<label
class="c0"
>
<input
class="c1"
placeholder="Ramdom text"
type="search"
/>
</label>
</DocumentFragment>
`;
As you can see there's no onChange prop in the input props.
onChange in your JSX is event handler provided by React and it's not shown in the actual DOM, which RTL renders.