Using state variable in styled components - javascript

Im trying to make a button that changes state depending on the value of a property on an object.
here is the styled component
const Btn = styled.button`
border-radius: ${props => props.theme.radius};
padding:5px 10px;
background-color:${ sub.saved ? 'green' : 'red'}
&:hover{
cursor:pointer;
}
`
and here is the component that it is being used inside of
const DisplaySubs = ({ queryResults, setSavedFunction, saved }) => {
return (
<>
<Total>{queryResults.length} results found </Total>
<UL>
{queryResults.map((sub, index) => {
return (
<LI onClick={() => {
window.open(`https://www.reddit.com/${sub.title}`)
}
}>
<H4>{sub.title}</H4>
<P>{sub.description}</P>
<Btn onClick={(e) => {
e.stopPropagation()
sub.saved = !sub.saved
}}>Save</Btn>
</LI>
)
})}
</UL >
</>
)
}

Instead of creating a component like this, you can write a simple functional component like
const Btn = ( props ) => {
<div style={{
borderRadius: {props.theme.radius},
padding:"5px 10px",
backgroundColor: {props.sub.saved ? 'green' : 'red'}
}}>
{props.children}
</div>
}
You can call this in your Main Container by
<Btn props={props}>Button<Btn>

For a javascript project:
// you "add" the interface to button, so you can access this custom properties
export const Btn = styled.button`
padding: 5px 10px;
background-color: ${(props) => (props.isSaved ? "green" : "red")};
&:hover {
cursor: pointer;
}
`;
For a typescript project, you can use interface to set custom properties, like:
// Create an interface defining custom properties to button
interface BtnProps {
isSaved: boolean;
}
// you "add" the interface to button, so you can access this custom properties
export const Btn = styled.button<BtnProps>`
padding: 5px 10px;
background-color: ${(props) => (props.isSaved ? "green" : "red")};
&:hover {
cursor: pointer;
}
`;
and your button:
// set isSaved property to button component,
// so you will have access to it on styled-components.
<Btn isSaved={sub.saved}
onClick={(e) => {
e.stopPropagation()
sub.saved = !sub.saved
}}>Save</Btn>

Related

Styled-components, how to refer to a child component and still use the childs props?

I'm trying to figure out a clean way to allow Cells in my table have a specific background based on whether or not the parent Wrapper has the prop.printMode provided.
Here is a simplified breakdown:
<Wrapper printMode>
<Table>
<Row key={index}>
<Cell severity='Low'>x</Cell>
<Cell transparent>y</Cell>
<Cell>z</Cell>
</Row>
</Table>
</Wrapper>;
simplified Cell
interface ICell {
transparent?: boolean;
alignLeft?: boolean;
severity?: unknown;
}
export const Cell = styled('div')<ICell>`
background-color: ${(props) =>
(props.transparent === true ?
'transparent' :
props.theme._palette.main.tiles[100])};
${(props) =>
props.alignLeft &&
css`
display: flex;
align-content: flex-start;
padding-left: 0.5rem;
`}
${({ severity }) => {
if (severity === 'High') {
return css`
background-color: ${({ theme }) => theme._palette.colors.red[0]};
color: ${({ theme }) => theme._palette.colors.red[60]};
`;
}
if (severity === 'Low') {
return css`
background-color: ${({ theme }) => theme._palette.colors.blue[0]};
color: ${({ theme }) => theme._palette.colors.blue[60]};
`;
}
}}
`;
simplified Wrapper
export const Wrapper = styled('div')<{ printMode?: boolean }>`
min-width: 750px;
max-width: 1000px;
${({ printMode }) =>
printMode === true &&
css`
${Header} {
background-color: ${({ theme }) => theme._palette.colors.red[80]};
}
// I want to access / handle the props for ${Cell}, but I lose access to them
// something like the following would be ideal:
//
// ${Cell} {
// background-color: ${(props) => transparent === true ?
// 'transparent' :
// props.theme._palette.colors.red[80];
// };
//
`}
`;
screenshot of above example shows that it is looking at the styled-component theme object and not the component level props passed in Cell.
Any thoughts on how I can get approach to handle the Children prop styles concisely within Wrapper when Wrapper.printMode is passed? In the Wrapper example directly above, transparent is effectively always false.
You could solve it with classes (or some other selector), though this would mean employing a different strategy which may not be desirable.
Something like:
<Wrapper printMode>
<Table>
<Row key={index}>
<Cell severity='Low'>x</Cell>
<Cell className='_transparent'>y</Cell>
<Cell>z</Cell>
</Row>
</Table>
</Wrapper>;
export const Wrapper = styled('div')<{ printMode?: boolean }>`
min-width: 750px;
max-width: 1000px;
${({ printMode }) =>
printMode === true &&
css`
${Header} {
background-color: ${({ theme }) => theme._palette.colors.red[80]};
}
// I want to access / handle the props for ${Cell}, but I lose access to them
// something like the following would be ideal:
//
// ${Cell} {
background-color: ${props =>
props.theme.palette.colors.red[80]};
&._transparent {
background-color: 'transparent';
}
// };
//
`}
`;

