Component not re-rendered after updating the state (mapStateToProps called) - javascript

I'm having a trouble updating the state. I'm using redux so the state is updated on the reducer level. After updating the state from another component a new state is returned with new data. mapStateToProps is called but the component is not re-rendering.
This is my component
class Users extends Component {
render() {
console.log("RENDERING ------");
const usernames= this.props.usernames.map((username, key) => {
return (<div key={key} className="card mt-2">
<div className="card-body">
{username}
</div>
</div>)
})
return (
<div data-spy="scroll" data-target="#navbar-example3" data-offset="0">
{usernames}
</div>
)
}
}
const mapStateToProps = state => {
console.log("STATE", state);
return {
usernames: state.usernames.data
}
}
export default connect(mapStateToProps, null)(Users);
when loading the component the usernames are displayed. but adding a new username from another component the mapStateToProps is called but the component is not re-rendered.
Parent Component
class Parent extends Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col">
<Editor />
</div>
<div className="col">
<Users />
</div>
</div>
</div>
)
}
This is the editor component where I'm dispatching the action
class Editor extends Component {
state = {
user: ""
}
handleChange = event => {
this.setState({ user: event.target.value });
}
onSubmit = e => {
e.preventDefault();
this.props.addUser(this.state.user);
}
render() {
return (
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label htmlFor="exampleFormControlTextarea1">User</label>
<textarea className="form-control" id="exampleFormControlTextarea1" rows="3" value={this.state.user} onChange={this.handleChange} ></textarea>
</div>
<button type="submit" className="btn btn-primary mb-2">Submit</button>
</form>
)
}
}
const mapDispatchToProps = dispatch => {
return {
addUser: (user) =>
dispatch(addUser(user))
}
}
export default connect(null, mapDispatchToProps)(Editor);

After trying and searching online. I discovered that the problem was that the component re-render if mapStateToProps returns a different value from the last call. For my case I was handling my state on a mutable way. I was using push to add new user to the state on the reducer. The solution is to do it in the immutable way using concat:
const us = [];
us.push(action.results.user);
return { ...state, users: state.users.concat(us) };

You are mapping the usernames prop in your mapStateToProps, but not making use of it in your component. You are in fact creating a new variable called usernames and assigning it to the result of a map on this.props.messages.
Forgive me if I'm not understanding what you're trying to do, but it looks like you should just make use of this.props.usernames, and then when you update it through a dispatch, the prop will update and in turn the component will re-render.

Related

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>
)
}

How to pass props without rendering the component?

I have two components one called ReturningCustomer and the other one called Update. I have a phone number saved in state in ReturningCustomer and I want to pass that phone number to the Update component with having to render Update or use Redux:
render() {
return (
<div className="row returning-customer">
<h3>Returning Customer</h3>
<div className="col">
<p className="error">{this.state.error}</p>
<form className="row" onSubmit={this.onSubmit}>
<label>Phone Number</label>
<input
type="text"
className="validate"
onChange={this.onPhoneNumberChange}
/>
<button className="btn">Go</button>
</form>
</div>
<Update
phoneNumber={this.state.phoneNumber}
/>
</div>
);
}
In the render function for Update you could use either one of the following and the component would not render
render() {
return false;
}
render() {
return null;
}
render() {
return [];
}
render() {
return <></>;
}
It's not entirely clear what you're asking based on the current description, but it sounds like you're either wanting to conditionally render a component while passing props, or you want to know how to use those props? I'm including both here.
Conditionally rendering a component and passing props:
// SomeComponent.js
import React from 'react'
import Update from '../..'
class SomeComponent extends React.Component {
state = { phoneNumber: '' };
renderUpdate() {
const someCondition = true;
if (someCondition) {
return <Update phoneNumber={this.state.phoneNumber} />
}
return null
}
render() {
return (
<div>
{this.renderUpdate()}
</div>
)
}
}
export default SomeComponent
Using props:
// Update.js
import React from 'react'
// destructuring the props object here
const Update = ({ phoneNumber }) => (
<div>
<p>{phoneNumber}</p>
</div>
)
export default Update

Keypress is failing to record all keystrokes

