I am facing a problem I can't figure out. For a project we use React to generate a layout from JSON input using the following code (simplified):
function generateComponents(children, params) {
let comps = [];
if (!children || children && children.length === 0) {
return [];
}
forEach(children, (comp) => {
let compName = comp.component;
let createdComp;
switch (compName) {
case 'test1':
createdComp = TestOne(Object.assign({}, comp, params));
break;
case 'test2':
createdComp = TestTwo(Object.assign({}, comp, params));
break;
}
comps.push(createdComp)
}
}
return comps.length === 1 ? comps[0] : comps;
}
This works well and the layout is generated correctly. We wanted to take this a step further and wrap the createdComp in a Higher Order Component. We implemented that in the following way:
function generateComponents(children, params) {
// see above for implementation
let component;
if (condition)
component = testingHOC(createdComp);
else
component = createdComp
comps.push(component);
}
// TestingHOC.js
export function testingHoc(WrappedComponent) {
console.log('wrapped')
return class TestingHoc extends Component {
render() {
console.log('props TestingHOC', this.props);
return ( <WrappedComponent { ...this.props} />);
}
}
};
This broke our component generation. The code returns nothing. The only thing that gets logged is the console.log('wrapped'), the render function of the class is never called. What are we missing here?
EDIT:
Render method of the render class:
render() {
const children = this.state.children;
const {params} = this.props;
const arrChildren = isArray(children) ? children : [children];
let comps = generateComponents(arrChildren, params || {});
if (isArray(comps)) {
return (
<ViewComponent>
{comps}
</ViewComponent>
);
} else {
return comps;
}
}
EDIT 2:
Console.log of {comps} with the testingHoc
Console.log of {comps} without the testingHoc
Edit 3
Added the code for ViewComponent:
import React from 'react';
const ViewComponent = (props) => (
<div {...props}/>
);
export default ViewComponent;
The issue you are facing is because of the inherent difference between a React element and a React component.
When you are not using the HOC, you are creating a React element which can be seen by the first console.log image. This is the output after reconciliation has occurred.
When you use the HOC, your HOC returns a component which shows up as the test(props) function in your second console.log image.
To have the same functionality with your HOC enhanced components, you need to change the code in generateComponents functions to
if (condition){
let Comp = testingHOC(createdComp);
component = <Comp/>;
}
Try
...
return (
<ViewComponent>
{comps.map((Comp) => <Comp />)}
</ViewComponent>
);
...
or
...
return (
<ViewComponent>
{comps.map((comp) => comp())}
</ViewComponent>
);
...
Related
I have the following code snippet, which gives me an infinite loop with error message "Cannot update during existing state transaction"
export const Child = (props) => {
console.log("rendering child element");
const retrieveValue = () => {
return "dummy";
}
const val = retrieveValue();
props.callback(val);
return (
<p>Hello World</p>
)
}
class App2 extends Component {
state = {
property: ""
}
callback = (val) => {
this.setState({property: val});
}
render() {
return (
<div>
<h1>{this.state.property}</h1>
<Child callback={this.callback}/>
</div>
);
}
}
render(<App2 />, document.getElementById('root'));
The error message makes sense, but what confuses me is why that changing the class component to function component and use the useState hook will walkaround the problem.
const App = () => {
const [property, setProperty] = React.useState("");
const callback = (val) => {
setProperty(val);
}
return (
<div>
<h1>{property}</h1>
<Child callback={callback}/>
</div>
);
}
Is there any other walkaround which let me achieve the same purpose but without using the function component?
The usecase is quite generic to me, the child component is trying to initialize some data, and somehow in certain edge case I wanna expose that data for the parent component or sibling component to render too.
stackblitz link
Here in render method, setState method is getting called when you call "props.callback(val);". So it goes into an infinite loop.
You can use shouldComponentUpdate method here to stop repeat of rendering.
shouldComponentUpdate(nextProps, nextState) {
return this.state.property != nextState.property;
}
#Abhijit Sil explication for infinite loop is correct. An alternative is to use useEffect in Child component:
export const Child = (props) => {
console.log("rendering child element");
const retrieveValue = () => {
return "dummy";
}
useEffect(()=>{
const val = retrieveValue();
props.callback(val);
}, [])
return (
<p>Hello World</p>
)
}
Can I access a function in a stateless component? I know that the function can be accessable if change to class component. However, as I know. Hooks is incomptable with class component. Please feel fee to comment. Thank you.
# This example is incorrect, for describe the idea only.
const StatelessComponent = () => {
const StatelessComponentFunction = () => {
return 1;
}
}
const thisFun = new TestComponent();
let A = thisFun.StatelessComponentFunction();
Update on 16, October 2019, solution found.
Base on the information on these pages "https://hackernoon.com/javascript-state-encapsulation-without-classes-in-2019-97e06c6a9643" and "https://basarat.gitbooks.io/typescript/docs/javascript/closure.html". I found a solution.
Modified
const StatelessComponent = () => {
return {
StatelessComponentFunction() {
return 1;
}
}
}
const thisFun = StatelessComponent();
console.log(thisFun.StatelessComponentFunction());
No, a stateless component is used to render jsx to the DOM.
What you have shown isn't really a react component, because it isn't returning jsx. It is just a JS function.
// This example is incorrect, for describe the idea only.
const TestComponent = () => {
const StatelessComponentFunction = () => {
return 1;
}
}
const thisFun = new TestComponent();
let A = thisFun.StatelessComponentFunction();
Stateless components should be instantiated like react components <TestComponent />
As #Joey_Gough mentioned. Your component should return JSX whether it is stateful or stateless component.
If you want to execute method in your stateless component then you should create a function inside the Parent Class Component and pass that function as a prop of Child Component. Something like this:
class ParentComponent extends Component {
handleClick = () => {
// Code here
}
render() {
return(
<StatelessComponent onClicked={handleClick} />
)
}
}
const StatelessComponent = (prop) => {
return(
<button onClick={prop.onClicked></button>
)
}
So I have this navigator component where depending on a value coming from another component, I need to show a different bottom navigation.
For now I am getting an error on the context consumer, here:
import { ThemeProvider, ThemeConsumer } from '../context/some';
const SelectedRoute = () => (
<ThemeConsumer>
{context => (context ? MainTabNavigator : PickupNavigator)}
</ThemeConsumer>
);
export default createAppContainer(
createSwitchNavigator(
{
App: SelectedRoute,
},
),
);
This is the only thing I have to create context:
const ThemeContext = React.createContext(0);
export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
I am getting this warning:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
What can I do to render what I need correctly?
You want to return JSX from the function given as child to ThemeConsumer, not just return a component.
const SelectedRoute = () => (
<ThemeConsumer>
{context => (context ? <MainTabNavigator /> : <PickupNavigator />)}
</ThemeConsumer>
);
I have not run the example, but just suggesting from the docs. I thought the explanation was pretty clear but I could be wrong.
Just define a context variable in a separate file, in your case like this:
export const IndexContext = React.createContext({
indexValue: value,
toggleNavigator: () => {},
});
In your component(which receives indexValue), you can use the context value and toggle accordingly:
<ThemeContext.Consumer>
{({indexValue, toggleNavigator}) => (
// your component which uses the theme
)}
</ThemeContext.Consumer>
Since your component A is a stateful component, you can handle changes and update the context value there.
class App extends React.Component {
constructor(props) {
super(props);
this.toggleIndex = () => {
this.setState({ index });
this.handleStateIndexChange();
MY_CONTEXT = index;
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
index: index,
toggleIndex: this.toggleIndex,
};
}
render() {
// The entire state is passed to the provider
return (
<IndexContext.Provider value={this.state}>
<Content />
</IndexContext.Provider>
);
}
}
I hope this helps.
I know this question has been answered but i just cannot handle what's going so wrong. I'm having a wrapper function:
const withTracker = (WrappedComponent, partnerTrackingCode, options = {}) => {
const trackPage = (page) => {
ReactGA.set({
page,
options
});
ReactGA.pageview(page);
};
class HOC extends Component {
componentDidMount() {
ReactGA.initialize(partnerTrackingCode);
const page = this.props.location.pathname;
trackPage(page);
}
componentWillReceiveProps(nextProps) {
const currentPage = this.props.location.pathname;
const nextPage = nextProps.location.pathname;
if (currentPage !== nextPage) {
trackPage(nextPage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return HOC;
};
export default withTracker;
and i'm calling it here:
export default (props) => {
const MainComponent = (
<div>
...
</div>
);
if (props.partnerTrackingCode) {
return (
withTracker(MainComponent, props.partnerTrackingCode)
);
}
return (<div />);
};
When the tracking code is defined and the withTracker is called even if mainComponent is a component it shows me this error: A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object
I've also try to replace the WrappedComponent with an empty div:
return(<div />)
but still the same error
It looks like you're confusing elements and components here. You're passing around elements (the actual output you want to be rendered), whereas a HOC is a component (a function that generally takes a set of props and returns an element). You're passing an element to your HOC, so when it tries rendering it (in the HOC render function) it can't render it and you get the error.
To fix, you'd firstly need to make your MainComponent into an actual component instead of just the element you want it to return, e.g.:
const MainComponent = props => (
<div>
...
</div>
)
Then to use that with your wrapper you'd want to wrap and then render that:
if (props.partnerTrackingCode) {
const MainWithTracker = withTracker(MainComponent, props.partnerTrackingCode)
return <MainWithTracker />;
}
This is a bit weird though, as you need to create the wrapped component within your render method, which isn't how you'd normally do things. It might make more sense to change your HOC so that it returns a component that takes the partnerTrackingCode as a prop instead of an argument to your HOC. Something along the lines of:
// your HOC (omitting irrelevant bits)
const withTracker = (WrappedComponent, options = {}) => {
...
class HOC extends Component {
componentDidMount() {
ReactGA.initialize(this.props.partnerTrackingCode);
...
}
...
render() {
// pull out the tracking code so it doesn't get passed through to the
// wrapped component
const { partnerTrackingCode, ...passthrough } = this.props;
return <WrappedComponent {...passthrough} />;
}
}
return HOC;
};
// in your component
const MainComponent = props => (
<div>
...
</div>
);
const MainWithTracker = withTracker(MainComponent);
export default (props) => {
if (props.partnerTrackingCode) {
return (<MainWithTracker partnerTrackingCode={props.partnerTrackingCode} />);
}
return (<div />);
};
(I don't think this is the best way to do it, I've just tried keeping as close to your code as I could. Once you start restructuring it, with your better knowledge of exactly what you're trying to do you may find a better way to organise it.)
your problem in your return method , in first step you must be know
when you want call HOC , you must write like this
return withTracker(MainComponent, props.partnerTrackingCode)
instead this
return (
withTracker(MainComponent, props.partnerTrackingCode)
);
remove ()
and then check again , if you still have error tell me
I'm having an issue rendering proper content in my component. Component has access to all the necessary store and props, I console.log to confirm that and yet it fails to render content.
I setup a store like that:
const levelDefaultState = {
level: "second"}
And my component where I want to render things depending on the current state looks like this:
import React from 'react';
import { connect } from 'react-redux'
import LevelTwo from './level2/level2'
class LevelRenderer extends React.Component {
renderLevel(){
let currentLevel = null;
if (this.props.level === "second") {
currentLevel = <LevelTwo level={this.props.level} />
} else {
currentLevel = 'No level'
}
return currentLevel;
}
render() {
console.log('store', this.props.level)
let renderLevelConst = (this.props.level) ? this.renderLevel() : notRendered()
function notRendered() {
return 'Not rendered yet'
}
return (
<div>
{renderLevelConst}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
level: state.level
}
}
export default connect(mapStateToProps)(LevelRenderer);
And even though the state is "second" all the time all it renders is "no level" string. Tried to setup the initial state to "first" then changing it to "second" with setTimeout, no changes whatsoever.
What am I doing wrong?
Try:
if (this.props.level.level === "second") {} in renderLevel
Since this.props.level = {level: "second", sublevel: "1.splash"} you have to call this.props.level.level to obtain the level value.