Redux update state only after a full page reload - reactjs

I have a form that is per-populated based on user saved information and the user can change those information at any time.
A simple example of how this is structured is:
componentWillMount() {
const { user, getProfile } = this.props;
if (user.profileId) {
getProfile(user.profileId); // this is a redux action that fetch for user profile data updating the props.
}
}
onChangeValue(e) {
const { updateProfileField } = this.props; // this is a redux action to update a single input
const { id, value } = e.target;
updateProfileField(id, value);
}
Inputs looks like this:
<InputField id="screenName" type="text" value={profile.screenName} onChangeValue={this.onChangeValue} placeholder="Your username" />
The InputField component is the following:
class InputField extends Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.onChangeValue = this.onChangeValue.bind(this);
}
onChangeValue(value) {
this.props.onChangeValue(value);
}
render() {
const {
id, type, parent,
} = this.props;
return (
<input
id={id}
parent-id={parent}
className="form-control"
type={type}
name={id}
value={this.state.value}
onChange={this.onChangeValue}
/>
);
}
}
The Redux action to handle the updateProfileField is:
export function updateProfileField(field, value) {
const payload = { field, value };
return dispatch => dispatch({payload, type: types.UPDATE_PROFILE_FIELD});
}
Finally the Reducer:
const initialState = {
data: {}, // Here are stored the profile information like `screenName`
...
};
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
...
case types.UPDATE_PROFILE_FIELD:
// Here my attempts to update the input value
return {
...state,
data: { ...state.data, [payload.field]: payload.value },
};
// Immutability helper
// return update(state, {
// data: { [payload.field]: { $set: payload.value } },
// });
// return Object.assign({}, state.data, {
// [payload.field]: payload.value,
// });
}
}
The point is that everything works just fine as soon as I load the page and I start to type the information in my form.
If I change something in the code, it reload automatically using HMR, I can see the information typed before in the inputs but if I try to update the information it doesn't work anymore...
I can see the "state" updating just the last letter of the input and the UI seems freeze. (ie.: input value is: 'foo', I type: 'bar' the result is: foob, fooa, foor... and it is not reflected in the UI).
I think I'm doing something wrong in here...

Related

Updating state with new value to the object of array is really slow

I have a data set much larger than the example below. I'm trying to add notes to the profiles, however, there is a relatively large lag between typing and the text appearing in the textarea. I assume this happening because it has to go through a lot of data. Is there a more efficient way to do this.
Data
const profiles = [{
firstName: 'Brady',
lastName: 'Smith'
}, {
firstName: 'Jason',
lastName: 'Brady'
}, {
firstName: 'Michael',
lastName: 'Bolten'
}];
Component
class Notes extends React.Component {
constructor(props) {
super(props);
this.state = {
profiles: []
};
this.handleAddingUserNotes = this.handleAddingUserNotes.bind(this);
}
handleAddingUserNotes(e, userId) {
const { profiles } = this.state;
const addNotesToProfiles = profiles.map(profile => {
if (profile.userId === userId) {
profile.notes = e.target.value;
}
return profile;
});
this.setState({ profiles: addNotesToProfiles });
}
render() {
const { profiles } = this.state;
return (
<div>
{profiles.map(profile => {
const { userId, notes } = profile;
return (
<textarea
className="form-control"
value={notes}
onChange={e => this.handleAddingUserNotes(e, userId)}
/>
);
})}
</div>
);
}
}
You can pass the index of the profile to update in the change handler and then directly update that profile.
// in your render method
{profiles.map((profile, index) => {
const { userId, notes } = profile;
return (
<textarea
className="form-control"
value={notes}
onChange={e => this.handleAddingUserNotes(e, index, userId)}
/>
);
})}
// in your handle change
handleAddingUserNotes(e, index, userId) {
const { profiles } = this.state;
const newProfiles = [...profiles.slice(0, index), {...profiles[index], notes: e.target.value}, ...profiles.slice(index+1)]
this.setState({ profiles: newProfiles });
}
Or, if you anyways have unique userIds for each user, you can convert the whole array into an object where each user is indexed by the userId and then directly update that user in the userProfiles object. O(1) operation.
Edit:
And yes, as the other answer mentions, please use key with each of your textarea so that react does not rerender every text area on every render.

ReactJS, componentWillReceiveProps can get data, but render cannot

