Missing context while using #connect - reactjs

I have React app which uses react-redux library. It works fine, I get my store context, I can dispatch actions. Beautiful.
Now I have met a problem. I want to declare child context in a root component and use it to pass global function right into the children.
export default class Root extends React.Component {
globalFunc() {
}
getChildContext() {
return {
globalFunc: this.globalFunc
};
}
render() {
return (
{ /* ChildComponent somewhere in here */ }
);
}
}
Root.childContextTypes = {
globalFunc: PropTypes.func
}
The problem is inside one of the children I get empty object when I have #connect decorator from react-redux. When I remove the decorator, I get my context correctly. Why Redux removes the context? How to make a workaround?
#connect(mapStateToProps)
export default class ChildComponent extends React.Component {
constructor(props, context) {
super(props, context);
console.log(this.context); // EMPTY {}
}
render() {
// ...
}
}
ChildComponent.contextTypes = {
store: PropTypes.store.isRequired,
globalFunc: PropTypes.func
}

I had a similar issue and when I used the "normal" HoC form for the connect() function instead of the decorator one, it was fixing the issue.
Note that this is not recommended to use the decorator form as said there by the creator of Redux: https://stackoverflow.com/a/35642430/757461
Also if you really want to use decorators, I could fix this issue by defining my context using a decorator. Something similar to this:
export function context(contextTypes, context) {
return DecoratedComponent => {
return class extends Component {
static childContextTypes = contextTypes;
getChildContext() {
return context(this.props)
}
render() {
return <DecoratedComponent {...this.props} />
}
}
}
}
And then you use it like this:
#connect(mapStateToProps, mapDispatchToProps)
#context(contextTypes, getChildContext)

Related

Pass dynamic value to HOC in react

I write some HOC and I need to pass to this HOC a dynamic object that I create on some life cycle level and I did not get him as a prop.
If I try to pass some static value ( for example initialize myObj from start) it works as expected and I get the correct value.
Let's say this is my component class :
let myObj = {};
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'};
return ( ... )
}
}
export default withHOC(Test, myObj);
And this is my HOC:
const withHOC = (Component, test) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = test; // ---->test is empty object always !!
...
}
}
return Hoc;
}
My 'Dynamic' object that I create on my 'test' class is always empty on my HOC class.
It's happend also when I try to pass some value from my props directly, in this case the page is stuck(without errors in console).
Does someone have any idea how to resolve that? Thanks!
When you compose a component that way, composition only happens at compile time (static composition). This means that withHOC runs only once and is receiving an empty myObj argument, as it is using the one defined on declaration.
export default withHOC(Test, myObj); //myObj = {}
If you want that value to be dynamic, the withHOC composition should be runned when that value changes.
You can't send data up from the WrappedComponent (Test) to the HOC (withHOC), so even if you change myObj value in Test.render, the HOC would never know.
What you could do, if you really need it, is do the composition on the Test.render
render(){
const Hoc = withHOC(this.state.myObj, WrappedComponent);//WrappedComponent can be anything
return(
<Hoc/>
)
}
This way, every time the component renders, Hoc is composed using as myObj a value from the component state, wich is not the preferable way to do it, because this.state.myObj might have the same value as it did at the previous render, and you would be re-composing with the same data as before.
A better way to do it is checking for changes in myObj at Test.componentDidUpdate, and if it did change, then compose Hoc again.
You are passing an empty object to the withHOC function
let myObj = {}; // <- your myObj is empty
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'}; // <- You're doing this in the render method of your Test component, so until the component is rendered myObj is empty
return ( ... )
}
}
export default withHOC(Test, myObj);
Some explanation about what's happening here, by order:
import Comp from '.../Test.js'
the withHOC function is triggered, with the params of Test (which is defined above the call) and myObj (which is defined above the call but is empty)
Test component is returned, and nobody used the logic of myObj = {test:'test'}
Suggested solution:
Make the HOC get the logic from the props with another hoc:
const withProps = newProps => BaseComponent => props => {
const propsToAdd = typeof newProps === 'function' ? newProps(props) : newProps
return <BaseComponent {...props} {...propsToAdd} />
}
Usage:
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
return ( ... )
}
}
export default withProps({test:'test'})(withHOC(Test));
// or: export default withProps(props => {test:'test'})(withHOC(Test));
const withHOC = (Component) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = this.props.test;
...
}
}
return Hoc;
}
you can use recompose, a library which has many hocs and utils, and for better readability:
import { compose, withProps } from "recompose"
class Test extends React.Component {...}
const enhance = compose(
withProps({test:'test'}),
withHOC
)
export default enhance(Test);
I can't say with confidence this is optimal but I solved a similar problem by having a function within the HOC that updates state that you can then invoke with any data in the wrapped component.
HOC:
func = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
return ({ <WrappedComponent func={this.func} /> })
Wrapped Component:
this.props.func(anythingA, anythingB);
You can then access the data through state in the HOC.
To elaborate:
const withHOC = (WrappedComponent) => {
class withHOC extends React.Component {
constructor(props) {
super(props)
this.state = {
stateA: 1,
stateB: 2
}
*use state however you want in this HOC, including pass it through to another component*
*the following is just a function*
*when it's invoked in the wrapped component state will update here in the
HOC*
changeState = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
render() {
return (
<div>
<p>this.state.stateA</p>
<p>this.state.stateB</p>
<WrappedComponent changeState={this.changeState} />
</div>
)
}
}
}
}
In wrappedComponent, after importing:
class aComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
*you can now invoke the function from the HOC in this wrapped component*
}
}
You can use react-redux and store your object in redux state. Change the object wherever you need (in your case it's in Test) and access it in component inside your HOC from redux state, it'll be always up to date.

How to get the data from React Context Consumer outside the render

I am using the new React Context API and I need to get the Consumer data from the Context.Consumer variable and not using it inside the render method. Is there anyway that I can achieve this?
For examplify what I want:
console.log(Context.Consumer.value);
What I tested so far: the above example, tested Context.Consumer currentValue and other variables that Context Consumer has, tried to execute Context.Consumer() as a function and none worked.
Any ideas?
Update
As of React v16.6.0, you can use the context API like:
class App extends React.Component {
componentDidMount() {
console.log(this.context);
}
render() {
// render part here
// use context with this.context
}
}
App.contextType = CustomContext
However, the component can only access a single context. In order to use multiple context values, use the render prop pattern. More about Class.contextType.
If you are using the experimental public class fields syntax, you can use a static class field to initialize your contextType:
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render something based on the value */
}
}
Render Prop Pattern
When what I understand from the question, to use context inside your component but outside of the render, create a HOC to wrap the component:
const WithContext = (Component) => {
return (props) => (
<CustomContext.Consumer>
{value => <Component {...props} value={value} />}
</CustomContext.Consumer>
)
}
and then use it:
class App extends React.Component {
componentDidMount() {
console.log(this.props.value);
}
render() {
// render part here
}
}
export default WithContext(App);
You can achieve this in functional components by with useContext Hook.
You just need to import the Context from the file you initialised it in. In this case, DBContext.
const contextValue = useContext(DBContext);
You can via an unsupported getter:
YourContext._currentValue
Note that it only works during render, not in an async function or other lifecycle events.
This is how it can be achieved.
class BasElement extends React.Component {
componentDidMount() {
console.log(this.props.context);
}
render() {
return null;
}
}
const Element = () => (
<Context.Consumer>
{context =>
<BaseMapElement context={context} />
}
</Context.Consumer>
)
For the #wertzguy solution to work, you need to be sure that your store is defined like this:
// store.js
import React from 'react';
let user = {};
const UserContext = React.createContext({
user,
setUser: () => null
});
export { UserContext };
Then you can do
import { UserContext } from 'store';
console.log(UserContext._currentValue.user);

