React Component State issue with duplicate components - javascript

I've recently started learning React and I'm a little bit confused.
Please see the following codepen or the code snippet below.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
sessions: [],
sessionSelected: null
}
this.addSession = this.addSession.bind(this);
this.switchSession = this.switchSession.bind(this);
}
addSession() {
let sessions = this.state.sessions;
let id = (sessions.length)
let title = "Session " + id;
let session = <Session title={title} index={id + 1} />;
sessions.push(session);
this.setState({
sessions: sessions,
sessionSelected: id
});
}
switchSession(id) {
this.setState({
sessionSelected: id
});
}
render() {
return (
<div>
<button onClick={this.addSession} >+ Add Session</button>
<div>{this.state.sessions[this.state.sessionSelected]}</div>
<div className="switchers">
{this.state.sessions.map((x, i) => {
return <SessionSwitcher index={i + 1} onClick={() => { this.switchSession(i) }} />;
})}
</div>
</div>
);
}
}
class Session extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
this.startTimer = this.startTimer.bind(this);
this.count = this.count.bind(this);
}
startTimer() {
setInterval(this.count, 1000);
}
count() {
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<h1>{this.props.title}</h1>
<p>{this.state.count}</p>
<button onClick={this.startTimer}>Start Timer</button>
</div>
)
}
}
class SessionSwitcher extends React.Component {
render() {
return (
<div className="switcher" onClick={this.props.onClick}>
<span>{this.props.index}</span>
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector('#app')
);
I want to be able to trigger multiple timers within multiple components.
For some reason when I click the start timer in one component, it triggers it for the other components too.
Can someone explain to me what I am doing wrong?

Here are a couple of things you could change to get a more predictable experience:
Rather than storing JSX in state, which is a complex object, store just the id, and render JSX according to that id.
Try not to mutate state in place (as with pushing onto an array that's already in state).
If you want to have persistent timers across components, regardless of which ones are currently selected, be sure not to unmount them when they're deselected.
Here are the relevant updated parts of your <App/> component (note that these are more quick fixes than best practices):
addSession() {
const id = this.state.sessions.length;
this.setState( state => ({
// treat your state as immutable
sessions: state.sessions.concat( id ),
sessionSelected: id
}));
}
// don't unmount when switching components, just hide
render() {
return (
<div>
<button onClick={this.addSession} >+ Add Session</button>
<div>
{ this.state.sessions.map( session =>
<div style={{ display: session === this.state.sessionSelected ? 'block' : 'none' }}>
<Session title={"Session " + session} />
</div>
)}
</div>
<div className="switchers">
{this.state.sessions.map((x, i) => {
return <SessionSwitcher index={i + 1} onClick={() => { this.switchSession(i) }} />;
})}
</div>
</div>
);
}
Try it here: https://codepen.io/glortho/pen/ZrLMda?editors=0011

Related

Why all div will go open If am clicking on any one button?

I am trying to fetch the data to show the content as the frontend side. In this image
you can see I have mentioned the button with the div attributes i.e. in blue color.I tried the below code but I don't know how to create/handle the button and div.
import React from "react";
import {Col } from "react-bootstrap";
import "./style.scss"
class Example extends React.Component{
constructor(props){
super(props);
this.state = {
opencollapse: true,
}
}
toggle = () => this.setState((currentState) => ({opencollapse: !currentState.opencollapse}));
renderApps = () => {
const result = this.props.userdata.appliers.map((item, i) => {
return(
<div key={i}>
<div>
<div className="d-flex">
<Col>
<button onClick={this.toggle}>{this.state.opencollapse ? 'Click to close' : 'See Letter'}</button>
</Col>
<Col>
helloIcon
</Col>
</div>
{this.state.opencollapse && <div>{this.props.userdata.appliers[i].letter}</div>}
</div>
</div>
)
});
return result;
}
render(){
return (
<div>
{this.renderApps()}
</div>
);
}
}
export default Example;
I don't know where I did wrong.
In the image as you can see I tried to click on the number three button to open the third div but number 1 and number 2 button is also getting opened. I want to open those div which is opened by me, others let be closed. If I will click on button number one then div number one should be open and the rest should be closed vice versa.
It is because you have the same state variable for all buttons.
You can create another component.
Then each mapped component will have its own state
Child Component:
import React from "react";
import {Col } from "react-bootstrap";
import "./style.scss"
class ChildExample extends React.Component{
constructor(props){
super(props);
this.state = {
opencollapse: true,
}
}
toggle = () => this.setState((currentState) => ({opencollapse: !currentState.opencollapse}));
render(){
return (
<div>
<div>
<div className="d-flex">
<Col>
<button onClick={this.toggle}>{this.state.opencollapse ? 'Click to close' : 'See Letter'}</button>
</Col>
<Col>
helloIcon
</Col>
</div>
{this.state.opencollapse && <div>{this.props.text}</div>}
</div>
</div>
);
}
}
export default ChildExample;
renderApps function in Example component:
renderApps = () => {
const result = this.props.userdata.appliers.map((item, i) => {
return(
<ChildExample text={this.props.userdata.appliers[i].letter}
)
});
return result;
}
A good solution would be to create a seperate component for your button and the content its hiding. This way we can give each component their own state.
A simplified example can be found below:
class ToggleElement extends React.Component {
constructor(props){
super(props);
this.state = {
opencollapse: true,
};
}
toggle = () => {
this.setState((currentState) => (
{opencollapse: !currentState.opencollapse}
));
}
render() {
return (
<div>
<button onClick={this.toggle}>{this.state.opencollapse ? 'Click to close' : 'See Letter'}</button>
{this.state.opencollapse && <div>{this.props.item.letter}</div>}
</div>
)
}
}
class Example extends React.Component {
constructor(props){
super(props);
}
renderApps() {
return this.props.data.map((item, i) => (
<ToggleElement item={item} />
));
}
render() {
return (
<div>
{this.renderApps()}
</div>
);
}
}
// Render it
ReactDOM.render(
<Example data={[
{letter: 'A'},
{letter: 'B'},
{letter: 'C'},
]} />,
document.body
);
<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>
your buttons have same state, you can try to create a different state for each button, or create an array of refs with your divs with content and connect this array to your buttons via some data-index and then open the only one div that matched to button's index
You can try making the state as array of states instead of one.
For example:
The state on constructor
constructor(props){
super(props);
let initOpenCollapse = [];
const n = 5; // This is set based on how many buttons you want to create
for (let i = 0; i < n; i++) initOpenCollapse.push(true);
this.state = {
opencollapse: initOpenCollapse,
}
}
The toggle function
toggle = (nthButton) => {
let resultState = [];
for (let i = 0; i < this.state.opencollapse.length; i++) {
if (i === nthButton)
resultState.push(!this.state.opencollapse[i]);
else
resultState.push(this.state.opencollapse[i]);
}
this.setState((currentState) => ({opencollapse: resultState}));
};
And in the render():
<button onClick={() => this.toggle(i)}>{this.state.opencollapse[i] ? 'Click to close' : 'See Letter'}</button>
...
...
{this.state.opencollapse[i] && <div>{this.props.userdata.appliers[i].letter}</div>}

How do I access and setState() of a parent component, from a child component onClick

I'm a young dev trying to learn some Reactjs, but I'm having trouble understanding how to configure this Todo app. My goal is to have a button that will add items to the list once entered and submitted. I feel like I'm pretty close to having it figured out.
I've got an App component (parent), button component, and a List component(also a header and item component). the list has a variable that has an empty array for me to add items to, which I reference in my App component.
Here lies the problem. I have an event listener on my button that runs a function that sets the state. I'm logging the list every time I click, which shows that the array is receiving the text inputs and making a new object. However, the DOM is not re-rendering what confuses me even more, is that when I make a slight edit (random semicolon) the DOM renders the items that were entered and logged before I last saved, but remains unresponsive.
What am I missing here? Also, I understand that lifecycle methods like componentDidMount() or componentDidUpdate() may be useful, but I do not fully understand how and where to use them.
export class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.setState(() => {
TodoList.push({id: (TodoList.length+1), name: input})
})
console.log(TodoList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button />
</div>
</div>
)
}
}
export default App
In your App.js, you should pass a function to <Button />, this technique called function as prop in react. The App.js code should look like below:
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
addTodo = (todo) => {
this.setState({ todos: [...this.state.todos, todo] })
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button todosList={this.state.todos} addTodo={(todo) => this.addTodo(todo)} />
</div>
</div>
)
}
In the code for Button.js, you get this function via this.props
export default class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.props.addTodo({id: this.props.todosList.length + 1, name: input })
console.log(this.props.todosList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}