I am working on a ReactJS 15 project using sagas and reselect as middle ware to fetch data. I can successfully get data in componentWillReceiveProps and set the data in the state, however there still not data in the render function first run when I take the data from the state. Anyone knows what's going on here? BTW, I used json-server as mock data server.Below is part of my code:
Component:
constructor(props) {
super(props);
this.state = {
timelineData: [],
};
}
componentDidMount() {
// use react redux to make the api call here
this.props.fetchTimelineData({
id: parse(this.props.location.search.substr(1)).id,
});
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
// Successfully take the data from the nextProps (timelineData is not [])
const { timelineData } = nextProps;
this.setState({
timelineData,
});
}
render() {
// the first render called timelineData is an empty array, which will not able to populate the UI
// which RaiseTimeline is empty
const { timelineData } = this.state;
return (
<RaiseTimelineStyled>
<RaiseDetailsGrid>
<Accordion
title={
<RaiseAccordionHeader image={image} title={'Timeline'} />
}>
<Timeline.wrapper>
<RaiseTimelineStyled.divider>
<RaiseTimelineStyled.container>
<RaiseTimeline timelineEvents={timelineData} />
action.js (works fine):
export const setTimelineData = timelineData => console.log('actions.js', timelineData) || ({
type: ACTIONS.SET_TIMELINE_DATA,
timelineData,
});
Api.js (works fine):
class TimelineAPI {
// payload will be used after backend done
static fetchTimelineData(payload) {
return http.get(`${baseURI}/timeline`).then(result => console.log('api', result.data) || result.data);
}
}
Reducers: (works fine)
function TimelineDataReducer(state = initialState, action) {
switch (action.type) {
case ACTIONS.SET_TIMELINE_DATA:
console.log('reducer', action.timelineData);
return state.set('numbers', action.timelineData);
default:
return state;
}
}
Sagas: (works fine)
export function* fetchTimelineData(action) {
yield put(togglePendingScreen(true));
const { result, error } = yield call(TimelineAPI.fetchTimelineData, action.payload);
if (error) {
yield put(
toggleErrorModal({
isOpen: true,
text: error.code,
source: 'Fetch Timeline Data',
}),
);
} else {
console.log('Sagas', result.timeline);
yield put(ACTIONS.setTimelineData(result.timeline));
}
yield put(togglePendingScreen(false));
}
Selectors(works fine):
import { createSelector } from 'reselect';
const selectTimelineData = state => state.get('TimelinePageData').toJS();
const selectTimeline = () =>
createSelector(selectTimelineData, TimelineDataState => TimelineDataState.numbers);
export { selectTimeline };
To me it seems logical that you have no data on the first run.
The reason is there render() function is called once before the componentDidMount() in the react life cycle. (V15)
Look here for the react 15 life cycle : https://gist.github.com/bvaughn/923dffb2cd9504ee440791fade8db5f9
I got the answer, everything is correct, but another person name but another person name same component and set its state in side the constructor, so it's not able to render in the first time

Object passed into Redux store is not reflecting all key/values after mapStateToProps

I have a component where toggle buttons are dynamically generated. Right now, I am just trying to get it working at a basic level so you click on a button and it adds a key/value pair to the cuts = {}.
After clicking on multiple buttons the cuts should have several key/value pairs: it does in the component where cuts resides, it does in the action, and it does in the Redux store via console.log(state.cuts).
However, after mapStateToProps it is only showing the first value and I am not sure why.
Anyway, here is my code and the flow as it is initiated by the user:
// bq_cuts.js component
constructor(props) {
super(props);
this.state = {
cuts: {}
}
}
onCutSelect(cut) {
const { bqResults } = this.props;
const { cuts } = this.state;
let key = cut.name;
let value = cut.value;
cuts[key] = value;
this.setState({
cuts
})
console.log(cuts); // shows all of the selected cuts here
bqResults(cuts);
}
// results.js actions
export function bqResults(results) {
console.log(results); // shows all of the selected cuts here
return function(dispatch) {
dispatch({
type: FILTER_RESULTS,
payload: results
})
}
}
// results.js reducer
import {
FILTER_RESULTS
} from '../actions/results';
export default function(state = {}, action) {
switch(action.type) {
case FILTER_RESULTS:
console.log(action.payload); //prints out all the cuts
return {
...state,
filter_results: action.payload
}
default:
return state;
}
return state;
}
const rootReducer = combineReducers({
results: resultsReducer,
});
export default rootReducer;
// bq_results.js component where the FILTER_RESULTS is accessed
render() {
console.log(this.props.filter_results); // only shows the first result
return (<div>...</div>)
}
function mapStateToProps(state) {
console.log(state.results.filter_results); // shows all selected cuts here
return {
filter_results: state.results.filter_results,
}
}
Maybe a better way of putting it is it seems like after the initial state is mapped to props, it is no longer receiving changes to state and mapping it to props.
Came across this article and used Approach #2:
https://medium.freecodecamp.org/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5
Ended up with:
onCutSelect(cut) {
let cuts = {...this.state.cuts, [cut]: cut}
this.setState({
cuts
}, () => this.props.bqResults(this.state.cuts));
}

How to update input value state via reducers and actions without redux-form

I'm having a problem with this piece of code. Basically, I want to update input value state with action dispatch and not with setState react method. So far i have this, and state always returns undefined. Can someone please help me, i stuck with this and i can't think of any solution that i didn't try
This is just pieces of code relevant for this problem:
/* SignupForm.js */
<InputField
label="Name"
name="name"
type="text"
floatingLabelText="Name"
value={this.props.values[name]}
onChange={this.onChange}
errorText={this.state.errors.name}
/>
onChange = (event) => {
this.props.onChange(event.target.name, event.target.value);
};
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
values: state.update.values
};
}
function mapDispatchToProps(dispatch) {
return {
onSave: data => dispatch(registerUser(data)),
onChange: (name, value) => dispatch(formUpdate(name, value))
};
}
/* reducerUpdate.js */
const INITIAL_STATE = {
values:{}
};
const reducerForm = (state = INITIAL_STATE , action) => {
switch(action.type) {
case types.FORM_UPDATE_VALUE:
return Object.assign({}, state, {
values: Object.assign({}, state.values, {
[action.name]: action.value
})
});
}
return state;
};
export default reducerForm;
/*actionUpdate.js */
let _formUpdateValue = (name, value, e) => {
return {
type: types.FORM_UPDATE_VALUE,
name,
value
};
};
export function formUpdate({ name, value}) {
return dispatch => {
dispatch(_formUpdateValue(name, value));
console.log(name, value);
};
}
Your formUpdate doesn't return an action object but function which dispatches an action. To handle such case you should use thunk middleware (or something similar). However, in your case it's not needed at all because there's no asynchronous actions so you should use _formUpdateValue as your action creator (you can just remove current formUpdate function and rename _formUpdateValue to formUpdate and it should work).
Shouldn't state.update.values be state.values.update?
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
values: state.update.values //<- shouldn't this be state.values.update?
};
}