I'm trying to capture keystrokes from a textarea and have tried using attributes onKeyUp, onKeyPress, onKeyPressCapture, onKeyDown, onKeyDownCapture. All of them seem to miss some key entries:
When I enter a new key, one of the ones that was not displaying before then shows, in order.
Because of that queued delay, I'm thinking I might need to put a delay on the console log. But that doesn't actually solve the underlying issue. Does anyone know why this behavior is happening?
Here is the parent (App) and child component (TypeArea)
Parent
class App extends React.Component {
constructor (props) {
super(props)
// sets up this.props to function
this.state = {
textbox_in_parent_state: 'string passed from state of Parent(App)',
someVar: 'parent_constructor_state',
text_from_textarea: ''
}
this.handler = this.handler.bind(this)
this.text_capture_from_parent = this.text_capture_from_parent.bind(this)
}
text_capture_from_parent(eventObject) {
this.setState({
text_from_textarea: eventObject.target.value
})
console.log(this.state.text_from_textarea)
}
render() {
return (
<div>
<div className="container">
<Header />
<div className="row">
<div className="col-sm-6">
<TypeArea textcapture={this.text_capture_from_parent}
/>
</div>
<div className="col-sm-6">
<MarkdownPreview />
</div>
</div>
</div>
{/* <div style={{textAlign: 'center'}}>*/}
{/* <h1>App component written in client/components/App.jsx</h1>*/}
{/* </div>*/}
</div>
)
}
}
export default App
Child
import React from 'react';
class TypeArea extends React.Component {
constructor(props) {
super(props)
this.state = {
textbox_text: 'string from state of child "TypeArea"'
}
console.log(this.state.textbox_text)
}
render() {
return (
<div>
<h1>Typing Area</h1>
<div className="form-group">
<textarea className="form-control" id="textbox" rows="25" placeholder="Type Here" onKeyPressCapture={this.props.textcapture}>
</textarea>
<button onClick={this.props.passdown}>Click me</button>
</div>
</div>);
}
}
export default TypeArea
text_capture_from_parent(eventObject) {
this.setState({
text_from_textarea: eventObject.target.value
})
console.log(this.state.text_from_textarea)
}
this.setState() is async, and you are calling directly console.log(..) after setting the state, at this moment the state maybe didn't successfully changed already. but luckily this.setState(..)is providing a callback when it finished setting the new state. so you can call it like this:
this.setState({
text_from_textarea: eventObject.target.value
}), () => {
console.log(this.state.text_from_textarea);
});
and you should see the actual value.

React Component not updating with state change