Data in array items not being displayed on the screen in React

I'm making a simple React app where I am getting a list of cars through useEffect hook and then mapping through each one to display them on the screen. Then, I have one button whose purpose would be to sort the cars based on their horsepower. So, when clicking on that button the user would see only the cars whose horsepower is greater than for example 700.
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [items, setItems] = useState([])
const [yo, setYo] = useState(false)
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data)
})
}, [])
function reverse(){
items.reverse();
setItems([...items])
}
function sortHigherHP(){
let cars = items.filter(item => {
return item.horsepower >= 700;
}).map(i => {
return(
<div key={Math.floor(Math.random()*10000)} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Make: {i.make}</p>
<p>Horsepower: {i.horsepower} HS</p>
</div>
)
});
setItems(cars)
}
function hey(){
setYo(!yo)
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hey}>Sort</h3>
<div className={yo ? "show" : "hide"}>Sorting options
<button onClick={reverse}>Reverse Order</button>
<button onClick={sortHigherHP}>Higher Horsepower</button>
</div>
</div>
{items.slice(0, 50).map(a => {
return (
<div key={Math.floor(Math.random()*10000)} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Make: {a.make}</p>
<p>Horsepower: {a.horsepower} HS</p>
</div>
)
})}
</div>
);
}
The reverse event handler is obviously working since the reverse method just reverses the array, without changing the original array. But this filter method returns a new array and I have problems here on how to actually display data on the screen. Could the problem be in the .map method that comes right after .filter()?
Could the problem be in the .map method that comes right after .filter()?
Yes this is indeed the problem:
function sortHigherHP() {
let cars = items
.filter((item) => {
return item.horsepower >= 700;
})
.map((i) => {
return (
<div
key={Math.floor(Math.random() * 10000)}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px",
}}
>
<p>Make: {i.make}</p>
<p>Horsepower: {i.horsepower} HS</p>
</div>
);
});
setItems(cars);
}
You should remove the map in its entirety here:
function sortHigherHP() {
let cars = items.filter((item) => {
return item.horsepower >= 700;
});
setItems(cars);
}
You already have a map call that handles displaying your items:
{
items.slice(0, 50).map((a) => {
return (
<div
key={Math.floor(Math.random() * 10000)}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px",
}}
>
<p>Make: {a.make}</p>
<p>Horsepower: {a.horsepower} HS</p>
</div>
);
});
}
Because of the extra map call you're actually setting the items state to jsx instead of an array of objects, which makes the code above not work.
Codesandbox example

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>

Reusing a modal, in a React component, with different children

