How do I update React context from within an onChange handler function - reactjs

There are plenty of examples of changing context directly - such as this:
export class UserProvider extends React.Component {
updateUsername = newUsername => {
this.setState({ username: newUsername });
};
state = {
username: "user",
updateUsername: this.updateUsername
};
}
followed by this in the Consumer:
<input
id="username"
type="text"
onChange={event => {
updateUsername(event.target.value);
}}
/>
but my onChange handler is a function and within that OnChange handler, I want to update a context variable.
Using this example, how would I call updateUsername from within the OnChange Handler or indeed from any function such as componentDidMoount, etc
I have searched but so far not been able to find anything that might give an indication as to how to accomplish this.

Using Context in React you'd usually want to have reducers when updating state, reducers allow you to dispatch actions that mutate state repetitively. Don't update state directly it's regarded as anti-pattern.
I wanted to add this a comment however my reputation doesn't allow me to do so yet, excuse me if this doesn't solve your problem.

Related

How to replace componentWillRecieveProps

I'm new to redux and followed this tutorial to create a simple blog app with react and redux. I've completed it, however I noticed that componentWillRecieveProps is being deprecated. I'm trying to replace it with more up-to-date code, but have been unable to understand how to do so. I've read this article about replacing ‘componentWillReceiveProps’ with ‘getDerivedStateFromProps’, but I don't think that this is the correct use for getDerivedStateFromProps as React's blog post on replacing one with the other describes.
My current componentWillRecieveProps code:
import axios from "axios";
import React from "react";
import { connect } from "react-redux";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: "",
author: ""
};
this.handleChangeField = this.handleChangeField.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//This is deprecated
componentWillReceiveProps(nextProps) {
console.log(this);
if (nextProps.articleToEdit) {
this.setState({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});
}
}
handleSubmit() {
const { onSubmit, articleToEdit, onEdit } = this.props;
const { title, body, author } = this.state;
if (!articleToEdit) {
return axios
.post("http://localhost:8000/api/articles", {
title,
body,
author
})
.then(res => onSubmit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
} else {
return axios
.patch(
`http://localhost:8000/api/articles/${articleToEdit._id}`,
{
title,
body,
author
}
)
.then(res => onEdit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
}
}
handleChangeField(key, event) {
this.setState({
[key]: event.target.value
});
}
render() {
const { articleToEdit } = this.props;
const { title, body, author } = this.state;
return (
<div className="col-12 col-lg-6 offset-lg-3">
<input
onChange={ev => this.handleChangeField("title", ev)}
value={title}
className="form-control my-3"
placeholder="Article Title"
/>
<textarea
onChange={ev => this.handleChangeField("body", ev)}
className="form-control my-3"
placeholder="Article Body"
value={body}
/>
<input
onChange={ev => this.handleChangeField("author", ev)}
value={author}
className="form-control my-3"
placeholder="Article Author"
/>
<button
onClick={this.handleSubmit}
className="btn btn-primary float-right"
>
{articleToEdit ? "Update" : "Submit"}
</button>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onSubmit: data => dispatch({ type: "SUBMIT_ARTICLE", data }),
onEdit: data => dispatch({ type: "EDIT_ARTICLE", data })
});
const mapStateToProps = state => ({
articleToEdit: state.home.articleToEdit
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form);
Full Repo
Codesandbox
If componentWillRecieveProps is being deprecated, how should I update my code?
Edit: using getDerivedStateFromProps, nextProps still has the previous values so when returning the object, state is set back to previous state, no updates are actually made.
Trying to use prevState values doesn't work in this case because the initial prevState values are all empty strings which are called and placed into state anytime the component renders, which occurs on initial page load, and when clicking the edit button.
static getDerivedStateFromProps(nextProps, prevState) {
if (
JSON.stringify(nextProps.articleToEdit) !==
JSON.stringify(prevState.articleToEdit)
) {
console.log(nextProps);
console.log(prevState);
return {
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
}; // <- is this actually equivalent to this.setState()?
} else {
return null;
}
}
componentWillReceiveProps() method is deprecated by introducing a new life cycle method called getDerivedStateFromProps().
Keep in mind that you should always compare current props with previous props like below and if they both are not same then do setState otherwise you will get into infinite setState warning
Replace below code in place of componentWillReceiveProps method
static getDerivedStateFromProps(nextProps, prevState) {
//Below I used JSON.stringify to compare current props with previous props of articleToEdit object
if (JSON.stringify(nextProps.articleToEdit) !== JSON.stringify(prevState.artileToEdit)){
return({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});// <- this is setState equivalent
}
return null;
}
Edit:
You cannot access this.props inside getDerivedStateFromProps function. If you want to access some previous props (like for comparison) inside the function, then you can mirror such values inside the state. Then you can compare the nextProps with the value stored in state like above
Check this thread for better understanding
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props
Alrighty. Here's a working example for your application. I completely rewrote its structure so that each component handles its own set of state and props. The components are also reusable so they can be placed wherever -- provided that it has required props.
Notes:
Don't mix .jsx and .js (see step 6 for fix)
Separate your container-components from your components. Also make reusable components -- this vastly simplifies your state and prop logic.
For your application, you can completely remove redux and instead allow your server to be the "source of truth". So instead of saving to redux state and manipulating redux state, you would simply make AJAX requests to leverage your API for CRUDing your articles (for example, form submission would send post request to API, which would add an article to the DB, then either redirect the user back to /articles or it could simply return all articles after it has posted and update ShowArticles state with new data). Up to you on how you want to handle that. But, with that said, the only time you really need redux is if you're sharing props from two separate heavily nested components.
If using redux, add a types and actions folder as shown in the example below. It makes your code easier to read and more modular.
Avoid using index.js unless the root folder only houses a single file. Otherwise, when the application breaks (and it will), it'll be a hassle to figure out which "index.js" broke if you have many.
The boilerplate you're using is pretty old and you're making your life harder by using it. I've created a MERN Fullstack Boilerplate that allows things like: fat arrow functions in class methods, scss/css node_module imports, component-level scss module imports, runs server and client simultaneously, has an error overlay, eslinting, and plenty more. I highly recommend porting what you have over to it.
Working example: https://codesandbox.io/s/4x4kxn9qxw (unfortunately, the create-react-app template doesn't allow scss module imports... oh well, you'll get the idea)
My advice would be to heed the warning that React is giving us by replacing this lifecycle method because of its misuse — they are telling us only do this if you really have to, because there is probably a better way (including the newer getDerivedStateFromProps).
From what I gather in this code, you are rendering a form, letting the user interact with it, but in some circumstance you replace the users' inputs with props (overwriting component state). Its easy to see why this is an anti-pattern but tricky to re-architect without knowing more about the app.
I'd suggest moving the inputs' values into the parent state (or creating a new parent), and change to value={props.title} etc. If there is a parent that controls a forms' state in any circumstance, you may as well hold form state there all the time.

Possible to use React's new Context API programmatically?

I'm trying to have 1 big global state where I can perform actions, but I don't want all those actions to live in the same file.
I want to break actions out to their own files and abstract with a changeState function (like a reducer), but I'm not sure how to do this.
I have an example here. If you click the button, it will show you how far through the app it's gotten: https://codesandbox.io/s/r49qyymjzn
It seems to never hit the {ctx => { console.log('...') }.
Any help would be much appreciated, thank you.
Think of the Context.Provider as a stateful component. The action changeName needs to update the state of the Context.Provider class.
Changes in context.js
handleNameChange = changeName;
actions: {
changeName: this.handleNameChange
}
changeName.js
export default (e, newName) => {
e.preventDefault();
this.setState({ name: newName });
};
Working sandbox example here

Correct Way to Transfer Props to State (if needed) React & Redux

I have created a Create-React-App react app & Redux.
I am using connect to map my userReducer to state (via mapStateToPrope) ie:
function mapStateToProps({ userProfile }) {
return { userProfile: userProfile ? userProfile : null };
}
export default connect(mapStateToProps, fetchUserProfile)(Profile);
and I have an input which I have binded the UserProfile Firstname to:
render(){
return (
<div>
<input type="text" value={this.props.userProfile.firstName} />
</div>
)
}
This issue is now that I have it binded to the props, I am unable to change it, If I type in the input it wont change the text inside the input..
If I add a onChange={updateValue} method, and try and update the prop it wont work as a component can not update its own props.
So that leaves Transfering the props to state.
constructor(props){
super(props);
this.state = { FirstName : this.props.userProfile.firstName }
}
is this the recommended way to get an initial value from props?
You are trying to use a controlled input without providing information about updating the state.
If you just want to provide an initial state, you could use the defaultValue property of input fields.
<input defaultValue={this.props.value} />
With this you could easily change the input value, without writing overhead.
However, if you want to update your component, you should use the component state (like you did in your onChange example) or you use the redux state, if you want to provide the input value to more components than your input component.
const Component = ({ firstname, updateFirstname }) => (
<input
onChange={updateFirstname}
value={firstname}
/>
);
const mapStateToProps = state => ({
firstname: state.profile.firstname,
});
const mapDispatchToProps = dispatch => ({
updateFirstname: e => dispatch({ type: 'UPDATE_FIRSTNAME', firstname: e.target.value }),
});
connect(mapStateToProps, mapDispatchToProps)(Component);
In order to have a change take place managed in either local state or redux, you want to call the function onChange as follows
onChange={(e) => this.updateField('fieldName',e.target.value,e)}
The value field would be and would always be updated when the props (or state are updated)
value={this.props.fieldName}
or
value={this.state.fieldName}
You onChange function would either call setState({fieldName: value}) to set local state or dispatch to redux to update the Store which is turn would update the local state via your mapStateToProps function.
There's nothing wrong in doing that for a controlled component. But one mistake you made is there is no this.props in the constructor. You're passing props directly to the constructor using the props variable. So the code will be
this.state = {
FirstName: props.userProfile.firstName
};
But if you want the value from the store to be updated on the component later on then do declare componentWillReceiveProps() and set the state value there also so that the changes in store are captured here.

ReactJS - Not able to edit the input field after i bind the data from this.props.reducer.result.firstName(Used redux)

Please note I'm Material-ui react js component for my Textfield. I have not used redux-forms.
I have declared the onchange listerner and state
constructor(props) {
super(props);
this.state = {
firstName: null,
};
this.handleInputChange = this.handleInputChange.bind(this);
}
componentDidMount() {
// This is reducer function to get my data from server. I'm getting data and able to display it to my element.
// I'm using redux for my data layer.
this.props.getuserDetails(userId);
}
I'm not sure how to update the input element value using state as well as the value i received from props via reducer function.
handleInputChange (event) {}
this.setState({
firstName: event.target.value
});
}
inside my render function.
Textfield displays the user firstname. But its not allowing me to edit the field. Because i have bind the props.reducer directly.
<TextField name="firstName" onChange={this.handleInputChange} floatingLabelText="First Name" value={this.props.reducer.result.firstName}/>
How do i bind the data from dispatcher/reducer. and also accept user input to edit the first name.
later i can update it in server
I'm struck with this issue. any help is appreciated. Thanks.
After componentDidMount, this.props.getuserDetails(userId) function will set the props again, you should set the state in componentWillReceiveProps. and use this.state.firstname instead of this.props.reducer.result.firstname
code:
componentWillReceiveProps(nextProps){
if(this.state.firstName != nextProps.reducer.result.firstname)
{
this.setState({
firstName: nextProps.reducer.result.firstname
});
}
}
this will update the props to state and use this.state.firstname
<TextField name="firstName" onChange={this.handleInputChange} floatingLabelText="First Name" value={this.state.firstName}/>
You're right. You can't update your input element using setState() since it's value propagated through and bound to props.
Depending on what you're trying to achieve, there are different solutions available..
Update props by dispatching an action each time your input changes: e.g.:
handleInputChange (e) {}
// NOTE: I just made up this action
this.props.setUserDetails(e.target.value);
}
Set initialState from props and bind the input to state (this is considered an anti pattern, though it's OK if you make clear that it's only used for initial state. Also, react will not update the value of the input field after it has been rendered):
componentDidMount() {
this.setState({ value: this.props.value })
}
handleInputChange (e) {}
this.props.setState({ value: e.target.value });
}
Merge props and state whenever props change.
...
But anyway, as said before, it depends on what you're trying to do.
Here's a more detailed answer

React-redux add a new variable to state

I am struggling to get working on a React-redux application to pass a value via the state from one component on to another. I do not wish to use the reducer and/or dispatch because I am not calling a webservice, I just want to take a value from a textbox form to enable another control on the order component.
Firstly, I can grab the value from control, but when mapstatetoprops is called the variable I set and wish to add to the state is undefined. This also possibly explains why my other problem. On my other component the function to use props is never called because of the state-componentWillReceiveProps
Here is the relevant code snippet :
<ListItemContent>
<Control component={Textfield} model="somemodel" label="MyLabel" onBlur={this.onChangeOfValue}/>
</ListItemContent>
onChangeOfValue = (event) => {
this.setState({
newValueToPassAlong: event.target.value
}); //newValueToPassAlong is set in constructor
};
let mapStateToProps = (state) => {
return {
newValueToGive: state.newValueToPassAlong
} // This is undefined
};
export default connect(mapStateToProps)(form)
So, my question is how do add a new variable to a state using React-redux without the need of reducers, etc and with this can I access this variable in my other component?
Ok action creator preferences aside, if you want to sync something between two components which share a common parent, you have to bring that state into the parent.
class ParentComponent {
onItemChanged = (newData) => {
this.setState({ thing: newData });
}
render() {
return (
<div>
<ChildA onItemChanged={ this.onItemChanged } />
<ChildB thing={ this.state.thing } />
</div>
);
}
}
when you change the value in ChildA, use this.props.onItemChanged with the new value. In ChildB, that value will be synchronised in this.props.thing. React will handle all the Component/prop updates.
That all being said, I genuinely think there's nothing wrong with using an action creator/reducer.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
Refer documentation for more about setState()

Resources