ReactJS: Conditional Rendering using Enums - javascript

I'm a bit new to React, and have been practicing by creating an application using the enums rendering method specified in this article.
However, I'm trying to apply it in a slightly different way than the article talks about, more specifically using it to conditionally render all of my website except for the <Nav /> based on the lastLinkClicked state. I've got different page classes for each condition as listed in the WEB_PAGES object.
Maybe I'm misunderstanding this method, since I don't have much experience with enums, but my pages aren't rendering correctly. Here's my code:
class App extends Component {
constructor(props){
super(props);
this.state = {
x: ...
y: ...
z: ...
lastClickedLink: 'home' //changes to 'new', 'settings', etc. using another function not listed here
}
}
render() {
function onLinkClick(link) {
const WEB_PAGES = {
home: <Home
x={this.state.x}
/>,
new: <NewPost />,
settings: <Settings />,
signup: <SignUp />,
login: <Login />
};
return (
<div>
{WEB_PAGES.link}
</div>
);
}
return (
<div>
<Nav
y={this.state.y}
z={this.state.z}
/>
{onLinkClick(this.state.lastClickedLink)}
</div>
);
}
}
export default App;
I removed some code for brevity's sake. The error I'm getting with this setup is TypeError: Cannot read property 'state' of undefined for home under the WEB_PAGES object.
I initially thought that this was pointing to the WEB_PAGES object, but changing this to App showed that state was undefined as well. I'm not really sure what to do at this point.
Is the enums conditional rendering method even doable on this scale? And if not, what other method would be the most ideal for this situation? Many thanks!

In javascript, When you create a function using function keyword it creates his own new scope and also creates default object this. So while you were trying to access this.state.x then it will not state property inside the function. It becomes this.undefined.x. so it is giving the error.
Whereas arrow function {(() => {})} does not create this object but create internal scope.
try following render method in your code:
render() {
return (
<div>
<Nav
y={this.state.y}
z={this.state.z}
/>
{((link) => {
const WEB_PAGES = {
home: <Home
x={this.state.x}
/>,
new: <NewPost />,
settings: <Settings />,
signup: <SignUp />,
login: <Login />
};
return (
<div>
{WEB_PAGES[link]}
</div>
);
})(this.state.lastClickedLink)}
</div>
);
}

use {WEB_PAGES[link]} when you try to use . it will not work

const Link = ({ lastClickedLink }) => {
const WEB_PAGES = {
home: <Home x={lastClickedLink} />,
new: <NewPost />,
settings: <Settings />,
signup: <SignUp />,
login: <Login />
};
return (
<div>
{WEB_PAGES[link]}
</div>
);
}
render() {
return (
<div>
<Nav
y={this.state.y}
z={this.state.z}
/>
<Link lastClickedLink={lastClickedLink} />
</div>
);
}
This variant has more readability and extensibility. Based on Shubham Batra example.

Related

How to access ref that was set in render

Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />

Send props to an array of components