Recently i had to write something in React, that required me to render different components in a modal. Being that i didn't want to repeat my self with different modals in the same parent component, i decided to reuse it, but wasn't sure how to do it "correctly". This is what i have done:
renderModalTitle = () => {
return this.state.currentModalAction === 'delete' ? `Are you sure you want to delete book "${this.state.currentBook.title}"?`
: this.state.currentBook ? `Edit book "${this.state.currentBook.title}"`
: 'Create new book'
}
renderModalBody = () => {
return this.state.currentModalAction === 'edit' ||
this.state.currentModalAction === 'new' ?
<BookForm book={this.state.currentBook} onSave={this.onBookSave}>
</BookForm>
: <ConfirmDelete onBookDeleteCancel={this.toggle} onBookDelete={()=>
{this.onBookDelete(this.state.currentBook.id)}} data=
{this.state.currentBook}></ConfirmDelete>
}
I know it's a bit hard to read, because the indentation in the code snippet is slightly messed up. But as you can see, i just have functions that return the relevant jsx, according to the "currentModalAction". Then in the modal:
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className}>
<ModalHeader className="color_main" toggle={this.toggle}>{this.renderModalTitle()}</ModalHeader>
<ModalBody>
{this.renderModalBody()}
</ModalBody>
<ModalFooter>
<Button className="square" color="default" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
So yes, i've achieved "reusability" of the modal, and didn't repeat my self, but it seems to me, that this might do more harm than good...Not vert readable, not very clear.
Is there some common approach towards this issue? Notice that i didn't use react-modal or something like that. It's just reactstrap.
I made some code representing your case.
Your can use a function prop like renderBodyComponent that will render your modal body.
class FlexibleModal extends React.Component {
render() {
if (!this.props.isOpen) {
return null;
}
return (
<div className="flexible-modal">
<div className="flexible-modal-header">
{this.props.headerTitle}
</div>
<div className="flexible-modal-body">
{this.props.renderBodyComponent()}
</div>
</div>
);
}
};
const BodyCase1 = () => (
<div>
Modal Body Case 1
</div>
);
const BodyCase2 = () => (
<div>
Modal Body Case 2
</div>
);
class App extends React.Component {
state = {
showModal: false,
case: 1,
}
toggleModal = () => {
this.setState({ showModal: !this.state.showModal });
}
toggleCase = () => {
const nextCase = this.state.case === 1 ? 2 : 1;
this.setState({ case: nextCase });
}
render() {
return (
<div>
<button
onClick={() => this.toggleModal()}
>
Toggle modal
</button>
<button
onClick={() => this.toggleCase()}
>
Toggle next case
</button>
<FlexibleModal
isOpen={this.state.showModal}
headerTitle="Customizable Modal Header Title"
renderBodyComponent={
this.state.case === 1
? () => (<BodyCase1 />)
: () => (<BodyCase2 />)
}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react'));
.flexible-modal {
margin: 15px;
border: 1px solid #ddd;
background: #fff;
}
.flexible-modal-header {
border-bottom: 1px solid #ddd;
padding: 10px;
background: #e7e7e7;
}
.flexible-modal-body {
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>

another case when the state of a component changes but seems that the component does not

I have a react component that has a state variable:
showEditor
when showEditor is false it is supposed to show only a div with some number inside it (initially showEditor is false). If this state variable is true my react component is supposed to show a textbox and a button with "save" label inside another div -making dissapear the first div with the number-. This textbox will be used to change the number. For the first div (the one that only shows a number) I defined:
<div onClick={this.showEditorProc}>
{ this.state.showEditor ?
<div ....>
{ just the numeric value }
</div>
:
<div ....>
textBox
<button onClick={save and make show Editor false}>
Save
</button>
</div>
</div>
the function this.showEditorProc will modify the state of the showEditor variable to true and the save button and textbox components will appear (inside another div too). I created a function that will executed if the save button is clicked. This function modifies the showEditor variable to false however, I can not see the div with just the numeric value. Instead I still see the textbox with the save button. Is there something else I could be missing? Here it is the code of my component:
import React from 'react';
import ReactDOM from 'react-dom';
import NumberFormat from 'react-number-format';
export class NumericBox extends React.Component {
constructor() {
super();
this.state = {
enteredValue: '',
showNumEditor: false,
index: ''
};
this.showNumericEditor = this.showNumericEditor.bind(this);
this.handle_enteredValue_change = this.handle_enteredValue_change.bind(this);
this.saveCellInfo = this.saveCellInfo.bind(this);
this.loadBasicInformation = this.loadBasicInformation.bind(this);
}
saveCellInfo(e){
alert(this.state.index);
/* cellAuxParams = new Map([['updateCellValue', this.state.updateCellValue]]); */
console.log('numericBox.js>saveCellInfo>cellAuxParams= ------------------------------------------------ ' + 28);
console.log(this.props.cellAuxParams);
var f = this.props.cellAuxParams.get('updateCellValue');
this.setState({showNumEditor: false}, () => f(this.state.Index, '77'));
}
handle_enteredValue_change(values) {
const {formattedValue, value} = values;
// formattedValue = $2,223
// value ie, 2223
this.setState({enteredValue: value});
}
showNumericEditor()
{
this.setState({showNumEditor: true})
}
loadBasicInformation()
{
this.setState({enteredValue: this.props.enteredValue,
index: this.props.index
});
}
componentDidMount(){
this.loadBasicInformation();
}
componentWillReceiveProps(nextProps){
alert(nextProps.enteredValue);
this.setState({enteredValue: nextProps.enteredValue}, () => this.loadBasicInformation());
}
render() {
const table4controls = {
display: 'table',
width: this.props.style.width,
backgroundColor: 'white',
border: '0px solid #666666',
borderSpacing: '0px',
paddingBottom: '0em',
paddingTop: '0em'
};
const table4controls_RowStyle = {
display: 'table-row',
width: 'auto',
clear: 'both',
borderBottom: '5px'
};
const table4controls_ColsStyleA = {
float: 'left',
display: 'table-column',
width: '60px',
backgroundColor: 'white'
};
const table4controls_ColsStyleB = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const table4controls_ColsStyleC = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const btnStyle={
};
return (
<div onClick={this.showNumericEditor}>
{ this.state.showNumEditor ?
<div style ={table4controls}>
<div style={table4controls_RowStyle}>
<div style={table4controls_ColsStyleA}>
<NumberFormat style={{width: '60px'}}
value={this.state.enteredValue}
thousandSeparator={true}
prefix={this.props.prefix}
onValueChange={this.handle_enteredValue_change}
/>
</div>
<div style={table4controls_ColsStyleB}>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▲
</button>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▼
</button>
</div>
<div style={table4controls_ColsStyleC}>
<button style={btnStyle} onClick={(e) => {this.saveCellInfo(e, this.state.index)}}>
Save
</button>
</div>
</div>
</div>
:
<div syle={table4controls_ColsStyleA}>
{this.state.enteredValue}
</div>
}
</div>
);
}
}
You have an onClick={this.showNumericEditor} handler on the surrounding div, so when you press the save button, the click event bubbles up and invokes a this.setState({showNumEditor: true}).
To fix it, you can either restructure the rendering or call e.stopPropagation(); at the start of saveCellInfo. Also note that some of your this.saveCellInfo calls are not passing the event.

Categories

Resources