I currently have a reducer that does a deep copy of state and returns it with the updated value.
function countableItems(state = initialState, action) {
switch (action.type) {
case types.ADD_TO_SUM:
let denomMap = findDenomination(state.denomGroups, action),
nestedCopy = Immutable.fromJS(state);
return nestedCopy.setIn(['denomGroups', denomMap.group, denomMap.key, denomMap.index, 'sum'], parseFloat(action.value)).toJS();
default:
return state;
}
}
In my render function of the display Component I see the correct updated values in this.props.denoms The render() function builds up child <DenomInput> components, and when I set my breakpoints I see the correct data being passed in
render() {
let denomGroups = this.props.denoms.map((denom, i) => {
return (
Object.keys(denom).map((key) => {
let denoms = denom[key].map((item, i) => {
return <DenomInput denom={item} onDenomChange={this.onDenomChange} key={i}></DenomInput>
});
return (<div className="col"><h2>{key}</h2>{denoms}</div>)
})
);
});
return (
<div className="countable-item-wrapper">
<div className="row">
{denomGroups}
</div>
</div>
);
}
However when the <DenomInput> components render it renders the same value as what they were initially set
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class DenomInput extends Component {
constructor(props) {
super(props);
this.state = { denom: props.denom }
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp = (e) => {
this.props.onDenomChange(e.target.value, this.state.denom.name);
}
render() {
return (
<div className="input-group denom">
<span className="input-group-addon">{this.state.denom.label}</span>
<input
type="text"
className="form-control"
onChange={this.handleKeyUp}
value={this.state.denom.sum} />
<span className="input-group-addon">{this.state.denom.count | 0}</span>
</div>
);
}
}
DenomInput.PropTypes = {
denom: PropTypes.object.isRequired,
onDenomChange: PropTypes.function
}
export default DenomInput;
What piece am I missing to update the view with React and Redux?
May be componentWillReceiveProps can do the trick. It will update the state of the component whenever new data is receive from parent, and call the render function again.
Try
class DenomInput extends Component {
...
componentWillReceiveProps(nextProps) {
this.setState({ denom: nextProps.denom })
}
...
}
It looks like you're seeding your initial state with the props from your store. You then render from the component state, but you never update the component state. They only get set once because constructor is only called once the component is rendered. To fix, either remove this component state entirely and just connect it to the redux store, or update the component state onChange. I recommend removing the local state. I have found that keeping the two states in sync is error-prone.
constructor(props) {
super(props);
this.state = { denom: props.denom }
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp = (e) => {
this.props.onDenomChange(e.target.value, this.state.denom.name);
this.setState({ denom: /*new state identitcal to change in redux store*/ })
}
edit2: An example of raising state up. The steps are:
1. Connect one of your parent components and grab the appropriate slice of state with a mapStateToProps function.
2. Pass the props through your connected parent component to DenomInput.
4. In this.denomsChange, dispatch the appropriate action. It is unclear what this is since you did not include your action in the post.
class DenomInput extends Component {
...
render() {
return (
<div className="input-group denom">
<span className="input-group-addon">{this.props.denom.label}</span>
<input
type="text"
className="form-control"
onChange={this.handleKeyUp}
value={this.props.denom.sum} />
<span className="input-group-addon">{this.props.denom.count | 0}</span>
</div>
);
}
}
export default DenomInput;

updating react state when filtering redux props

I am new to react & redux and having a bit of trouble filtering what I am calling insights (articles, case studies, reports). I want to filter by industry, and I cannot get to the point where the state is updating with the filtered insights.
InsightsPage.js
const FilterLink = ({
filter,
data,
children
}) => {
return(
<a href="#"
onClick={e => {
e.preventDefault();
getVisibleInsights(
data,
filter
)
}}
>{children}
</a>
);
};
const getVisibleInsights = (
insights,
filter
) => {
switch (filter) {
case 'SHOW_ALL':
return insights;
case 'SHOW_AEROSPACE':
return insights.filter(
i => i.industry == 'aerospace');
case 'SHOW_HEALTHCARE':
return insights.filter(
i => i.industry == 'healthcare');
}
}
class InsightsPage extends React.Component {
render() {
const insights = this.props.insightsPageData.insights;
return(
<div className="col-md-12">
<h2>{this.props.insightsPageData.header}</h2>
<div className="col-md-4">
<InsightsList insights={this.props.visibleInsights} />
</div>
<div className="col-md-8">
{this.props.children}
</div>
<p>
Show:
{' '}
<FilterLink filter="SHOW_ALL" data={insights}>All</FilterLink>
{' '}
<FilterLink filter="SHOW_AEROSPACE" data={insights}>Aerospace</FilterLink>
{' '}
<FilterLink filter="SHOW_HEALTHCARE" data={insights}>Healthcare</FilterLink>
</p>
</div>
);
}
}
InsightsPage.propTypes = {
insightsPageData: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
insightsPageData: state.insightsPageData,
visibilityFilter: state.insightsPageData.visibilityFilter,
visibleInsights: getVisibleInsights(state.insightsPageData.insights, state.insightsPageData.visibilityFilter )
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(InsightsPage);
I am initializing my state with the visibilityFilter set to 'SHOW_ALL'
initialState.js
export default {
employeesPageData: {employees: [], page_info:{stats: []}},
insightsPageData: {insights:[], visibilityFilter: 'SHOW_ALL'},
newsArticlesPageData: {news_articles:[], page_info:{}},
}
So, what is the best way to update the state of my InsightsPage? They all show at the beginning, so my InsightsList is working. Thanks so much.
Your <FilterLinks> functional component expects 3 props. You are pushing in the first, but you are not pushing in the prop for data. Amend it to do so as follows:
<FilterLink filter="SHOW_AEROSPACE" data={this.props.insightsPageData} updateInsights={this.updatInsights.bind(this)} >Aerospace</FilterLink>
// Where updateInsights is something like:
function updateInsights (updated_insights){
this.setState({insights: updated_insights});
}
You can then modify the onClick to call:
this.props.updateInsights(getVisibleInsights(data, filter));
Be aware that this approsch only sets the local component state of InsightsPage and not your Redux store.
To achieve the latter:
You'd have to use the new state on your reducer. So: dispatch action on click, that action fires a reducer which uses the filtered state. The change will then propogate down the component tree

Categories

Resources