React Higher Order Component (HOC) in typescript

I am trying to write a React HOC in typescript but I am not getting the definitions correct. I am not sure if what I am trying to accomplish is possible.
Here is my code
import * as React from 'react'
export default function Ajax<Props, State>(InnerComponent: typeof React.Component): React.ComponentClass<Props & State> {
return class extends InnerComponent<Props & State,any> {
constructor() {
super()
this.state = {
request: 'initial'
}
}
changeRequest(newRequest) {
this.setState({request: 'loading'})
}
render() {
return <InnerComponent
{...this.props }
{...this.state}
changeRequest={this.changeRequest}
/>
}
}
}
If i am just passing the props and state to the child it works. But how can I write the definitions to be able to pass additional props to the wrapped component? In this case the changeRequest prop.
Thanks
I was able to make it work. But I am not sure this is the correct way. But now the compiler is not complaining and the code works.
For the compiler stop complaining I had to add the props as javascript Objects.
Here is my working code:
import * as React from 'react'
export default function Ajax<Props, State> (InnerComponent: typeof React.Component): React.ComponentClass<Props & State> {
return class extends InnerComponent<Props & State, any> {
constructor() {
super()
this.state = {
request: 'initial'
}
this.changeRequest = this.changeRequest.bind(this)
}
changeRequest(newRequest) {
this.setState({request: 'loading'})
}
render() {
return <InnerComponent {...this.props} {...this.state} {...{onCLick: this.changeRequest}}/>
}
}
}
I believe you just need to bind the HOC's context to the changeRequest function.
constructor() {
...
this.changeRequest = this.changeRequest.bind(this)
}
And make sure you're handling the prop in InnerComponent. For example.
<InnerComponent onClick={this.props.changeRequest}>

Unable to access child function via ref?