React Redux and side effects explanation

I'm wrapping my forms to provide automatic validation (I don't want to use redux-form).
I want to pass an onSubmit handler which must be fired after every input in form is validated: but how do I wait for form.valid property to turn into true && after wrapping submit was fired? I'm missing some logic here!
//in my Form.js hoc wrapping the forms
#autobind
submit(event) {
event.preventDefault();
this.props.dispatch(syncValidateForm({ formName: this.props.formName, form: this.props.form }));
// ==> what should I do here? Here I know submit button was pressed but state is not updated yet with last dispatch result reduced!
//if(this.props.form.valid)
// this.props.submit();
}
render() {
return (
<form name={this.props.formName} onSubmit={this.submit}>
{ this.props.children }
</form>
);
//action.js validating given input
export const syncValidateInput = ({ formName, input, name, value }) => {
let errors = {<computed object with errors>};
return { type: INPUT_ERRORS, formName, input, name, value: errors };
};
//action.js validating every input in the form
export const syncValidateForm = ({ formName, form }) => {
return dispatch => {
for(let name in form.inputs) {
let input = form.inputs[name];
dispatch(syncValidateInput({ formName, input, name: input.name, value: input.value }));
}
};
};
//in my reducer I have
case INPUT_ERRORS:
let errors = value;
let valid = true;
let errorText = '';
_.each(errors, (value, key) => {
if(value) {
valid = false;
errorText = `${errorText}${key}\n`;
}
});
form.inputs[name].errors = errors;
form.inputs[name].valid = valid;
form.inputs[name].errorText = errorText;
_.each(form.inputs, (input) => form.valid = !!(form.valid && input.valid));
return state;
Help!
Depending on your build config you could use Async/Await for your submit function. Something like
async submit(event) {
event.preventDefault();
const actionResponse = await this.props.dispatch(syncValidateForm({ formName: this.props.formName, form: this.props.form }));
if (actionResponse && this.props.form.valid) { //for example
// finish submission
}
}
And I think you will need to update your syncValidateForm slightly but this should put you on the right path.

Resources