React form handleChange is not updating state - react-redux

Form input onChange is not updating state. The action and reducer fire properly and backend rails API is updated, but the state does not change and nothing renders in the DOM.
I have used console logs to ensure that the action and reducer are working properly.
import React, { Component } from 'react';
import { Button, Form } from 'semantic-ui-react'
import { connect } from 'react-redux'
class TaskInput extends Component {
constructor() {
super()
this.state = {
name: ""
}
}
handleChange = (e) => {
this.setState({
name: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.addTask({name: this.state.name}, this.props.goal.id)
this.setState({
name: ''
})
}
render() {
return (
<Form className="new-task-form" onSubmit={(e) =>this.handleSubmit(e)}>
<Form.Field>
<label className="form-label">Add Task</label>
<input id="name" required value={this.state.name} onChange={(e) => this.handleChange(e)} />
</Form.Field>
<Button basic color='purple' type="submit">Add Task</Button>
<hr/>
</Form>
)
}
}
export default connect()(TaskInput)
import React, { Component } from 'react'
import { addTask, deleteTask } from '../actions/tasksActions'
import { connect } from 'react-redux'
import { fetchGoal } from '../actions/goalsActions'
import Tasks from '../components/Tasks/Tasks';
import TaskInput from '../components/Tasks/TaskInput';
class TasksContainer extends Component {
componentDidMount() {
this.props.fetchGoal(this.props.goal.id)
}
render(){
return(
<div>
<TaskInput
goal={this.props.goal}
addTask={this.props.addTask}
/>
<strong>Tasks:</strong>
<Tasks
key={this.props.goal.id}
tasks={this.props.goal.tasks}
deleteTask={this.props.deleteTask}
/>
</div>
)
}
}
const mapStateToProps = state => ({
tasks: state.tasksData
})
export default connect(mapStateToProps, { addTask, deleteTask, fetchGoal })(TasksContainer);
export default function taskReducer(state = {
loading: false,
tasksData: []
},action){
switch(action.type){
case 'FETCH_TASKS':
return {...state, tasksData: action.payload.tasks}
case 'LOADING_TASKS':
return {...state, loading: true}
case 'CREATE_TASK':
console.log('CREATE Task', action.payload )
return {...state, tasksData:[...state.tasksData, action.payload.task]}
case 'DELETE_TASK':
return {...state, loading: false, tasksData: state.tasksData.filter(task => task.id !== action.payload.id )}
default:
return state;
}
}
handleSubmit calls the action to addTask. handleChange updates state and renders the new task in DOM. handleSubmit is working. handleChange is not.

Related

RematchJS Refresh Issue

I am new at using RematchJS and have managed to display hardcoded Objects from an array. The dispatcher (addTopicAsync) however does not seems to update the Array, when I try to add a new Object.
The Array is briefly updated and the topic is flashes on the screen, but the Array is empty shortly afterwards.
My model code:
import { createModel } from "#rematch/core";
import { RootModel } from ".";
export interface Topic {
topic: String
}
export interface TopicsList {
list: Array<Topic>
}
const TOPICS_LIST_STATE = {
list: []
}
export const topics = createModel<RootModel>()({
state: TOPICS_LIST_STATE as TopicsList,
reducers: {
addTopic: (state, topic) => {
return { list: [...state.list, { topic }] }
},
clearTopics: () => {
return { list: [] }
}
},
effects: (dispatch) => ({
async addTopicAsync(topic: string) {
await dispatch.topics.addTopic(topic)
},
async clearTopicsAsync() {
await dispatch.topics.clearTopics()
}
})
});
My application code:
// eslint-disable-next-line #typescript-eslint/no-unused-vars
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, Dispatch } from '#vanilla/data'
import { Topic } from 'libs/data/src/lib/models/topics';
export function App() {
const [topic, setTopic] = useState("aaa")
const topicsState = useSelector((state: RootState) => state.topics)
const dispatch = useDispatch<Dispatch>()
const topicChange = (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault()
setTopic(event.target.value)
}
const updateTopicList = async () => {
await dispatch.topics.addTopicAsync(topic)
console.log('topicsState : ', topicsState.list)
}
return (
<>
<h3>Topics</h3>
<form>
<input type='text' value={topic} onChange={topicChange} />
<button onClick={() => { updateTopicList() }}> Add Topic</button>
</form>
<div className="container">
{topicsState.list.map((topicRecord: Topic, index: number) => (
<h5 key={index}>
{topicRecord.topic}
</h5>
))}
</div>
</>
)
}
export default (App)

State updates but Component doesn't re-render

I'm creating a simple react-redux chat application. I managed to display some dummy data from my redux state in my Message component. I succeed to push a new 'message' to the redux state from my Submit component. But the new item doesn´t render in the Message component.
So I tried to console log the previous state and the new state from the messageReducer and it seems to work. I get the state array with all the dummy data + the new pushed object.
Here is the Github repo if needed: https://github.com/MichalK98/Chat.V.2
// Message Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Message extends Component {
render() {
return (
<ul id="chatroom">
{this.props.messages.map((msg) => (
<li className={(msg.username == 'You' ? "chat-me" : "")} key={msg.id}>
<p>{msg.message}</p>
<small>{msg.username}</small>
</li>
)).reverse()}
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
messages: state.message.messages
}
}
export default connect(mapStateToProps)(Message);
// Submit Component
...
import { connect } from 'react-redux';
...
class Submit extends Component {
state = {
message: []
}
clear = async () => {
await this.setState({
message: ''
});
}
handleChange = async (e) => {
await this.setState({
message: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.writeMessage(this.state.message);
this.clear();
}
render() {
return (
<div className="chat-footer">
<form onSubmit={this.handleSubmit} autoComplete="off">
<input onChange={this.handleChange} value={this.state.message} type="text" placeholder="Skriv något..."/>
<button id="btnSend"><SendSvg/></button>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
messages: state.messages
}
}
const mapDispatchToProps = (dispatch) => {
return {
writeMessage: (message) => { dispatch({type: 'WRITE_MESSAGE', messages: {id: Math.random(), username: 'You', message: message}})}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Submit);
// messageReducer
const initState = {
messages: [
{id: 1, username: 'You', message: 'Hi, data from reducer!'},
{id: 2, username: 'Mattias', message: 'Wow..'},
{id: 3, username: 'Alien', message: 'Awesome!'}
]
}
const messageReducer = (state = initState, action) => {
if (action.type === 'WRITE_MESSAGE') {
state.messages.push(action.messages);
console.log('Action ',action.messages);
console.log('State ',state.messages);
}
return state;
}
export default messageReducer;
I expect that the new data will render in my Message component when I add a new object to the state array in messageReducer.
first of all in your Message Component you should Change mapStateToProps :
const mapStateToProps = (state) => {
return {
messages : state.messages
}
}
And then in your message reducer you should change reducer. this is better way for reducer. you shouldn't directly change state :
const messageReducer = (state = initState, action) => {
switch (action.type) {
case "WRITE_MESSAGE":
return { ...state, messages: [...state.messages, { ...action.messages }] }
default:
return state;
}
}
if you need any help i can help you to complete your project :-)

Reset after save bug - Not restoring to correct initialValues

I am having a very weird bug. I have reproduced the simplest test case scenario here: https://codesandbox.io/s/PNyPwyWP2
I have also uploaded a screencast explaining, the screencast is on youtube here - https://youtu.be/iILiFieO-gk
My goal is that I have a form with a single field, a button "Reset" and a button "Save". Clicking "Save" saves the form values into a reducer in my store called save. Clicking "Reset" should reset the form values to the last "pristine" values (the values in initialValues).
However my issue is, after saving the form, the "Reset" button should reset it to the "pristine" value (the newly saved value, the value in initialValues) but it is reseting it to the "old pristine value"
Here is my full app code:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import { connect } from 'react-redux'
import { Field, reduxForm, reducer as form } from 'redux-form'
// ACTION & ACTION CREATOR
const SAVE_FORM = 'SAVE_FORM';
function saveForm(values) {
return {
type: SAVE_FORM,
values
}
}
// REDUCER - save
const INITIAL = { url:'hiiii' };
function save(state=INITIAL, action) {
switch(action.type) {
case SAVE_FORM: return action.values;
default: return state;
}
}
// STORE
const reducers = combineReducers({ form, save });
const store = createStore(reducers);
// MY FORM COMPONENT
class MyFormDumb extends React.Component {
handleReset = e => {
e.preventDefault();
this.props.reset();
}
render() {
console.log('MyFormDumb :: pristine:', this.props.pristine, 'initialValues:', this.props.initialValues);
return (
<form onSubmit={this.props.handleSubmit}>
<label htmlFor="url">URL</label>
<Field name="url" component="input" type="text" />
<button onClick={this.handleReset}>Reset</button>
<button type="submit">Save</button>
</form>
)
}
}
const MyFormControlled = reduxForm({ form:'my-form' });
const MyFormSmart = connect(
function(state) {
return {
initialValues: state.save
}
}
);
const MyForm = MyFormSmart(MyFormControlled(MyFormDumb));
// MY APP COMPONENT
class App extends React.PureComponent {
submitHandler = (values, dispatch, formProps) => {
dispatch(saveForm(values));
}
render() {
return (
<Provider store={store}>
<div className="app">
<MyForm onSubmit={this.submitHandler} />
</div>
</Provider>
)
}
}
// RENDER
ReactDOM.render(<App />, document.getElementById('app'))
Please use enableReinitialize: true flag on your reduxForm component, as per the docs.

relay refetch doesn't show the result

I'm trying to create a live search-result component(lazy load one). It works perfectly for the first time but refetch doesn't update the data. I see the request and respoonse in Network tab! so it does get the data, but it doesn't supply it to the component!
any idea why?
import React, { Component } from 'react';
import {
createRefetchContainer,
graphql,
} from 'react-relay';
import ProfileShow from './ProfileShow';
class ProfileList extends Component {
render() {
console.log("rendering....", this.props)
return (
<div className="row">
<input type="text" onClick={this._loadMe.bind(this)} />
{this.props.persons.map((person) => {
return (
<div className="col-md-3">
<ProfileShow person={person} />
</div>
);
})}
</div>
);
}
_loadMe(e) {
const refetchVariables = fragmentVariables => ({
queryStr: e.target.value,
});
this.props.relay.refetch(refetchVariables, null, (...data) => {
console.log(data)
});
}
}
const FragmentContainer = createRefetchContainer(
ProfileList,
{
persons: graphql.experimental`
fragment ProfileList_persons on Person #relay(plural: true) {
fullname
number
email
pic
}
`
},
graphql.experimental`
query ProfileListRefetchQuery($queryStr: String!) {
talentList(query: $queryStr) {
...ProfileList_persons
}
}
`,
);
export default FragmentContainer;

Validation Error Message not getting displayed for custom validation in Angular 2

I have a register form where user need to provide username. When customer enters username, I want to show validation error message if that username already exists in db or not.
register.html
<-- code here-->
<div class="form-group">
<label for="username" class="col-sm-3 control-label">UserName</label>
<div class=" col-sm-6">
<input type="text" ngControl="userName" maxlength="45" class="form-control" [(ngModel)]="parent.userName" placeholder="UserName" #userName="ngForm" required data-is-unique/>
<validation-message control="userName"></validation-message>
</div>
</div>
<--code here-->
register.component.ts
import {Component} from 'angular2/core';
import {NgForm, FormBuilder, Validators, FORM_DIRECTIVES} from 'angular2/common';
import {ValidationService} from '../services/validation.service';
import {ValidationMessages} from './validation-messages.component';
#Component({
selector: 'register',
templateUrl: './views/register.html',
directives: [ROUTER_DIRECTIVES, ValidationMessages, FORM_DIRECTIVES],
providers: []
})
export class ParentSignUpComponent {
parentSignUpForm: any;
constructor(private _formBuilder: FormBuilder) {
this._stateService.isAuthenticatedEvent.subscribe(value => {
this.onAuthenticationEvent(value);
});
this.parent = new ParentSignUpModel();
this.parentSignUpForm = this._formBuilder.group({
'firstName': ['', Validators.compose([Validators.required, Validators.maxLength(45), ValidationService.nameValidator])],
'middleName': ['', Validators.compose([Validators.maxLength(45), ValidationService.nameValidator])],
'lastName': ['', Validators.compose([Validators.required, Validators.maxLength(45), ValidationService.nameValidator])],
'userName': ['', Validators.compose([Validators.required, ValidationService.checkUserName])]
});
}
}
validation-message.component
import {Component, Host} from 'angular2/core';
import {NgFormModel} from 'angular2/common';
import {ValidationService} from '../services/validation.service';
#Component({
selector: 'validation-message',
inputs: ['validationName: control'],
template: `<div *ngIf="errorMessage !== null" class="error-message"> {{errorMessage}}</div>`
})
export class ValidationMessages {
private validationName: string;
constructor (#Host() private _formDir: NgFormModel) {}
get errorMessage() {
let control = this._formDir.form.find(this.validationName);
for (let propertyName in control.errors) {
if (control.errors.hasOwnProperty(propertyName) && control.touched) {
return ValidationService.getValidatorErrorMessage(propertyName);
}
}
return null;
}
}
validation-service.ts
import {Injectable, Injector} from 'angular2/core';
import {Control} from 'angular2/common';
import {Observable} from 'rxjs/Observable';
import {Http, Response, HTTP_PROVIDERS} from 'angular2/http';
import 'rxjs/Rx';
interface ValidationResult {
[key:string]:boolean;
}
#Injectable()
export class ValidationService {
static getValidatorErrorMessage(code: string) {
let config = {
'required': 'This field is required!',
'maxLength': 'Field is too long!',
'invalidName': 'This field can contain only alphabets, space, dot, hyphen, and apostrophe.',
'userAlreadyInUse': 'UserName selected already in use! Please try another.'
};
return config[code];
}
static checkUserName(control: Control): Promise<ValidationResult> {
let injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
let http = injector.get(Http);
let alreadyExists: boolean;
if (control.value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
http.get('/isUserNameUnique/' + control.value).map(response => response.json()).subscribe(result => {
if (result === false) {
resolve({'userAlreadyInUse': true});
} else {
resolve(null);
}
});
}, 1000);
});
}
}
}
Now, when i run, and give a username that already exists in db, the value of 'result' variable i am getting as false, which is expected and correct. But validation error message is not getting displayed. I am able to run and get validation error message for other custom validation functions. I am using Angular 2.0.0-beta.15. Can somebody help me to understand what could be the issue?
There are some known issues with async validation
https://github.com/angular/angular/issues/1068
https://github.com/angular/angular/issues/7538
https://github.com/angular/angular/issues/8118
https://github.com/angular/angular/issues/8923
https://github.com/angular/angular/issues/8022
This code can be simplified
return new Promise((resolve, reject) => {
setTimeout(() => {
http.get('/isUserNameUnique/' + control.value).map(response => response.json())
.subscribe(result => {
if (result === false) {
resolve({'userAlreadyInUse': true});
} else {
resolve(null);
}
});
}, 1000);
});
to
return http.get('/isUserNameUnique/' + control.value).map(response => response.json())
.timeout(200, new Error('Timeout has occurred.'));
.map(result => {
if (result === false) {
resolve({'userAlreadyInUse': true});
} else {
resolve(null);
}
}).toPromise();
Don't forget to import map, timeout, and toPromise.
If you use subscribe() instead of then() on the caller site, then you can event omit toPromise()
if you look into this -
'userName': ['', Validators.compose([Validators.required, ValidationService.checkUserName])] });
-- you can see I am using both synchronous and asynchronous validations together. When i changed method for checkUserName like 'Validators.composeAsync(ValidationService.checkUserName)' instead of Validators.compose method, error message got displayed.

Resources