Initially everything was working fine,I have a component something like.
this
class A extends React.Component {
constructor(props) {
super(props);
this.childRef = null
}
componentDidMount() {
this.childRef = this.refs.b
// now I can call child function like this
this.childRef.calledByParent()
}
render(){
<B ref = "b"/>
}
}
In other file
class B extends React.Component {
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default B
till here it was working fine but when I do something like this in class B export default connect(mapStateToProps, mapDispatchToProps)(B)
It is not working. I have imported connect from react-redux
connect() accepts option as the forth parameter. In this option parameter you can set flag withRef to true. After this you can access functions to refs by using getWrappedInstance() like
class A extends React.Component {
constructor(props) {
super(props);
this.childRef = null
}
componentDidMount() {
this.childRef.getWrappedInstance().calledByParent()
}
render(){
<B ref = {ref => this.childRef = ref}/>
}
}
class B extends React.Component {
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(B)
Might be a little late but another (better) solution than using refs is to only give control to specific functions of the component.
class A extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.ctrl_B.calledByParent()
}
render(){
<B provideCtrl={ctrl => this.ctrl_B = ctrl} />
}
}
class B extends React.Component {
componentDidMount() {
this.props.provideCtrl({
calledByParent: () => this.calledByParent()
});
}
componentWillUnmount() {
this.props.provideCtrl(null);
}
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(B)
I had similar problem but I didn't want to make my APIs dependent on getWrappedInstance() calls. In fact some components in your class hierarchy may use connect() and access the store and some others are just stateless components that don't need that additional Redux layer.
I have just written a small (maybe a bit hackish) method. Please note, it hasn't been fully tested yet so expect you may need to make some adjustments to get it working in your own scenario.
TypeScript (should be easy to convert to pure JavaScript syntax):
function exposeWrappedMethods(comp: React.ComponentClass<any>, proto?: any): any {
if (!proto) {
if (comp.prototype.constructor.name === 'Connect') {
// Only Redux component created with connect() is supported
proto = comp.prototype.constructor.WrappedComponent.prototype;
} else {
console.warn('Trying to extend an invalid component.');
return comp;
}
}
let prototypeName: string = proto.constructor.name;
if (prototypeName.search(/^React.*Component.*/) < 0 && proto.__proto__) {
for (let propertyName of Object.getOwnPropertyNames(proto)) {
if (!comp.prototype[propertyName]) {
let type: string = typeof proto[propertyName];
if (type === 'function') {
// It's a regular function
comp.prototype[propertyName] = function (...args: any[]) {
return this.wrappedInstance[propertyName](args);
};
} else if (type === 'undefined') {
// It's a property
Object.defineProperty(comp.prototype, propertyName, {
get: function () {
return (this as any).wrappedInstance[propertyName];
},
set: function (value: any) {
(this as any).wrappedInstance[propertyName] = value;
}
});
}
}
}
return exposeWrappedMethods(comp, proto.__proto__);
}
return comp;
}
Use it by simply wrapping your connect() call with exposeWrappedMethods. It will add all methods and properties from your own class (and subclasses) but will not overwrite already existing methods (i.e. methods from React.Component base class).
export default exposeWrappedMethods(
connect<any, any, Properties>(
(state: ApplicationState) => state.counter,
CounterState.actionCreators,
null,
{ pure: false, withRef: true } // It requires use of "withRef: true"
)(Counter)) as typeof Counter;
Hope you (or someone else) find it useful.
/Lukasz

Accessing this.context from onSubmitSuccess of redux-form

Here is the problem I am facing:
I am using react-router and redux-form in my application. In one of my forms, I want to use the router to go back to the main page after the submission succeeds. Here is the code I have:
function Router(target) {
target.contextTypes = target.contextTypes || {};
target.contextTypes.router = React.PropTypes.func.isRequired;
}
enter code here
#Router
#reduxForm({
form: 'editLocation',
fields: LocationFormFields,
validate: ValidateLocationForm,
onSubmitSuccess: () => {
var path = '/locations';
this.context.router.push(path);
},
onSubmitFail: () => {
console.log('failed');
}
}, MapStateToProps)
export class EditLocation extends React.Component .....
The problem is that 'this.context' is not defined inside the onSubmitSucess method.
I know that I can take care of this using browserHistory directly. However, I don't want to really go that route and I want to use this.cotext. Any ideas?
I think your best bet is to not make your form component the same as your route component, and pass in the onSubmitSuccess as a prop to your form component.
import { withRouter } from 'react-router'
#withRouter // injects router as prop
export class EditLocation extends React.Component {
handleSubmitSuccess() {
this.props.router.push('/locations')
}
render() {
return <EditLocationForm
onSubmitSuccess={this.handleSubmitSuccess}/>
}
}
#reduxForm({
form: 'editLocation',
fields: LocationFormFields,
validate: ValidateLocationForm,
onSubmitFail: () => {
console.log('failed');
}
}, MapStateToProps)
export class EditLocationForm extends React.Component .....
To use context inside a component you need to specify contextTypes
static contextTypes = {
router: PropTypes.object
};
So I would recommend either to call you function from inside the component and specify contextTypes there or add one higher order component to handle that.

Resources