React Component receive props but doesn't render it, why?

I have a page displaying user's books.
On this MyBooks page, React component mount. When it's mounted it fetch user's books through API. Then it update component's state with user's books.
mount component
fetch books through API
when we have results, update component's state
render again BooksList component (but it's not happening)
Here is my code for MyBooks component :
class MyBooks extends Component {
// TODO: fetch user info
constructor(props) {
super(props);
this.state = {
books: [],
errors: []
};
this.fetchBooks = this.fetchBooks.bind(this);
}
componentDidMount() {
console.log('component mounted!');
this.fetchBooks();
}
fetchBooks() {
let _this = this;
BooksLibraryApi.getBooks().then(foundBooks => {
console.log('books found:', foundBooks);
_this.setState({
books: foundBooks
});
});
}
render() {
console.log('MyBooks state:', this.state);
return (
<Section>
<Container>
<h1>My books</h1>
<BooksList books={this.state.books} />
</Container>
</Section>
);
}
}
export default withRouter(MyBooks);
Here is the result for console.log('books found:', foundBooks):
Here is my code for BooksList component :
class BooksList extends React.Component {
render() {
console.log('BooksList props:', this.props);
return (
<Columns breakpoint="mobile">
{this.props.books.map((book, i) => {
console.log(book);
return (
<Columns.Column
key={i}
mobile={{ size: 'half' }}
desktop={{ size: 2 }}
>
<BookCard book={book} />
</Columns.Column>
);
})}
</Columns>
);
}
}
export default BooksList;
Here is the code for BookCard component:
class BookCard extends React.Component {
constructor(props) {
super(props);
console.log('props', props);
this.readBook = this.readBook.bind(this);
this.addBook = this.addBook.bind(this);
this.deleteBook = this.deleteBook.bind(this);
this.wantBook = this.wantBook.bind(this);
}
readBook() {
BooksLibraryApi.readBook(this.props.book.id);
}
addBook() {
BooksLibraryApi.addBook(this.props.book.id);
}
wantBook() {
BooksLibraryApi.wantBook(this.props.book.id);
}
deleteBook(e) {
BooksLibraryApi.deleteBook(this.props.book.id, e);
}
render() {
return (
<div className="card-book">
<Link to={`/book/${this.props.book.id}`}>
{this.props.book.doHaveThumbnail ? (
<Image
alt="Cover"
src={this.props.book.thumbnailUrl}
size={'2by3'}
/>
) : (
<div className="placeholder">
<span>{this.props.book.title}</span>
</div>
)}
</Link>
<Button fullwidth color="primary" size="small" onClick={this.wantBook}>
Add to wishlist
</Button>
</div>
);
}
}
export default withRouter(BookCard);
The console.log in BooksList component is not called. Which means that the component is render only one time, when the this.props.books array is empty.
I don't understand why BooksList is not rendered again when his props are updated (when MyBooks component has his state updated).
Strange behavior: I'm using React Router, and when I first click on the link "My books" (which go to my MyBooks component), it doesn't work, but when I click again on it, everything works fine. Which means that something is wrong with rendering / component's lifecyles.
Thanks.

ReactJS instant Search with input

Im making my first react project. Im new in JS, HTML, CSS and even web app programming.
What i want to do it is a Search input label. Now its look like this:
Like you can see i have some list of objects and text input.
I Have two components, my ProjectList.js with Search.js component...
class ProjectsList extends Component {
render() {
return (
<div>
<Search projects={this.props.projects} />
<ListGroup>
{this.props.projects.map(project => {
return <Project project={project} key={project.id} />;
})}
</ListGroup>
</div>
);
}
}
export default ProjectsList;
... and ProjectList.js displays Project.js:
How looks Search.js (its not ended component)
class Search extends Component {
state = {
query: ""
};
handleInputChange = () => {
this.setState({
query: this.search.value
});
};
render() {
return (
<form>
<input
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
<p />
</form>
);
}
}
export default Search;
My project have name property. Could you tell me how to code Search.js component poperly, to change displaying projects dynamically based on input in text label? for example, return Project only, if text from input match (i want to search it dynamically, when i start typing m... it shows all projects started on m etc).
How to make that Search input properly? How to make it to be universal, for example to Search in another list of objects? And how to get input from Search back to Parent component?
For now, in react dev tools whatever i type there i get length: 0
Thanks for any advices!
EDIT:
If needed, my Project.js component:
class Project extends Component {
state = {
showDetails: false
};
constructor(props) {
super(props);
this.state = {
showDetails: false
};
}
toggleShowProjects = () => {
this.setState(prevState => ({
showDetails: !prevState.showDetails
}));
};
render() {
return (
<ButtonToolbar>
<ListGroupItem className="spread">
{this.props.project.name}
</ListGroupItem>
<Button onClick={this.toggleShowProjects} bsStyle="primary">
Details
</Button>
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
</ButtonToolbar>
);
}
}
export default Project;
To create a "generic" search box, perhaps you could do something like the following:
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
This revised version of Search takes some additional props which allows it to be reused as required. In addition to the projects prop, you also pass filterProject and onUpdateProjects callbacks which are provided by calling code. The filterProject callback allows you to provide custom filtering logic for each <Search/> component rendered. The onUpdateProjects callback basically returns the "filtered list" of projects, suitable for rendering in the parent component (ie <ProjectList/>).
The only other significant change here is the addition of visibleProjects to the state of <ProjectList/> which tracks the visible (ie filtered) projects from the original list of projects passed to <ProjectList/>:
class Project extends React.Component {
render() {
return (
<div>{ this.props.project }</div>
);
}
}
class ProjectsList extends React.Component {
componentWillMount() {
this.setState({ visibleProjects : [] })
}
render() {
return (
<div>
<Search projects={this.props.projects} filterProject={ (query,project) => (project == query) } onUpdateProjects={ projects => this.setState({ visibleProjects : projects }) } />
<div>
{this.state.visibleProjects.map(project => {
return <Project project={project} key={project.id} />;
})}
</div>
</div>
);
}
}
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
ReactDOM.render(
<ProjectsList projects={[0,1,2,3]} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I will assumes both your Search and ProjectList component have a common parent that contains the list of your projects.
If so, you should pass a function into your Search component props, your Search component will then call this function when the user typed something in the search bar. This will help your parent element decide what your ProjectsLists needs to render :
handleInputChange = () => {
this.props.userSearchInput(this.search.value);
this.setState({
query: this.search.value
});
};
And now, here is what the parent element needs to include :
searchChanged = searchString => {
const filteredProjects = this.state.projects.filter(project => project.name.includes(searchString))
this.setState({ filteredProjects })
}
With this function, you will filter out the projects that includes the string the user typed in their names, you will then only need to put this array in your state and pass it to your ProjectsList component props
You can find the documentation of the String includes function here
You can now add this function to the props of your Search component when creating it :
<Search userSearchInput={searchChanged}/>
And pass the filtered array into your ProjectsList props :
<ProjectsList projects={this.state.filteredProjects}/>
Side note : Try to avoid using refs, the onCHnage function will send an "event" object to your function, containing everything about what the user typed :
handleInputChange = event => {
const { value } = event.target
this.props.userSearchInput(value);
this.setState({
query: value
});
};
You can now remove the ref from your code

ReactJs : How to reload a component onClick

I am new to React and am still learning it. I have a component Requirements which I would like to reload everytime getDocFinancialInfo () is called by clicking on the event. The issue is that it loads the correct information the first time but does not refreshes it on subsequent clicks. Any help or suggestion would be most welcome.
import React, { Component } from 'react';
import './UploadDocument.css'
import spinner from './spinner.gif'
import verified from './verified.png';
import notverified from './not-verified.png';
import Requirements from './Requirement.js'
class UploadDocument extends Component{
constructor(props) {
super(props);
this.state = {
application: [],
document:[],
id: null,
files: [],
docFinacialInfo: [],
uploaded: null,
fileInfo: []
}
}
componentDidMount() {
......
}
getDocFinancialInfo = async(docId) => {
sessionStorage.setItem('docId',docId);
var req = document.getElementById('requirements');
req.style.display = "block";
}
render(){
......
if(notVerifiedStatus > 0){
docVerificationStatus[index] = <td className="red"><img src={notverified} alt="Not Verified"/><label onClick={()=>this.getDocFinancialInfo(docId)}>Not Verified{docId}</label></td>;
}else{
docVerificationStatus[index] = <td className="green"><img src={verified} alt="Verified" /><label>Verified</label></td>;
}
console.log("Not Verified >>>"+notVerifiedStatus);
});
......
return(
<div>
.........
<div id="requirements">
<div id="requirements-content">
<span className="close" onClick={()=>this.closeRequirements()}>×</span>
<Requirements />
</div>
</div>
.........
</div>
)
}
}
export default UploadDocument
You can change the key prop given to a component in order to unmount it and mount a new one.
You could keep a random value in your state that you randomise again when getDocFinancialInfo is called and use this value as the key for Requirements.
Example
class Requirements extends React.Component {
state = { count: 0 };
componentDidMount() {
this.interval = setInterval(() => {
this.setState(({ count }) => ({ count: count + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <div> {this.state.count}</div>;
}
}
class UploadDocument extends React.Component {
state = {
requirementKey: Math.random()
};
getDocFinancialInfo = docId => {
this.setState({ requirementKey: Math.random() });
};
render() {
return (
<div>
<Requirements key={this.state.requirementKey} />
<button onClick={this.getDocFinancialInfo}> Reload </button>
</div>
);
}
}
ReactDOM.render(<UploadDocument />, document.getElementById("root"));
<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>
As mentioned above - change key value is a great idea
But instead of using Math.random I prefer using new Date() value to be sure that key is changed anyway :)
You can use vanilla Javascript to call reload method window.location.reload(false)
<button onClick={() => window.location.reload(false)}>Click to reload!</button>

Categories

Resources