I have an array of components that I want to render, but I don't know how to send props to each of them. Do you have any idea how? I am using also using formik for form management and the page components consist of basic inputs.
const pages = [<Page1 />, <Page2 />, <Page3 />, <Page4 />]
and I am rendering them this way:
<div>{pages[state]}</div>
You can accomplish this by using React.cloneElement:
const newProps = {}
<div>{React.cloneElement(pages[state], newProps)}</div>
React.cloneElement
There are 2 options to achieve this:
Option 1: On the declaration site you may pass the state while initialising the array.
const pages = [<Page1 props={page1Prop: 'value'}/>,
<Page2 props=page2Props />,
<Page3 props=page3Props />,
<Page4 props=page4Props />]
Or if you do not want or can not update the declaration you may pass when rendering:
Or you can
Option 2: clone the component prop and apply the new props
...
render() {
return (
<div>
{React.cloneElement(pages[currentPage] propsForPage[currentPage])}
OR
{React.cloneElement(pages[currentPage] {someProp:'value'})}
</div>
);
}
}
Could you not initialise the component in the array?
const pages = [Page1, Page2, Page3, Page4]
return (
<div>
{pages.map(page => {
const Component = <page />
return (
<Component {...props} />
)
}
</div>
)

Refresh Component on route parameter change

Why the component doesn't update the props when changing the route param?
Trying to use setState inside componentWillReceiveProps but it doesn't even fire?
export default class HomePage extends React.PureComponent {
movies = require('../../movieList.json');
state = {
match: this.props.match,
id: null
}
componentWillReceiveProps(np) {
if(np.match.params.id !== this.props.match.params.id) {
console.log('UPDATING PROPS')
this.setState({ match: np.match })
}
}
render() {
const { match } = this.props;
const movie = this.movies[match.params.id || movieOrder[0]];
const nextMovie = getNextMovie(match.params.id || movieOrder[0]);
const prevMovie = getPrevMovie(match.params.id || movieOrder[0]);
return (
<>
<h1>{movie.title}</h1>
<Link to={nextMovie}>Next</Link>
<Link to={prevMovie}>Prev</Link>
</>
);
}
}
nextMovie and prevMovie get the id which should be set inside link. Unfortunatelly it sets only during 1st render. When clicked I can see the url change, but no updates are fired
Here is the component holding the switch
export default class App extends React.PureComponent {
state = {
isLoading: true,
}
componentDidMount() {
PreloaderService.preload('core').then(() => {
console.log('LOADED ALL');
console.log('PRELOADER SERVICE', PreloaderService);
this.setState({ isLoading: false });
console.log('Preloader assets', PreloaderService.getAsset('dog-video'))
});
}
render() {
const { isLoading } = this.state;
if(isLoading) {
return(
<div>
is loading
</div>
)
}
return (
<div>
{/* <Background /> */}
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/:id" component={props => <HomePage {...props} />} />
<Route component={NotFoundPage} />
</Switch>
<GlobalStyle />
</div>
);
}
}```
Seems to be working with my codesandbox recreation.
I had to improvise a bit with the missing code:
What's the value of movieOrder[0]? Is 0 a valid value for match.params.id?
How do getNextMovie and prevMovie look like?
Also, you're not using the Home component's state in your render logic, so I think, it should render the correct movie even without them.
There are several things off here. componentWillReceiveProps is considered unsafe and is deprecated. Try using componentDidUpdate instead. Also your class component needs a constructor. As it is now state is defined as a static variable.
constructor(props) {
super(props);
this.state = {
match: this.props.match,
id: null
}
}
componentDidUpdate(np) {
if(np.match.params.id !== this.props.match.params.id) {
console.log('UPDATING PROPS')
this.setState({ match: np.match })
}
}
Last, double check to make sure your logic is correct. Right now the Next and Prev buttons link to the same thing:
const nextMovie = getNextMovie(match.params.id || movieOrder[0]);
const prevMovie = getPrevMovie(match.params.id || movieOrder[0]);
<Link to={nextMovie}>Next</Link>
<Link to={prevMovie}>Prev</Link>
As an alternative, if you want to stick with componentWillReceiveProps you need to unbind the current movie, and rebind it with a new one. Here is a code example doing the exact same thing with users.

react-responsive: cannot pass props to children of a Responsive component

I am using the common use case indicated in the ReadMe:
const Desktop = props => (
<Responsive {...props} minWidth={1680} maxWidth={2560} />
)
const LaptopL = props => (
<Responsive {...props} minWidth={1440} maxWidth={1679} />
)
...
and I have a prop (bottleState) that I am trying to pass to specific components inside the Responsive component (eg Desktop):
const WineHeader = ({ bottleState }) => (
<HeaderCard>
<Desktop>
<WineBox />
<WineBackgroundBox />
<VineyardBackgroundBox />
<WineInfoContainer>
<LeftContainer>
<WineTypeBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
<WineDescriptionBox bottleState={bottleState} />
</LeftContainer>
<WineProperties />
</WineInfoContainer>
</Desktop>
<LaptopL>
<WineBox />
<WineBackgroundBox />
<VineyardBackgroundBox />
<WineInfoContainer>
<LeftContainer>
<WineTypeBox />
<WineTitleBox />
<WineDescriptionBox />
</LeftContainer>
<WineProperties />
</WineInfoContainer>
</LaptopL>
...
</HeaderCard>
)
WineHeader.propTypes = Object.freeze({
bottleState: PropTypes.object, //eslint-disable-line
})
export default WineHeader
When logging the bottleState prop in one of the above child components in which I am trying to access it - it is not available (the log returns undefined) :
const WineTypeBox = ({ bottleState }) => (
<WineTypeStyle>{console.log(bottleState)}</WineTypeStyle>
)
> undefined ---- WineTypeBox.jsx?a13c:36
and when I simply remove the Responsive component, I can access the bottleState prop as expected:
const WineHeader = ({ bottleState }) => (
<HeaderCard>
<WineBox />
<WineBackgroundBox />
<VineyardBackgroundBox />
<WineInfoContainer>
<LeftContainer>
<WineTypeBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
<WineDescriptionBox bottleState={bottleState} />
</LeftContainer>
<WineProperties />
</WineInfoContainer>
...
</HeaderCard>
)
returns the bottleState object when logged to the console:
{wineCollection: "Classics", wineType: "Semi Sweet Red", bottleName: "Khvanchkara", bottleImage: Array(1), bottleNoteText: "<p>test</p>", …}
bottleImage: ["http://localhost/uploads/bottle.png"]
bottleName: "Khvanchkara"
bottleNoteText: "<p>test</p>"
temperatureHigh: null
vintage: null
volume: null
wineCollection: "Classics"
wineType: "Semi Sweet Red"
__proto__: Object ---- WineTypeBox.jsx?a13c:36
Any ideas why this is the case? I have tried defining the desktop function inside the WineHeader functional component, because that is the function where I am pulling off the bottleState prop from this.props but this doesn't change the behaviour; when throwing a debugger before the return statement of the Desktop component, I can clearly see the bottleState prop being passed in, I do not even need it to be passed in as I am directly passing it into other components nested further down the DOM tree without any issue when the Desktop Component is not wrapping them, but the fact that my other components that need to access this prop are nested inside the Desktop component is causing the props to be blocked for some reason. Any help is greatly appreciated. Thanks,
Corey
So, it's possible that you're expecting to look at the <Desktop> and actually looking at the <LaptopL> screen:
const WineHeader = ({ bottleState }) => (
<HeaderCard>
<Desktop>
...
<WineTypeBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
<WineDescriptionBox bottleState={bottleState} />
...
</Desktop>
<LaptopL>
...
<WineTypeBox /> // <- pass props here
<WineTitleBox /> // <- pass props here
<WineDescriptionBox /> // <- pass props here
...
</LaptopL>
</HeaderCard>
)
Which you mentioned fixed it in the comments above. Kudos!
For reference:
It's also good practice with react-responsive to add a <Default ...> wrapper.
This can happen with other libraries, and require a different fix.
Eg. The initial destructure of props into ({ bottleState }) might mean that react-responsive can't access props in order to pass them onto their child components. When this happens with external libraries, it can be fixed in several ways:
Pass the props with the spread syntax
const WineHeader = props => (
...
...
...
)
OR
const WineHeader = ({bottleState, foo, bar}) => (
...
<Desktop>
...
<WineTypeBox {...{bottleState, foo, bar}} />
...
</Desktop>
)
Pass a single component, or wrap the libraries JSX inner components because the library can't handle multiple child components
const WineHeader = ({bottleState}) => (
...
OR
const WineHeader = ({bottleState}) => (
<HeaderCard>
<Desktop>
<Fragment>
<WineTitleBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
...
</Fragment>
</Desktop>
Passing props using React Context

React Native: custom component that is accessible from every app screen

What is the best 'React' way of creating a custom component that can be accessible from any screen?
Think of a custom Alert component. I need it in every screen of my app, so when an error occurs I can show it.
Currently I am doing it this way:
// AlertModal.js
import ... from ...;
const AlertModal = (props) => {
const {isSuccess, headerText, bodyText, onClosed, isOpen, onBtnPress} = props;
return (
<Modal
...
isOpen={isOpen}
onClosed={() => onClosed()}
>
<View ...>
<Text>{headerText}</Text>
<Text>{bodyText}</Text>
<Button
...
onPress={() => onBtnPress()}
>
...
</Button>
</View>
</Modal>
)
};
export default AlertModal;
//Foo.js
import ...from ...;
const Foo = (props) => {
const { alertIsSuccess, alertHeaderText, alertBodyText, alertIsOpen, alertOnClosed, alertOnBtnPress, onBtnPress, ...rest } = props;
return (
<View style={}>
...
<View style={}>
...
</View>
<Button
onPress={() => onBtnPress()}
/>
<AlertModal
isSuccess={alertIsSuccess}
headerText={alertHeaderText}
bodyText={alertBodyText}
isOpen={alertIsOpen}
onClosed={alertOnClosed}
onBtnPress={alertOnBtnPress}
/>
</View>
)
};
export default QrCodeReader;
//FooContainer.js
import ... from ...;
class FooContainer extends Component {
constructor(props) {
super(props);
this.state = {
bar: false,
baz: true,
bac: '',
bax: false,
bav: '',
// Alert
alertIsOpen: false,
alertIsSuccess: false,
alertHeaderText: '',
alertBodyText: '',
alertOnClosed() {},
alertOnBtnPress() {},
};
this.onBtnPress = this.onBtnPress.bind(this);
}
alert(isSuccess, headerText, bodyText, onBtnPress, onClosed) {
const self = this;
this.setState({
alertIsOpen: true,
alertIsSuccess: isSuccess,
alertHeaderText: headerText,
alertBodyText: bodyText,
alertOnBtnPress: onBtnPress || function () { self.alertClose() },
alertOnClosed: onClosed || function () {},
});
}
alertClose() {
this.setState({
alertIsOpen: false,
});
}
onBtnPress() {
this.alert(
true,
'header text',
'body text',
)
}
render() {
return (
<Foo
onBtnPress={this.onBtnPress}
{...this.state}
/>
);
}
}
export default FooContainer;
As you can see it is a pain (and I think an incorrect way). Doing this way I would need to include AlertModal component in every component of my app where I need to display alerts = duplicate props and making new unnecessary <AlertModal /> components.
What is the correct way?
p.s. I use react-native-router-flux as a router in my app.
p.s.s. I am coming to React-native from a Meteor.js + Cordova. There I can just create one modal and include it in the main layout and show/hide it when necessary with the appropriate dynamic text inside it.
This is how I navigate in my app:
//Main.js
class Main extends Component {
render() {
return (
<Router backAndroidHandler={() => true}>
<Scene key="root">
<Scene key="login" type='reset' component={SignInContainer} initial={true} hideNavBar={true}/>
<Scene key="qrCode" type='reset' component={FooContainer} hideNavBar={true} />
</Scene>
</Router>
);
}
}
Based on the fact that you use react-native-router-flux I can suggest:
Rework AlertModal into scene component on is own, do not use Modal. You can control what to display in it by passing properties.
Include AlertModal component in your router as 'modal' schema like this:
import AlertModal from '../components/AlertModal';
//Main.js
class Main extends Component {
render() {
return (
<Router backAndroidHandler={() => true}>
<Scene key="root">
<Scene key="login" type='reset' component={SignInContainer} initial={true} hideNavBar={true}/>
<Scene key="qrCode" type='reset' component={FooContainer} hideNavBar={true} />
<Scene key="alertModal" duration={250} component={AlertModal} schema="modal" direction="vertical" hideNavBar={true} />
</Scene>
</Router>
);
}
}
And then you can just call it from any scene with:
Actions.alertModal({ >optional props you want to pass< });
Not sure if is desired behaviour but this kind of modals may be dismissed in Android with back button. If not, there is a way around it.
This might not be the best solution, but what you can do is use the context functionality of React. Essentially, pass a function reference that triggers your component to show/hide (maybe by changing its state, for instance) into the context. Then every child in the hierarchy should be able to call that function.
However, Facebook discourages using the context functionality, and it might give you some problems when debugging, since it is not as easy to track as your props/state.
Another solution that comes to mind might be playing with Redux, creating an action that changes a property to which the component is subscribed. Then all your other components can just dispatch that action, changing the value of the property that makes the modal component to display.

Categories

Resources