accessing state inside calculatestate from componentdidmount - reactjs

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>

Related

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.

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>

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

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.

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,document.getElementById("app"));
<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="app" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

React Redux re-fetch data using state and state change

I would like to pass data from my state to a fetch call (e.g. search term). When the state changes, it should re-fetch my data.
The lifecycle methods seem to be the place to do this, but when I use them it either does nothing (render is called before state change), or it goes on an endless loop.
Here is my simplified code:
#connect((store) => {
return {
collections : store.collections.collections
}
}, {
fetchCollections
})
export default class CollectionPane extends Component {
constructor(props){
super(props);
this.state = {
term : null
}
}
componentWillMount(){
this.handleFetchCollections(this.state.term); // This loads initial ok
}
componentDidUpdate(){
// this.handleFetchCollections(this.state.term); // This causes loop
}
handleFetchCollections(props, sort){
log('Fetching...', 'green');
this.props.fetchCollections(props, sort);
}
onSearch(){
const term = "test example";
this.setState({
term
})
}
render(){
const { collections } = this.props;
return (
<div>
<a style={{ display: "block", padding: "1em" }} onClick={this.onSearch.bind(this)}>Search</a>
<CollectionList collections={collections} />
</div>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>
You should compare your new state with the previous state before performing fetch:
componentDidUpdate(prevProps, prevState) {
if (prevState.term !== this.state.term) {
this.handleFetchCollections(this.state.term);
}
}
With react-redux you should change your state through actions and reducers. Then you can use async actions as described here: http://redux.js.org/docs/advanced/AsyncActions.html

Resources