how can I select only one component of my array of component - reactjs

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.

Related

accessing state inside calculatestate from componentdidmount

I am new to flux and react and I would like to know how can I access state inside calculatestate() from
componentdidmount()?
how can I access events state inside componentdidmount()? I would like loop through events and then run a query per event, which in turn updates another store.
static getStores() {
return [SomeStore];
}
static calculateState(prevState, props) {
const somestore = SomeStore.getState();
return {
events: SomeStore.getState(),
};
}
componentdidmount(){
//this.state.events;
//need to do some query based on the events
//this query will update another store.
}
You need to use the lifecycle methods to understand the componentDidMount method which are outlined here
To initialise the localstate, you should use the constructor method (static can also be used as an alternative, but jsfiddle throws an error.
To show the initial state, you can log within componentDidMount with this.state.
const someStore = ["event"];
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = { events: someStore };
}
// if you want to change state based on props
shouldComponentUpdate(nextProps, nextState) {
// change state if you want here
if (this.state.events.length !== nextState.events.length) {
console.log(this.state, nextState)
return true // update
}
return false
}
// get initial state
componentDidMount() {
console.log(this.state.events);
}
render() {
return (
<div>
<span>example</span>
<button onClick = {() => {
this.setState(state => {
return {
events: [...state.events, "new event"]
}
})
}}>Add Event</button>
<div>
events: { JSON.stringify(this.state.events) }
</div>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

react counter for each item of list

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.

How to capture click outside React component

I started to learn React and I am trying to implement a modal window. I am at the same time using TypeScript.
I wanted to capture a click outside my React component, so when I click outside the modal window, this one closes. I based my approach on this: How to capture click outside React component
import styled from 'styled-components';
const StyledModal = styled.div`
width: 100%;
background-color: #fff;
box-shadow: 0 0 0.625rem, rgba(0, 0, 0, 0.2);
#media (min-width: 576px) {
width: 32rem;
},
`;
class Modal extends React.Component<ModalProps> {
private modal: HTMLDivElement;
onOutsideClick = (e: any) => {
if (!_.isNil(this.modal)) {
if (!this.modal.contains(e.target)) {
this.onClose(e);
}
}
}
componentDidMount() {
document.addEventListener('mousedown', this.onOutsideClick, false);
}
componentWillMount() {
document.removeEventListener('mousedown', this.onOutsideClick, false);
}
render() {
<div>
<StyledModal ref={(node: any) => { this.modal = node; }}>
...
</StyledModal>
</div>
}
}
The issue is whenever I click inside or outside the modal I get this error, which I don't know what it is or how to fix it:
Any lights please let me know...
Since your StyledModal is styled-components you need to add innerRef to be able to get the DOM node. Keep in mind innerRef is a custom prop only for styled-components
https://github.com/styled-components/styled-components/blob/master/docs/tips-and-tricks.md#refs-to-dom-nodes
<StyledModal innerRef={(node: any) => { this.modal = node; }}>
...
</StyledModal>
From styled-components v4 onward it is ref prop.
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return (
<Input
ref={this.inputRef}
/>
);
}
For more info Refs
If you want to use a tiny component (466 Byte gzipped) that already exists for this functionality then you can check out this library react-outclick. It lets you capture clicks outside of a component.
The good thing about the library is that it also lets you detect clicks outside of a component and inside of another. It also supports detecting other types of events.

Redux: making sharing component trigger unique redux path depends on parent component

Here is my scenario: I have a feed with list of questions. In each question, I have a upvote button for upvoting this question.
So I will design an upvote button component sharing between other question components. When clicking to this button, button will trigger an event to server. method signature sometime such as:
func upvote(questionOwnerId, upvoteUserId) { ... }
After upvoting, upvote will dispatch an event for redux handle and update state respectively. So upvote button will always display latest state. (how many upvoted users for that question).
This is my problem: I don't know how to make sharing component (upvote component), after calling method to server then trigger unique redux path on redux tree.
I have a solution for this: Upvote component will receive following parameters:
total number of upvote.
question's user id.
redux path.
Then upvote function and Reducer will use redux path for update state on redux tree respectively. Does this method works well and look clean for project ? Or is there any "true" way for my problem?
#Edit: Redux path for example: feeds.item.questionList.question[0] for question component at 0 index. feeds.item.questionList.question[1] for question component at 1 index ... so Reducer can understand how to update redux tree.
Such shared component can be done by just receiving the proper props and passing a generic onClick events.
For example, let say we create component named <Question /> and it will the props:
votes - to display current number of votes.
onUpvote - as a click event for the up-vote button.
onDownvote - as a click event for the down-vote button.
id - it needs the id in order to pass it to the parent's
function.
question - The value of the question to display.
Now, your parent component will take a list of questions from the redux-store and will .map on it and will return for each question a <Question /> component with the respective props (id, votes and question).
As for the onUpVote and onDownVote you will pass functions that created in the parent. These functions will dispatch actions and that will handled by your reducer which will return the new state, this will trigger a re-render with new data to show.
I've created a simple example, note that i can't use redux here so i managed the state inside the App component, but i mentioned in comments where you can dispatch actions and what logic should go inside the reducers.
const questionsList = [
{
id: 1,
votes: 2,
question: "whats up"
},
{
id: 2,
votes: -1,
question: "whats the time"
},
{
id: 3,
votes: 0,
question: "where are you"
},
{
id: 4,
votes: 7,
question: "who are you"
}
];
class Question extends React.Component {
constructor(props) {
super(props);
this.onUpvote = this.onUpvote.bind(this);
this.onDownvote = this.onDownvote.bind(this);
}
onUpvote() {
const { id, onUpvote } = this.props;
onUpvote(id);
}
onDownvote() {
const { id, onDownvote } = this.props;
onDownvote(id);
}
render() {
const { votes, question } = this.props;
const voteClassName = `votes ${votes < 0 ? 'low' : 'high'}`;
return (
<div className="question-wrapper">
<div className="buttons-wrapper">
<button className="button" onClick={this.onUpvote}>+</button>
<div className={voteClassName}>{votes}</div>
<button className="button" onClick={this.onDownvote}>-</button>
</div>
<div className="question">{question}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
questions: questionsList
};
this.onUpvote = this.onUpvote.bind(this);
this.onDownvote = this.onDownvote.bind(this);
}
onUpvote(id) {
const { questions } = this.state;
// you can dispatch an action here
// and instead of doing this logic in here you can do it in your reducer
const nextState = questions.map(question => {
if (question.id != id) {
return question;
}
return {
...question,
votes: question.votes + 1
};
});
this.setState({ questions: nextState });
}
onDownvote(id) {
const { questions } = this.state;
// you can dispatch an action here
// and instead of doing this logic in here you can do it in your reducer
const nextState = questions.map(question => {
if (question.id != id) {
return question;
}
return {
...question,
votes: question.votes - 1
};
});
this.setState({ questions: nextState });
}
render() {
const { questions } = this.state; // get the questions via props (redux store)
return (
<div>
{questions.map(q => (
<Question
id={q.id}
question={q.question}
votes={q.votes}
onUpvote={this.onUpvote}
onDownvote={this.onDownvote}
/>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.question-wrapper{
display: flex;
flex-direction: row;
width: 150px;
box-shadow: 0 0 2px 1px #333;
margin: 10px 0;
padding: 5px 20px;
}
.buttons-wrapper{
display:flex;
flex-direction: column;
}
.button{
margin: 10px 0;
cursor: pointer;
}
.question{
align-self: center;
margin: 0 20px;
font-size: 22px;
}
.high{
color: #3cba54;
}
.low{
color: #db3236;
}
<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="root"></div>

React-Webpack. Update className with css-loader

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

Resources