I am quite new to react and redux. This is a little confusing problem for me. Can someone please explain this why my searchTerm state value printed twice (render method is getting called twice) after every input change. I read react and learnt that on every state change, render is called but in this called render is called twice? Am I getting this wrong?
App.js
import React from 'react';
import Todos from './components/Todos';
import Header from './components/Header';
class App extends React.Component {
state = {
searchTerm : '',
todos: [{
id: 1,
completed: true,
text: "I am number one"
},
{
id: 2,
completed: false,
text: "I am number two"
},
{
id: 3,
completed: false,
text: "I am number three"
}]
}
markComplete = (id) => {
this.setState({
todos: this.state.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
}
deleteTo = (id) => {
this.setState({
todos: [...this.state.todos.filter(todo => todo.id!==id)]
});
}
search = (evt) => {
const value = evt.target.value;
this.setState({
searchTerm: value
});
}
render() {
return (
<div>
<Header />
{ console.log(this.state.searchTerm) }
<input type="text" onChange = {this.search} />
<Todos todos = {this.state.todos} markComplete = {this.markComplete} deleteTo = {this.deleteTo}/>
</div>
);
}
}
export default App;
Todos.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
return this.props.todos.map((todo) =>
<TodoItem key={todo.id} todo = {todo} markComplete = {this.props.markComplete} deleteTo={this.props.deleteTo}/>)
}
}
export default Todos;
TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
getStyle = () => {
return { textDecoration: this.props.todo.completed ? "line-through": "none" };
};
getButtonStyle = () => {
return {
backgroundColor: 'red',
border: 'none',
cursor: 'pointer',
float: 'right',
padding: '5px 10px',
borderRadius: '50%'
};
}
render() {
const {id, text} = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox" onChange= { () => this.props.markComplete(id) }/> {' '}
{text}
<button style={this.getButtonStyle()} onClick = { () => this.props.deleteTo(id)}> x </button>
</p>
</div>
);
}
}
export default TodoItem;
it's probably because of React StrictMode in your index.js file (if you use create-react-app).
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
strict mode checks for potential problems and caused to run some lifecycle methods twice (like constructor, render, componentShouldUpdate, etc).
Strict mode checks are run in development mode only; they do not impact the production build.
you can read more about it on strict-mode
The render function can be called almost any number of times before the commit phase occurs and updates are flushed to the DOM. The render function should also be a pure function, meaning there are no side-effects, like console logging. Instead use the componentDidUpdate lifecycle function to log when state or props update. Perhaps this diagram would help.
A simple solution to avoid the side effects is to not assign Dynamic values in the Render(), just keep it in the state, and call it before the Render() as example on Click() method.
Related
So when I run this and check the checkbox, I can see the values changing in the state, but why is the checkbox control not changing its status from check/uncheck? I know the render() method is being hit as well. Why, oh why, Gods of code? Lost in hours of figuring out what's wrong and I'm lost!
bob-Todos.js FILE
class Todo extends React.Component {
constructor(param) {
super();
this.state = {
id: param.data.id,
text: param.data.text,
completed: param.data.completed,
onMyChange: param.OnChange,
};
}
render() {
console.log("In TODO Render");
return (
<div>
<p>
<input
type="checkbox"
onChange={() => {
this.state.onMyChange(this.state.id);
}}
checked={this.state.completed}
/>
{this.state.text}
</p>
</div>
);
}
}
export default Todo;
Bob-App.js FILE
import React, { Component } from "react";
import Todo from "./bob-Todo";
import todoData from "../data/bob-todosData";
class App extends Component {
constructor() {
super();
this.state = { data: todoData };
this.OnChange = this.OnChange.bind(this);
}
OnChange(myId) {
this.setState((prev) => {
let updatedTodos = prev.data.map((todo) => {
if (todo.id === myId) {
todo.completed = !todo.completed;
}
return todo;
});
return { data: updatedTodos };
});
console.log(this.state.data);
}
render() {
return this.state.data.map((item) => {
return <Todo key={item.id} data={item} OnChange={this.OnChange} />;
});
}
}
export default App;
bob-todosData.js FILE
const todosData = [
{
id: 1,
text: "take out the trash",
completed: true
},
{
id: 2,
text: "rest for a while and relax",
completed: false
},
{
id: 3,
text: "watch an online movie",
completed: true
}
]
export default todosData
index.js FILE
import React from 'react';
import ReactDOM from 'react-dom';
import AppBob from "./bobComponents/Bob-App";
ReactDOM.render(
<AppBob />, document.getElementById('root')
);
You don't need to assign your props to state in your Todo component
Just remove them and invoke the function also use those variables directly:
Then your component will be:
class Todo extends React.Component {
render() {
const {
data: {
id,
text,
completed,
},
OnChange, // <-- Should rename this to "onChange"
} = this.props;
console.log('In TODO Render');
return (
<div>
<p>
<input
type="checkbox"
onChange={() => {
OnChange(id);
}}
checked={completed}
/>
{text}
</p>
</div>
);
}
}
export default Todo;
Also, rename your OnChange function to onChange to enable js convention
I have built this site
https://supsurvey.herokuapp.com/surveycreate/
now I am trying to move the fronted to React so I can learn React in the process.
with vanila js it was much easier to create elements dynamically.
I just did createElement and after that when I clicked "submit" button
I loop throw all the elements of Options and take each target.value input.
so I loop only 1 time in the end when I click Submit and that's it I have now a list of all the inputs.
in react every change in each input field calls the "OnChange" method and bubbling the e.targe.value to the parent and in the parent I have to copy the current array of the options and rewrite it every change in every field.
is there other way? because it seems crazy to work like that.
Options.jsx
```import React, { Component } from "react";
class Option extends Component {
constructor(props) {
super(props);
this.state = { inputValue: "", index: props.index };
}
myChangeHandler = event => {
this.setState({ inputValue: event.target.value });
this.props.onChange(this.state.index, event.target.value);
};
render() {
return (
<input
className="survey-answer-group"
type="text"
placeholder="Add Option..."
onChange={this.myChangeHandler}
/>
);
}
}
export default Option;
______________________________________________________________________________
Options.jsx````
```import React, { Component } from "react";
import Option from "./option";
class Options extends Component {
render() {
console.log(this.props);
return <div>{this.createOptions()}</div>;
}
createOptions = () => {
let options = [];
for (let index = 0; index < this.props.numOfOptions; index++) {
options.push(
<Option key={index} onChange={this.props.onChange} index={index} />
);
}
return options;
};
}
export default Options;```
______________________________________________________________________________
App.jsx
```import React from "react";
import OptionList from "./components/Options";
import AddButton from "./components/add-button";
import "./App.css";
class App extends React.Component {
state = {
numOfOptions: 2,
options: [{ id: 0, value: "" }, { id: 1, value: "" }]
};
handleChange = (index, value) => {
const options = [...this.state.options];
console.log("from App", value);
options[index].value = value;
this.setState({
options: options
});
console.log(this.state);
};
addOption = () => {
const options = [...this.state.options];
options.push({ id: this.state.numOfOptions + 1, value: "" });
this.setState({
numOfOptions: this.state.numOfOptions + 1,
options: options
});
};
submitButton = () => {};
render() {
return (
<div className="poll-create-grid">
<div id="poll-create-options">
<OptionList
onChange={this.handleChange}
numOfOptions={this.state.numOfOptions}
/>
</div>
<button
className="surveyCreate-main-btn-group"
onClick={this.addOption}
>
Add
</button>
<button
className="surveyCreate-main-btn-group"
onClick={this.submitButton}
>
Submit
</button>
</div>
);
}
}
export default App;
```
So firstly,
The issue is with the way your OptionList component is defined.
Would be nice to pass in the options from the state into the component rather than the number of options
<OptionList
onChange={this.handleChange}
options={this.state.options}
/>
The you basically just render the options in the OptionsList component (I'm assuming it's same as the Options one here
class Options extends Component {
...
render() {
return
(<div>{Array.isArray(this.props.options) &&
this.props.options.map((option) => <Option
key={option.id}
index={option.id}
onChange={this.props.onChange}
value={option.value}
/>)}
</div>);
}
...
}
You would want to use the value in the Option component as well.
this.props.onChange(this.state.index, event.target.value); No need using the state here to be honest
this.props.onChange(this.props.index, event.target.value); is fine
I'm new to react and I'm trying component functional style.
I have simple todo list. I would like to strike out todo item from list using style property. From Chrome debug mode I do not see immediate reaction on checkbox changes, also Item is not striked out... It seams to me, that it is problem with how I manage state of components. I would appreciate some guidance.
App.js
import React, {useState} from 'react';
import Todos from "./components/Todos";
import './App.css'
const App = () => {
const [todos, setTodos] = useState(
[
{id: 1, title: 'Take out the trash', completed: false},
{id: 2, title: 'Dinner with wife', completed: false},
{id: 3, title: 'Meeting boss', completed: false}
]
);
const markComplete = id => {
console.log((new Date()).toString());
todos.map(todo => {
if (todo.id === id) {
todo.completed = ! todo.completed;
}
return todo;
});
setTodos(todos);
};
return (
<div className="App">
<Todos todos={todos} markComplete={markComplete}/>
</div>
);
};
export default App;
Todos.js
import React from "react";
import TodoItem from "./TodoItem";
const Todos = ({todos, markComplete}) => {
return (
todos.map(todo => (
<TodoItem key={todo.id} todoItem={todo} markComplete={markComplete} />
))
);
};
export default Todos;
TodoItem.js
import React from "react";
const TodoItem = ({todoItem, markComplete}) => {
const getStyle = () => {
console.log("style: " + todoItem.completed);
return {
background: '#f4f4f4',
padding: '10px',
borderBottom: '1px #ccc dotted',
textDecoration: todoItem.completed ? 'line-through' : 'none'
}
};
return (
<div style={getStyle()}>
<p>
<input type="checkbox" onChange={markComplete.bind(this, todoItem.id)}/>{' '}
{todoItem.title}
</p>
</div>
);
};
export default TodoItem;
I expect that this getStyle() will follow state... somehow...
Don't mutate state. In markComplete function, you are mutating the todos array directly. Change your function like this to avoid mutation
const markComplete = id => {
console.log((new Date()).toString());
let newTodos = todos.map(todo => {
let newTodo = { ...todo };
if (newTodo.id === id) {
newTodo.completed = !newTodo.completed;
}
return newTodo;
});
setTodos(newTodos);
};
Array.prototype.map() returns a new Array, which you are throwing away. You need to use the new array, e.g.:
const markComplete = id => {
...
setTodos(totos.map(...))
Components ->
Box
Todolist
Add
AddModal
Main component App
But it is not working that is when I add a new task. It does not get added properly.
I think I cannot use this.setstate twice in a function.
Hope I am correct
Here is given the main component.
App.js :
import React, { Component } from 'react';
import './App.css';
import Box from './Components/Box';
import Add from './Components/Add';
import Todolist from './Components/Todolist';
class App extends Component {
constructor(props) {
super(props);
this.state = {
lists: '',
inputValue: '',
itemArray: []
}
}
onAddTask = () => {
this.setState ({
lists: this.state.inputValue
});
const item = this.state.itemArray;
const title = this.state.lists;
item.push({ title })
this.setState(prevState => ({
itemArray: [...prevState.lists, title]
}))
}
updateInputValue = (event) => {
this.setState({
inputValue: event.target.value
});
}
render() {
let length = this.state.itemArray.length;
return (
<div className="App">
<Box createTodo = {
<div>
{this.state.itemArray.map((itemArr) => {
return (
<div className="box">
<Todolist tasks = {itemArr} />
</div>
)
})}
</div>
}>
</Box>
<Add addTask = {this.onAddTask} inputValues = {this.updateInputValue} inputV = {this.state.inputValue} />
</div>
);
}
}
export default App;
Your addTasks function is not correct, you are mixing up things here.
In your inputValue you save the current value from the input field right? So if you write the following
this.setState({
lists: this.state.inputValue
});
you set your todo list to this single value. And your todo list is not an array anymore.
Secondly, state is imutable. So if you write the following
this.state.itemArray.push({ title });
the state will not be updated. What you actually want is the following:
onAddTask = () => {
this.setState({
itemArray: [...this.state.itemArray, this.state.inputValue]
})
}
And I'm not sure what the lists property on the state is for. You don't use it anywhere besides in your onAddTask function. So I guess you can remove it.
I'm getting repeated warnings
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the RoundView component.
and it points to a socket.io line
socket.on(
'rankings',
(payload) => this.setState({
totals: payload.totals,
scores: payload.scores
},this.updateRoundProgress)
);
But the component is clearly mounted and everything seems to function fine, the state gets updated just fine when a 'rankings' event is captured. The full code is below:
https://github.com/JellyKid/ballsavr/blob/master/Frontend/app/components/user/RoundView.jsx
import React from 'react';
import handleFetch from '../../helpers/handleFetch';
import { Grid, Col, PageHeader } from 'react-bootstrap';
import { connect } from 'react-redux';
import update from 'immutability-helper';
import ConfirmScores from './view/ConfirmScores';
import Rankings from './view/Rankings';
import Scores from './view/Scores';
import Group from './view/Group';
import Manage from './view/Manage';
const socket = require('socket.io-client')('/round',{
path: '/api/socket.io',
autoConnect: false
});
class RoundView extends React.Component {
constructor(props) {
super(props);
this.state = {
round: this.props.round || {
event: {
title: 'Loading...'
},
name: "Please wait",
tables: [],
players: []
},
scores: [],
totals: []
};
this.refreshScores = this.refreshScores.bind(this);
this.updateRoundProgress = this.updateRoundProgress.bind(this);
}
componentDidMount(){
this.handleSocket();
let fetches = [
handleFetch('GET',`/api/score/totals?round=${this.props.params.roundID}`),
handleFetch('GET',`/api/score/round?id=${this.props.params.roundID}`)
];
if(!this.props.round){
fetches.push(handleFetch('GET', `/api/round/${this.props.params.roundID}`));
}
return Promise.all(fetches)
.then(
(res) => {
this.setState({
totals: res[0].payload,
scores: res[1].payload,
round: this.props.round || res[2].payload
});
}
).catch((err) => {console.log(err);});
}
handleSocket(){
socket.open();
socket.on(
'connect',
() => socket.emit('join round', this.props.params.roundID)
);
socket.on(
'rankings',
(payload) => this.setState({
totals: payload.totals,
scores: payload.scores
},this.updateRoundProgress)
);
socket.on(
'connect_error',
() => setTimeout(socket.open, 1000)
);
}
updateRoundProgress(){
if(this.props.player.admin){
let scores = this.state.scores.filter((score) => score.confirmed).length;
let progress = Math.floor((scores/(this.state.round.tables.length * this.state.round.players.length))*100);
return this.setState(update(
this.state,
{round: {progress : {$set: progress}}}
));
}
}
refreshScores(){
handleFetch('GET',`/api/score/round?id=${this.props.params.roundID}`)
.then(
(res) => this.setState({scores: res.payload})
).catch((err) => {console.log(err);});
}
componentWillUnmount(){
socket.close();
}
render(){
let player = this.state.round.players.find((p) => p.user._id === this.props.player._id);
let groupName = (player) ? player.group : "";
return (
<Grid>
<Col md={6} mdOffset={3}>
<PageHeader>
{this.state.round.event.title}<br/><small>{this.state.round.name}</small>
</PageHeader>
<Rankings
totals={this.state.totals}
players={this.state.round.players}
player={this.props.player}
/>
<hr />
<Group
group={groupName}
players={this.state.round.players}
/>
<hr />
<Scores
player={this.props.player}
scores={this.state.scores}
round={this.state.round}
group={groupName}
/>
<hr />
<ConfirmScores
scores={this.state.scores}
player={this.props.player}
/>
<hr />
<Manage
round={this.state.round}
player={this.props.player}
/>
</Col>
</Grid>
);
}
}
function mapStateToProps(state, ownProps) {
return {
round: state.rounds.find((round) => round._id === ownProps.params.roundID),
player: state.user
};
}
export default connect(mapStateToProps)(RoundView);
I close the socket when the component unmounts. I also tried logging when the component unmounts and it doesn't unmount before this so why the heck am I getting the error?
This probably related to the fact your component is connected to Redux
as well, it means there's another instance (that's not mounted) that's
listening to the socket. - My guess is to try to disconnect redux and
see if that helps. in any case, i would move the socket and optionally
redux to a container component, there's too much going on on this
component to debug it easily! – Patrick
Thanks Patrick!