Converting ES5 Mixins to Higher Order Components - javascript

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)

Related

How to pass data in two way data binding in class base component in ReactJS?

My problem that is in a child component, I have a state that updated via post method and I must show this state in my parent component both these component are the class base component
The ReactJS is a library with One directional data binding. So that is not possible to pass data like Angular or VueJS. you should pass a handler function to the child-component and then after the Axios answer update the local and also the parent component.
And there is a little hint here, there is no different for your situation between class components and functional components. pay attention to the sample code:
class ParentComponent extends React.Component {
state = {
data: undefined,
};
handleGetData = data => {
this.setState({
data,
});
};
render() {
return (
<ChildComponent onGetData={this.handleGetData} />
);
}
}
And now inside the ChildComponent you can access to the handler function:
class ChildComponent extends React.Component {
componentDidMound() {
const { onGetData } = this.props;
Axios
.get('someSampleUrl')
.then( (data) => {
onGetData(data); // running this update your parent state
});
}
render() {
return (
<div />
);
}
}
React's practice is one-directional. For best practice, you must 'Raise' the states into the parent component.
But if you were looking specifically to pass data up, you need to pass a callback down as a prop. use that callback function to pass the data up:
Parent:
<Parent>
<Child callback={console.log} />
</Parent>
Child:
const Child = (props) => {
const { callback } = props;
const postData = (...) => {
...
callback(result);
}
...
}

Modifying state in parent component with prevState in React

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

Chain connect/mapStateToProps/mapDispatchToProps functions for code reuse in react-redux

Say I have two redux connected components. The first is a simple todo loading/display container, with the following functions passed to connect(); mapStateToProps reads the todos from the redux state, and mapDispatchToProps is used to request the state to be provided the latest list of todos from the server:
TodoWidgetContainer.js
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
The second redux component is intended to be applied to any component on a page so that component can indicate whether a global "loading" icon is displayed. Since this can be used anywhere, I created a helper function that wraps MapDispatchToProps in a closure and generates an ID for each component, which is used to make sure all components that requested the loader indicate that they don't need it anymore, and the global loader can be hidden.
The functions are basically as follows, with mapStateToProps exposing the loader visibility to the components, and mapDispatchToProps allowing them to request the loader to show or hide.
Loadify.js
function mapStateToProps(state) {
return {
openLoader: loaderSelectors.getLoaderState(state)
};
}
function mapDispatchToProps() {
const uniqId = v4();
return function(dispatch) {
return {
showLoader: () => {
dispatch(loaderActions.showLoader(uniqId));
},
hideLoader: () => {
dispatch(loaderActions.hideLoader(uniqId));
}
};
};
}
export default function Loadify(component) {
return connect(mapStateToProps, mapDispatchToProps())(component);
}
So now, if I have a component that I want to give access to the loader, I can just do something like this:
import Loadify from '...'
class DisplayComponent = new React.Component { ... }
export default Loadify(DisplayComponent);
And it should give it a unique ID, allow it to request the loader to show/hide, and as long as there is one component that is requesting it to show, the loader icon will show. So far, this all appears to be working fine.
My question is, if I would like to apply this to the todos component, so that that component can request/receive its todos while also being allowed to request the loader to show while it is processing, could I just do something like:
TodoWidgetContainer.js
import Loadify from '...'
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
const TodoContainer = connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
export default Loadify(TodoContainer);
And will redux automatically merge the objects together to make them compatible, assuming there are no duplicate keys? Or will it take only the most recent set of mapStateToProps/mapDispatchTo unless I do some sort of manual merging? Or is there a better way to get this kind of re-usability that I'm not seeing? I'd really rather avoid having to create a custom set of containers for every component we need.
connect will automatically merge together the combination of "props passed to the wrapper component", "props from this component's mapState", and "props from this component's mapDispatch". The default implementation of that logic is simply:
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
So, if you stack multiple levels of connect around each other , the wrapped component will receive all of those props as long as they don't have the same name. If any of those props do have the same name, then only one of them would show up, based on this logic.
Alright, here is what I would do. Create a higher order component (HOC) that adds a new spinner reference to your reducer. The HOC will initialize and destroy references to the spinner in redux by tying into the life cycle methods. The HOC will provide two properties to the base component. The first is isLoading which is a function that takes a boolean parameter; true is on, false is off. The second property is spinnerState that is a readonly boolean of the current state of the spinner.
I created this example without the action creators or reducers, let me know if you need an example of them.
loadify.jsx
/*---------- Vendor Imports ----------*/
import React from 'react';
import { connect } from 'react-redux';
import v4 from 'uuid/v4';
/*---------- Action Creators ----------*/
import {
initNewSpinner,
unloadSpinner,
toggleSpinnerState,
} from '#/wherever/your/actions/are'
const loadify = (Component) => {
class Loadify extends React.Component {
constructor(props) {
super(props);
this.uniqueId = v4();
props.initNewSpinner(this.uniqueId);;
this.isLoading = this.isLoading.bind(this);
}
componentWillMount() {
this.props.unloadSpinner(this.uniqueId);
}
// true is loading, false is not loading
isLoading(isOnBoolean) {
this.props.toggleSpinner(this.uniqueId, isOnBoolean);
}
render() {
// spinners is an object with the uuid as it's key
// the value to the key is weather or not the spinner is on.
const { spinners } = this.props;
const spinnerState = spinners[this.uniqueId];
return (
<Component isLoading={this.isLoading} spinnerState={spinnerState} />
);
}
}
const mapStateTopProps = state => ({
spinners: state.ui.spinners,
});
const mapDispatchToProps = dispatch => ({
initNewSpinner: uuid => dispatch(initNewSpinner(uuid)),
unloadSpinner: uuid => dispatch(unloadSpinner(uuid)),
toggleSpinner: (uuid, isOn) => dispatch(toggleSpinnerState(uuid, isOn))
})
return connect(mapStateTopProps, mapDispatchToProps)(Loadify);
};
export default loadify;
Use Case Example
import loadify from '#/location/loadify';
import Spinner from '#/location/SpinnerComponent';
class Todo extends Component {
componentWillMount() {
this.props.isLoading(true);
asyncCall.then(response => {
// process response
this.props.isLoading(false);
})
}
render() {
const { spinnerState } = this.props;
return (
<div>
<h1>Spinner Testing Component</h1>
{ spinnerState && <Spinner /> }
</div>
);
}
}
// Use whatever state you need
const mapStateToProps = state => ({
whatever: state.whatever.youneed,
});
// use whatever dispatch you need
const mapDispatchToProps = dispatch => ({
doAthing: () => dispatch(doAthing()),
});
// Export enhanced Todo Component
export default loadify(connect(mapStateToProps, mapDispatchToProps)(Todo));

React: componentDidMount + setState not re-rendering the component

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.

Multiple state version merge for React

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

Categories

Resources