As far as I understood its possible to add only one class to the element by doing className={styles.className} even if it's composed of many. So at the current moment the code uses ternary operator in order to render different element styles depending on the state.cross value.
export default class Header extends Component {
constructor(props) {
super(props);
this.state = { cross: false };
this.showCross = this.showCross.bind(this);
this.showLine = this.showLine.bind(this);
}
showCross() {
this.setState({cross: true});
}
showLine() {
this.setState({cross: false});
}
render() {
return (
<div onMouseEnter={this.showCross} onMouseLeave={this.showLine} className={styles.blockClose}>
<a className={this.state.close ? styles.closeCross : styles.closeLine}> </a>
</div>
)
}
}
What it actually does it makes 2 lines to look like a cross after state has been changed and transform has been applied.
:local(.closeLine) {
...20px line
&::before {
...equal line
}
}
:local(.closeCross) {
composes: closeLine;
transform: rotate(-45deg);
&::before {
transform: rotate(90deg);
}
}
My question is:
Is it possible instead of conditional rendering just toggle class by doing smth like element.classList.toggle(className) to manage the styling of the element.
:local(.closeCross) {
transform: rotate(-45deg);
&::before {
transform: rotate(90deg);
}
}
You can use the really awesome classnames package, which allows you to easily have multiple classes. I'm not sure of your final goal, but it would be easy to do something like:
<a className={classNames(
styles.closeCross, { // <-- always applied
[styles.closeLine]: this.state.close <-- applied only when closed
})}
> </a>
https://github.com/JedWatson/classnames
Related
I'm trying to make a function that will on each click move my container 80% of window width to the left.
I am using this in my state
left: 0
and this is part of the style of my container that I want to be moved, like this
<div className="horizontal_container" style={{ left: props.left }}>
and I want to use a function that I'm passing via props that looks like this
pomeranjeGalerije = () => {
const {left} = this.state;
left -= window.innerWidth*0,8;
this.setState({left: left});
}
this is how I pass the function via the prop
<ContainerTheatre klikLevo={this.pomeranjeGalerije}/>
and this is the actual button
<img onClick={props.klikLevo} src="xx" alt="right_arrow" />
I get the error message
Expected an assignment or function call and instead saw an expression
for the line left -= window.innerWidth*0,8;
can you please help me get the function right?
you have several problems in your code.
You have to use the decimal point instead of a comma. So use 0.8 instead of 0,8.
You also have to access your left-value from state.left instead of props.left.
Your calculation of left doesn't make much sense. I change that so your div has the initial value of window.innerWidth and shrinks to 80% of its previous value on each button click.
:
import React from 'react';
export default class YourComponent extends Component {
constructor(props) {
super(props);
this.state = { left: window.innerWidth };
this.buttonClick = this.buttonClick.bind(this);
}
buttonClick() {
let newWidth = this.state.left * 0.8;
console.log('new width is', newWidth);
this.setState({left: newWidth });
}
render() {
return (<div style={{"background": "#ff0000", "width": this.state.left + "px", "left": this.state.left}}>
<button onClick={this.buttonClick}>Shrink to 80% of Width</button>
</div>)
}
}
I'm not sure exactly what you are looking for, but this code will create a container component that when clicked on will shrink to 80% of the window. If it's not exactly the same as what you want, it can at least give you an idea of what to do.
import React from 'react';
class App extends React.Component {
render () {
return (
<div>
<Container/>
</div>
)
}
}
class Container extends React.Component {
constructor(props){
super(props)
this.state = {
left:window.innerWidth
}
}
onClick=()=>{
var left = (this.state.left)-window.innerWidth*0.2;
this.setState({left: left});
console.log(left)
}
render () {
return(
<div style={{width:this.state.left+"px",backgroundColor:'red'}} onClick={this.onClick}>
<h1>Hello</h1>
</div>
)
}
}
export default App;
I'm trying to create a counter for each item in a list in React. I want each to be incremented or decremented individually depending on what the user clicks on. This issue is that all counters increment and decrement on click
of a single element, but I would like only the clicked element's counter to change.
this is my code:
class name extends Component {
constructor(){
super()
this.state = {
news: [],
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
I'm trying to do this:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
but it doesn't work also tried this.onVote(item.i) and same result
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = [] } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn\'t want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing in the name component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news: []
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
A few things I noticed unrelated to your question.
1) onVoting should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.
this code here works but I don't know how to just click one of my component in >the array with this code I can change the color.
but I want to know how can I not change the color when I already change it in one >component thanks for the future answer
import React, { Component } from 'react';
export default class Seats extends Component {
constructor() {
super()
this.state = {
status: false,
};
}
changeColor(event) {
if (this.state.status === false) {
event.currentTarget.style.backgroundColor = '#D70202';
this.state.status = true;
}else {
this.state.status = false;
event.currentTarget.style.backgroundColor = '#0CB607';
}
}
render() {
let array = [];
for (var i = 0; i < 5; i++) {
array[i] = i;
}
const list = array.map((d, index) => <div className="seat" onClick={this.changeColor.bind(this)} key={index}></div>);
return (
<div>
{list}
</div>
);
}
}
.seat {
background-color: #0CB607;
border: 1px solid black;
height: 90px;
width: 90px;
}
There are two problems here, which need to be resolved separately:
Instead of using this.state.status = true|false you should use this.setState({ status: true|false }). This forces a re-render.
In your current approach, you are managing your state via just manipulating the DOM directly, setting the style.backgroundColor. This will get blown away the next time your component renders.
To address the second issue, I suggest storing the array of items you are manipulating as state at the component level. As an example:
JS:
export default class Seats extends React.Component {
constructor() {
super()
const seats = [...Array(5)].map(() => ({ status: false }))
this.state = { seats }
}
handleSeatClick(index) {
const seats = this.state.seats
const seat = seats[index]
seat.status = !seat.status
seats[index] = seat
this.setState({ seats })
}
render() {
return (
<div>{list.map((seat, index) =>
<div className={`seat ${seat.status ? 'highlight' : ''}`}
onClick={this.handleSeatClick.bind(index)}
></div>
</div>
)
}
}
CSS:
.seat {
background-color: #0CB607;
border: 1px solid black;
height: 90px;
width: 90px;
}
.seat.highlight {
background-color: #D70202;
}
In this example, we're persisting the array of seats in the component's state. If you are getting a pre-defined list of seats passed in, in the future, you could replace the line that creates the [...Array(5)]... bit with something that instead reads from props being passed in, or loads from an ajax call, etc.
Because the seats are persisted with their own state, as an array, we can simply inspect that state when rendering to determine whether to output the highlight CSS class - which applies the color.
One other thing you can refactor (which I didn't do, to keep this a clear explanation) is to get rid of the .bind in render entirely. Doing this is an anti-pattern, as it will re-create new functions for every item in the list, every time it renders.
I am calling a custom NanoButton component from my page along with an onClick instruction to route to another page:
// Page.js
import { Component } from 'react';
import Router from 'next/router';
class Page2 extends Component {
render() {
return(
<NanoButton type="button" color="success" size="lg" onClick={() => Router.push('/about')}>About</NanoButton>
)
}
}
When the button (NanoButton component) is clicked, I want to execute some internal code before moving on to the onClick coming in as props. Through this internal code, I am trying to simulate the material-design ripple effect that lasts 600 milliseconds. This is how I do it:
import { Component } from 'react';
import { Button } from 'reactstrap';
class NanoButton extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
this.makeRipple(e);
this.props.onClick();
}
makeRipple(e) {
const elements = e.target.getElementsByTagName('div');
while (elements[0]) elements[0].parentNode.removeChild(elements[0]);
const circle = document.createElement('div');
e.target.appendChild(circle);
const d = Math.max(e.target.clientWidth, e.target.clientHeight);
circle.style.width = `${d}px`;
circle.style.height = `${d}px`;
const rect = e.target.getBoundingClientRect();
circle.style.left = `${e.clientX - rect.left - (d / 2)}px`;
circle.style.top = `${e.clientY - rect.top - (d / 2)}px`;
circle.classList.add('ripple');
}
render() {
return (
<Button
className={this.props.className}
type={this.props.type}
color={this.props.color}
size={this.props.size}
onClick={this.onClick}
>
{this.props.children}
</Button>
);
}
}
export default NanoButton;
So as you can see, I need the makeRipple method to execute before this.props.onClick. And initially, it didn't seem to doing that. However, after further testing, it turns out that the methods do run in the right order after all, except the routing (as coded in this.props.onClick) happens instantly and the ripple animation that's styled to last 600 milliseconds doesn't get a chance to run. The CSS that makes this animation happen is:
button {
overflow: hidden;
position: relative;
}
button .ripple {
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.7);
position: absolute;
transform: scale(0);
animation: ripple 0.6s linear;
}
#keyframes ripple {
to {
transform: scale(2.5);
opacity: 0;
}
}
How do I make the this.props.onClick run only AFTER the animation is complete? I tried setting a timeout like so:
setTimeout(this.props.onClick(), 600);
But that throws an error.
Note: I'm using NextJS for server side rendering, if that makes any difference.
There are a lot of ways to do it, like Promise, async/await, etc.
But if you try setTimeout please use
setTimeout(() => this.props.onClick(), 600);
or
setTimeout(this.props.onClick, 600);
your case:
setTimeout(this.props.onClick(), 600);
won't work because this line will pass the result of this.props.onClick() into the first param instead of passing the whole function.
I used react-leaflet to visualize a quite long path on a map. Users can select from a list and I would like to have different color for the selected path. Changing the state and rendering again is too slow, I am looking for a faster solution.
Leaflet path elements have setStyle() method, so my first idea was using it instead of rendering again. But how to reference the leaflet layer?
class MyPathComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.selected){
this.setState({selected: true});
LEAFLET_POLYLINE.setStyle({
color: 'red'
});
}
return false;
}
render() {
return(
<Polyline polylines={this.props.path} />
);
}
}
So what should I write instead of LEAFLET_POLYLINE in this code?
Components in react-leaflet have a property called leafletElement. I believe you can do something like this:
class MyPathComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.selected){
this.setState({selected: true});
this.refs.polyline.leafletElement.setStyle({
color: 'red'
});
}
return false;
}
render() {
return(
<Polyline ref="polyline" polylines={this.props.path} />
);
}
}
Two things to note:
I haven't tested this code, so it may need some small tweaks.
Using a string for "ref" is considered legacy in React, so you'll probably want to do something slightly different (see here). The leafletElement is the important part here.
Instead of the code above, it may be better to just extend the Polyline component for your custom component (limited docs here):
import { Polyline } from 'react-leaflet';
class MyPathComponent extends Polyline {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.selected){
this.setState({selected: true});
this.leafletElement.setStyle({
color: 'red'
});
}
return false;
}
}
Let me know if any of this works out for you.
Full example using React callback ref and adding to #Eric's answer above:
export default class MyMap extends Component {
leafletMap = null;
componentDidMount() {
console.debug(this.leafletMap);
}
setLeafletMapRef = map => (this.leafletMap = map && map.leafletElement);
render() {
return (
<Map
ref={this.setLeafletMapRef}
>
<TileLayer
attribution="Powered by <a href="https://www.esri.com">Esri</a>"
url="http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
/>
</Map>
);
}
}