I have a single state inside my React component which has the structure like this
{a: {x: 0}, b: {x:0}}. During the operation time, i need to trigger several requests to server in order to request the data in order to update the state, and this need to be updated in the state. Since the number of the record in the database is quite big, I have to trigger several times
If I do like this
request_data(e) {
['a', 'b'].forEach((k) => {
// do not mutate the state directly
let new_state = _.extend({}, state);
request(params, (err, res) => {
// set result to the new_state
new_state = res;
// update the original state
this.setState(newState);
})
});
}
inside the callback, the second request will not have the data of the first request the moment it request and update the origin state with the empty value for the first branch of the state structure.
One simple solution is updating the origin state directly, but I don't think it is a good idea. I am planning to integrate redux later but at this moment, since the current code base is quite big, migrating gradually is better. However, I dont' want to alter the origin state directly since it is not the redux way. Any suggestion to overcome this issue ?
I'm assuming by the way your code is written, you're using es6 classes for your component? That being the case, hopefully this tidbit will help:
import React from 'react'
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
foo:'bar'
}
}
request_data = (e) => {
['a', 'b'].forEach((k) => {
// do not mutate the state directly
let new_state = _.extend({}, state);
request(params, (err, res) => {
// set result to the new_state
// update the original state
this.setState(foo:res.foo)
})
});
}
render() {
return (
// component code here
)
}
}
export default MyComponent
Notice that I'm using an arrow function for the request_data part...this is to avoid need to bind the function to the this variable inside of the constructor.
UPDATE:
I'm still not quite sure I understand what problem you're having here...but if you're not wanting to use redux right now, you could go with a container component method. Basically, your container component's main job is to manage the data and do nothing but pass its updated state to a child component as props. Something like this:
class MyComponentContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
a: {
x: 0
},
b: {
x:0
}
}
}
componentWillMount() {
['a', 'b'].forEach((k) => {
request(params, (err, res) => {
this.setState({
a: res.a,
b: res.b
});
})
});
}
render() {
return (
<MyComponent data={this.state} />
)
}
}
class MyComponent extends React.Component {
constructor (props) {
super(props)
}
render() {
return (
<div>
{/* display stuff here */}
</div>
)
}
}
export default MyComponentContainer
Related
I currently have a Main.js file which contains a 'global' state. However, to modify that state that means I am placing all of my functions in Main.js to update state. Of course this means that the Main.js file is getting too large.
I am working on implementing some code splitting using React.lazy. However, I not sure how to move these functions to the children components to be able to update the global state.
The state is complex implementing various levels of nested objects and/or arrays. Because of this I need to use prevState and the spread operator to be able to modify and/or populate a deeply nested object.
How can I properly access prevState in child components to be able to safely update deeply nested objects in state?
An example of my code currently in Main.js might look like this:
updateObject = (objectA3, newProp1, newProp2) = {
this.setState(prevState => {
//some code here
return ({
objectA: {
...prevState.objectA
[objectA3]: {
...prevState.objectA[objectA3]
newProp1: "some value",
newProp2: "some other value"
}
}
})
}
}
So because of the complex nature of the state I am updating, what approach can I take so that I can move these functions to the proper child component so that I can update state in the main component.
I have come across something like this link that looks promising using a generic function in the Main.js component. However, I cannot wrap my head around how to implement such a mechanism for complex states.
Rather than trying to pass state up and down the component hierarchy, I'd recommend moving your state out to a store, and letting the components that need to get or set state talk to the store. Redux implements this sort of pattern, but there are many ways to do this. One approach could use a higher order component that provides state getters and setters via props.
Here's a relatively dumb proof-of-concept demo of a store with a connect method:
class Store {
connect(Component) {
return () => <Component store={this} />
}
get foo () {
return 'this is foo from the store';
}
}
const store = new Store();
function SomeComponent (props) {
return (
<div>SomeComponent: Foo: {props.store.foo}</div>
)
}
const Connected = store.connect(SomeComponent);
function App () {
return (
<Connected />
);
}
ReactDOM.render(<App />, document.querySelector("#app"))
<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>
<div id="app"></div>
If the main concern is to avoid over-bloating your Parent component with event-handlers, what you can do is create a folder for all the utility functions you want to have for that component tree. That will let you freely bring in the necessary functions for whatever logical pattern you need to follow.
Consider a scenario like this:
You have a Main component which holds state and renders children:
import React from "react";
import Child from "./Child";
import { handleChange } from "./utils/inputHandlers.js";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = handleChange;
componentDidMount() {
const data = [
{ id: 1, text: "test1" },
{ id: 2, text: "test2" },
{ id: 3, text: "test3" }
];
this.setState({
data: data
});
}
createChildren = () => {
const { data } = this.state;
return data.map(item => {
return <Child {...item} handleChange={this.handleChange} />;
});
};
render() {
return <div>{this.createChildren()}</div>;
}
}
In the Main component, we brought in a utility function, created a local method in our component and set it equal to that utility function. Then, we bound the this keyword from that same handleChange function to our component's execution context.
That step is integral for updating state because we need the this keyword to refer to our component. There's no way for the Child-component to just have an event-handler that can update Parent state, without the handler first existing in the Parent.
Now take a look at our utility function:
export const handleChange = function(e, id) {
const dataCopy = [...this.state.data];
const itemToUpdate = dataCopy.find(item => item.id == id);
itemToUpdate.text = e.target.value;
this.setState(prevState => {
return {
...prevState,
data: dataCopy
};
});
};
It looks just like a traditional event-handler, except its outside a component. Pretty cool stuff. The this keyword will now refer to whatever component you bounded this function to.
Lastly, you have your Child component, in which you passed down the handleChange method as prop, so that it can update the state belonging to the Parent component.
import React from "react";
class Child extends React.Component {
render() {
const { id, text, handleChange } = this.props;
return (
<div>
<input value={text} onChange={e => handleChange(e, id)} />
</div>
);
}
}
export default Child;
See working sandbox: https://codesandbox.io/s/lucid-voice-yecnn
Currently I'm using the following:
const mapStateToProps = state => {
return {
stateInProps: state.someSliceIWant
};
};
Foo.propTypes = {
dispatch: PropTypes.func,
stateInProps: PropTypes.object
};
export default connect(mapStateToProps)(Foo);
and since we can only actually map state to props, and not directly to state in my component, I'm getting around this in a hacky way by doing this:
class Foo extends React.Component {
constructor(props){
super(props);
this.state = {};
setTimeout(() => {this.setState(this.props.stateInProps);},1);
}
How should I do this properly? I've tried using different lifecycle hooks and all sorts of methods.
An interesting thing I found was that if I put setState() in mapStateToProps.... it ALMOST works.
const mapStateToProps = state => {
Foo.setState(state.someSliceIWant);
return {
stateInProps: state.someSliceIWant
};
};
It throws a bunch of errors, but when I delete that line and webpack grinds through and the page re-renders, the state has actually been updated properly and the values are there! WHAT?! I can't figure out how to get it do that, without throwing the errors, as Redux/React itself won't allow it as best I can tell.
Have you tried ComponentWillReceiveProps after mapping state to Props?
For instance:
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
The hacky way you are using, you might not able to access your stateInProps if it get updated in later lifecycle.
You can use something as below
class Foo extends React.Component {
state = {};
componentWillMount() { this.setStateInProps(this.props); }
componentWillReceiveProps(nextProps) { this.setStateInProps(nextProps)}
setStateInProps= (props) => this.setState(props.stateInProps)
}
Considering this pseudocode:
component.js
...
import {someFunc} from "./common_functions.js"
export default class MyComp extends Component {
constructor(props) {
super(props);
this.someFunc = someFunc.bind(this);
this.state = {...};
}
_anotherFunc = () = > {
....
this.someFunc();
}
render() {
...
}
}
common_functions.js
export function someFunc() {
if(this.state.whatever) {...}
this.setState{...}
}
How would I bind the function someFunc() to the context of the Component? I use it in various Components, so it makes sense to collect them in one file. Right now, I get the error "Cannot read whatever of undefined". The context of this is unknown...
You can't setState outside of the component because it is component's local state. If you need to update state which is shared, create a store (redux store).
In your case, you can define someFunction at one place and pass it the specific state variable(s) or entire state. After you are done in someFunction, return the modified state and update it back in your component using setState.
export function someFunc(state) {
if(state.whatever) {...}
const newState = { ...state, newValue: whateverValue }
return newState
}
_anotherFunc = () = > {
....
const newState = this.someFunc(this.state);
this.setState({newValue: newState});
}
it's not a React practice and it may cause lot of problems/bugs, but js allows to do it:
Module A:
export function your_external_func(thisObj, name, val) {
thisObj.setSate((prevState) => { // prevState - previous state
// do something with prevState ...
const newState = { // new state object
someData: `This is updated data ${ val }`,
[name]: val,
};
return newState
});
}
Then use it in your react-app module:
import { your_external_func } from '.../your_file_with_functions';
class YourReactComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.state={
someName: '',
someData: '',
};
}
handleChange = (e) => {
const { target } = event;
const { name } = target;
const value = target.type === 'checkbox' ? target.checked : target.value;
your_external_func(this, name, value);
}
render() {
return (<span>
{ this.state.someData }
<br />
<input
name='someName'
value={ this.state.someName }
onChange={ this.handleChange }
/>
</span>);
}
}
It's a stupid example :) just to show you how you can do it
The best would obviously to use some kind of external library that manages this. As others have suggested, Redux and MobX are good for this. Using a high-order component to wrap all your other components is also an option.
However, here's an alternative solution to the ones above:
You could use a standard javascript class (not a React component) and pass in this to the function that you are calling from that class.
It's rather simple. I've created a simple example below where the state is changed from a function of another class; take a look:
class MyApp extends React.Component {
constructor() {
super();
this.state = {number: 1};
}
double = () => {
Global.myFunc(this);
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.double}>Double up!</button>
</div>
);
}
}
class Global {
static myFunc = (t) => {
t.setState({number: t.state.number*2});
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<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="app"><div>
There is a functional form of setState that can even be used outside of a component.
This is possible since the signature of setState is:
* #param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* #param {?function} callback Called after state is updated.
See Dan's tweet: https://twitter.com/dan_abramov/status/824308413559668744
This all depends on what you are trying to achieve. At first glance I can see 2 options for you. One create a child component and two: use redux as redux offers a singular state between all of your child components.
First option:
export default class parentClass extends Component {
state = {
param1: "hello".
};
render() {
return (
<Child param1={this.state.param1}/>
);
}
}
class Child extends Component {
render() {
console.log(this.props.param1);
return (
<h1>{this.props.param1}</h1>
);
}
}
Now the above child component will have the props.param1 defined from the props passed from it's parent render function.
The above would work but I can see you're trying to establish a 'common' set of functions. Option 2 sort of provides a way of doing that by creating a singular state for your app/project.
If you've haven't used redux before it's pretty simple to use once you've got the hang of it. I'll skip out the setup for now http://redux.js.org/docs/basics/UsageWithReact.html.
Make a reducer like so:
import * as config from './config';//I like to make a config file so it's easier to dispatch my actions etc
//const config.state = {param1: null}
//const config.SOME_FUNC = "test/SOME_FUNC";
export default function reducer(state = config.state, action = {}) {
switch(action.type) {
case config.SOME_FUNC:
return Object.assign({}, state, {
param1: action.param1,
});
break;
default:
return state;
}
}
}
Add that to your reducers for your store.
Wrap all your components in the Provider.
ReactDOM.render(
<Provider store={store} key="provider">
<App>
</Provider>,
element
);
Now you'll be able to use redux connect on all of the child components of the provider!
Like so:
import React, {Component} from 'react';
import {connect} from 'react-redux';
#connect(
state => (state),
dispatch => ({
someFunc: (param1) => dispatch({type: config.SOME_FUNC, param1: param1}),
})
)
export default class Child extends Component {
eventFunction = (event) => {
//if you wanted to update the store with a value from an input
this.props.someFunc(event.target.value);
}
render() {
return (
<h1>{this.props.test.param1}</h1>
);
}
}
When you get used to redux check this out https://github.com/redux-saga/redux-saga. This is your end goal! Sagas are great! If you get stuck let me know!
Parent component example where you define your callback and manage a global state :
export default class Parent extends Component {
constructor() {
super();
this.state = {
applyGlobalCss: false,
};
}
toggleCss() {
this.setState({ applyGlobalCss: !this.state.applyGlobalCss });
}
render() {
return (
<Child css={this.state.applyGlobalCss} onToggle={this.toggleCss} />
);
}
}
and then in child component you can use the props and callback like :
export default class Child extends Component {
render() {
console.log(this.props.css);
return (
<div onClick={this.props.onToggle}>
</div>
);
}
}
Child.propTypes = {
onToggle: PropTypes.func,
css: PropTypes.bool,
};
Well for your example I can see you can do this in a simpler way rather than passing anything.
Since you want to update the value of the state you can just return it from the function itself.
Just make the function you are using in your component async and wait for the function to return a value and set the state to that value.
import React from "react"
class MyApp extends React.Component {
constructor() {
super();
this.state = {number: 1};
}
theOnlyFunction = async() => {
const value = await someFunctionFromFile( // Pass Parameters );
if( value !== false ) // Just for your understanding I am writing this way
{
this.setState({ number: value })
}
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.double}>Double up!</button>
</div>
);
}
}
And in SomeOtherFile.js
function someFunctionFromFile ( // catch params) {
if( //nah don't wanna do anything ) return false;
// and the blahh blahh algorithm
}
you should use react Context
Context lets us pass a value deep into the component tree without explicitly threading it through every component.
here is a use case from react docs : create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
resource: https://reactjs.org/docs/context.html
In my project I'm trying to get rid of all the mixins and replace them with HOCs. I am stuck using ES5 at the moment.
export default React.createClass({
mixins: [SomeAsyncMixin],
data: {
item1: {
params: ({params, query}) => {
params: ({params, query}) => {
if (!query.p) {
return null;
}
const status = someTernaryResult
return {
groups: query.groups,
status,
subject: params.subject,
};
},
promise: query => query && query.subject && api(makeUrl(`/some/endpoint`, query))
},
item2: {
params: ({params, query}) => {
//same as before
},
promise: ({subject, query}) =>
// same as before
}
render() {
// some stuff
return(
// some jsx
);
}
}
Inside of the mixin, it has a componentWillMount and a componentWillUpdate that runs an update function that will loop through each key on data and update the props/state.
In React's docs about removing mixins, their mixins hold the data, not the component.
There are MANY components in my project that have a data object and use this mixin to update their props/state. How do I make a reusable component to handle this data object?
Also, how do I even access this data object from within the component? In the component this.data is null. Inside of the mixin this.data is the data object from inside the component.. why?
Your higher order component and mixin will look very similar. The main difference will be how data, props, and state are shared/passed. In the mixin case, you are altering your component's definition with the mixin's behavior, so the state and props are all in the one resulting component.
In the higher order component case, you are creating a new component that wraps around your other component. Thus, the shared state/behavior is entirely contained within wrapping component, and any data that needs to be used within the wrapped component can be passed via props.
So from what you have in your example, your higher order component would be something like
const someAsync = (data) => (WrappedComponent) => {
class SomeAsyncComponent extends React.Component {
constructor(...args) {
super(...args)
this.state = {
...
}
}
componentWillMount() {
// Make use of data, props, state, etc
...
}
componentWillUpdate() {
...
}
render() {
// May filter out some props/state, depending on what is needed
// Can also pass data through if the WrappedComponent needs it.
return (
<WrappedComponent
{ ...this.props }
{ ...this.state }
/>
)
}
}
return SomeAsyncComponent
}
And then your usage of it
export default someAsync(dataConfig)(WrappedComponent)
I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.