I have an app using React. It includes the React Router. In one of my components, I need to read the query string, and conditionally show one block if the query string parameter has a value. If the value is not present in the query string, I want to show another block.
import React, {Component} from 'react';
import {Container} from 'reactstrap';
import {NavMenu} from './NavMenu';
export class Layout extends Component {
static displayName = Layout.name;
render() {
return {
<div>
<NavMenu /> <!-- I want to hide this is query string has hideNav=true -->
<Container> <!-- I want to use a fluid layout if the query string has hideNav=true -->
{this.prop.children}
</Container>
</div>
}
}
}
How do I conditionally render content based on the query string value.
Just to represent a quick example let me suggest a URL with a query string parameter what the code operates with. It's called hideNav as below:
https://yourawesomewebsite.com/?hideNav=true
As a first step:
We need to get the query string what you need work with in your render method. There are quite a couple of options to get the query parameters, let me summarize here:
1. Using the window object
let queryString = new URLSearchParams(window.location.search);
let hideNav = queryString.get('hideNav');
Read further about URLSearchParams in this link. Highlighted from the mentioned URL:
The URLSearchParams interface defines utility methods to work with the query string of a URL.
2. Using the React Router way
let queryString = this.props.match.params;
let hideNav = queryString.hideNav;
Please follow this link if you want to read further. Additionally let me highlight below the key point here:
params - (object) Key/value pairs parsed from the URL corresponding to the dynamic segments of the path
As a second step:
The code needs to use the conditional rendering based on the value.
import React, {Component} from 'react';
import {Container} from 'reactstrap';
import {NavMenu} from './NavMenu';
export class Layout extends Component {
static displayName = Layout.name;
render() {
const hideNav = // here you can decide based on the above explanation which one you want to use
return {
<div>
{ !hideNav ? <NavMenu /> : null }
<Container fluid={hideNav}>
{this.prop.children}
</Container>
</div>
}
}
}
About fluid container you can read further here:
fluid (boolean) - Allow the Container to fill all of it's available horizontal space.
So basically rendering NavMenu once the hideNav value is true other than that React renders null. On the Container the code is using the fluid property to manipulate the horizontal space.
Assuming you have params in your react router routes you can get those via this.props.match.params:
export class Layout extends Component {
static displayName = Layout.name;
render() {
const {hideNav} = this.props.match.params;
return {
<div>
{!hideNav && <NavMenu />} // Render NavMenu if hideNav is false
<Container fluid={hideNav}>
{this.prop.children}
</Container>
</div>
}
}
}
Hi check these conditions based example
class Layout extends Component {
constructor(props) {
super(props);
this.state = {
hideNave: false
}
}
componentDidMount() {
axios.get('http://url')
.then(res => {
if (res.date) {
this.setState({
hideNave: true
})
}
});
}
render() {
return
(<div>
{ this.state.hideNave ? (<NavMenu />) : (<Container> {this.prop.children} </Container>) }
</div>)
}
}
Related
I have a class component as shown below:
import React, { Component } from "react";
import Aux from "../../../hoc/Auxilary/Auxilary";
import ComponentDetails from "./SideBarLayoutDetails";
import DropDownDetails from "../UI/DropDown/DropDownDetails";
import Row from "../UI/Row/Row";
import "./Layouts.css";
import Dropdown from "../UI/DropDown/DropDown";
import Calendar from "../UI/Calendar/Calendar";
class SideBarLayout extends Component {
state = {
isActive: Array(DropDownDetails.length),
isDateActive: false,
}
DropDownArrowHandler = (index) =>
{
}
render()
{
const calendar = this.state.isDateActive ? <Calendar />:null;
return <Aux>
<div className = "SideBar-Central">
<Row Elems = {ComponentDetails.TopHeaders.elements}
Container = {ComponentDetails.TopHeaders.container}
/>
<Dropdown Elems = {DropDownDetails[0].DateFilter}
Container = {DropDownDetails[0].DateFilterContainer}
/>
{calendar}
</div>
</Aux>
}
}
export default SideBarLayout;
I want to initialize this.state.isActive in a loop fashion like this:
const initializedvalue = DropDownDetails.map(elem => elem.active);
and then use this initializedvalue to assign to this.state.isActive.
But I want to do this just when the component renders in the screen. What is the best way to do so? Should I use constructor? I don't prefer it as I get a warning using super(props). Please let me know the best way to do so. My end goal is to have a this.state.isActive array ready to be used in making the decisions about rendering the screen components.
First, forEach does not return any value as it is a void function. So try map function instead. This will return elements for which active is true (assuming active is a boolean type property in elem)
const initializedvalues = DropDownDetails.map(elem => elem.active);
Next, if you want to use this to set this.state.isActive after render, run it in componentDidMount(). Its a lifecycle function that runs after the initial render is done.
I am making a very very simple nextjs application where I am trying to fetch the data from api.
My requirement is I should display the data in layout.js file and this layout.js file is a children in index.js file.
index.js:
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js:
import React from "react";
import fetch from "isomorphic-unfetch";
function Layout(props) {
return (
<div>
<p>Preact has {props.stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
Layout.getInitialProps = async () => {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
return { stars: json.stargazers_count };
};
export default Layout;
So as per the above given code, I have called the layout page inside index.js page (in my real application I need to call like this only so no changes in calling layout inside index)..
But when I made a console.log() in the function Layout.getInitialProps in layout, it doesn't print anything and hence the api data not fetched..
Complete working demo here with code
Why can't I fetch the data inside the layout.js while calling as a children from index.js?
Also provide me the right updated solution to achieve this.. I really searched for many questions but none solved my issue and I couldn't understand those solutions clearly so please help me with the above given example.
That because getInitialProps can only be added to the default component exported by a page, adding it to any other component won't work.
You should use componentDidMount() or useEffect instead, or move getInitialProps in the index and then pass the result to the component. something like (not tested) :
index.js :
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js
import React from "react";
import fetch from "isomorphic-unfetch";
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
stars: false
};
}
async componentDidMount() {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
this.setState({ stars: json.stargazers_count });
}
render() {
const { stars } = this.state;
return (
<div>
<p>Preact has {stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
}
export default Layout;
Edit:
Example with class component
Bonus: If you want to add the layout for all the pages of your app this isn't the best approach, instead you should take a look to custom _app.js, example
I am new to React and am really struggling.
I want to create a small app that:
Reads in data from my data.js file (an object)
Outputs it each sub-object info into a card component
I have created a component called Lister where the data is imported, and iterated over using forEach.
I then want to call the Card component and output the data on the page in this component for each sub-object.
However I keep getting the error that the render in my Lister component does not return anything.
It is a small component so here it is in full:
import React from 'react';
import data from "../../data/data";
import Card from "../card";
class Lister extends React.Component {
render() {
return (
Object.keys(data()).forEach(function(key) {
<Card data={data()[key]}/>
})
)
}
}
export default Lister;
For React you'll want to be using .map to handle your loops. Also any time you're using JSX, remember you add the open and closing curly braces {} (you were missing one right before Object). Lastly, everything inside of the return inside of render needs to be wrapped in a single root node like a <div> for example.
Here's an example of how you might rewrite this:
import React from 'react';
import data from "../../data/data";
import Card from "../card";
class Lister extends React.Component {
render() {
const items = data();
return (
<div>
{Object.keys(items).map(function(key, index) {
return <Card key={index} data={items[key]}/>
})}
</div>
);
}
}
export default Lister;
I think you should use .map instead of .forEach. With .forEach, you can handle that like this:
render() {
const items = data();
let cards=[];
Object.keys(items).forEach(function(key,i) {
objArray.push(<Card key={i} data={items[key]})
});
return (<div>{cards}</div>)
}
At the moment, I'm converting the Markdown to HTML code using marked, then I replace some parts of it with React elements. This results in an array of HTML strings and React elements, which can be rendered indeed:
const prepareGuide = markdown => replaceToArray(
marked(markdown),
/<a href="SOME_SPECIAL_HREF".*?>(.*?)<\/a>/,
(match, label, slug) => <a href={`/${slug}`}>{label}</a>
)
const Guide = ({ guide }) =>
prepareGuide(guide.fields.text).map(
n => typeof n === 'object'
? n
: <span dangerouslySetInnerHTML={{ __html: n }} />
)
The problem with this, let's just call it workaround, is that every piece of HTML needs a wrapper element, like span (and uses dangerouslySetInnerHTML).
What I basically need is the ability to replace the rendered HTML elements with React components to add React functionality like Router links and other, custom elements.
Any other approaches?
Edit: The replaceToArray function I used is like String.prototype.replace, but returns an array (so any type can be returned)
Edit: Another approach I had was to render the HTML directly to the DOM (using dangerouslySetInnerHTML), and using the container element's ref to query all elements I want to replace. But, next problem: To render React components inside the HTML ref I have, I'd need another React root, which is possible, but unpractical, because I'd lose all the contexts (like Router), so I can't even properly use Router Links that way.
I was able to solve this as follows:
I kept using marked and dangerouslySetInnerHTML to directly set the HTML. Now, as described in the second approach, I used the ref to query the elements I want to replace. Now to be able to render React elements to the HTML, I just used the ReactDOM.render function.
The biggest problem with this was that components didn't have access to the app's context, since I now had multiple React roots. To solve this, I figured out that we can copy the context from one component to another: Is it possible to pass context into a component instantiated with ReactDOM.render?
So to be able to access the context in the component that renders the HTML, we need to set the component's contextTypes for the contexts we need to copy.
class Article extends Component {
static contextTypes = {
router: PropTypes.any
}
static propTypes = {
markdown: PropTypes.string
}
prepare(ref) {
const Provider = createContextProvider(this.context)
const links = Array.from(ref.querySelectorAll('a'))
links.forEach((link) => {
const span = document.createElement('span')
const { pathname } = url.parse(link.href)
const text = link.innerText
link.parentNode.replaceChild(span, link)
ReactDOM.render(
<Provider>
<Link to={pathname}>{text}</Link>
</Provider>,
span
)
})
}
render() {
return (
<article
ref={this.prepare}
dangerouslySetInnerHTML={{ __html: marked(this.props.markdown) }}
/>
)
}
}
The above code requires the snipped which I copied from the question linked above. The method I called prepare replaces specific HTML nodes with React roots.
function createContextProvider(context) {
class ContextProvider extends React.Component {
getChildContext() {
return context
}
render = () => this.props.children
static propTypes = { children: PropTypes.node }
}
ContextProvider.childContextTypes = {}
Object.keys(context).forEach(key => {
ContextProvider.childContextTypes[key] = PropTypes.any.isRequired
})
return ContextProvider
}
So we basically have a function that creates a Provider component. That function needs to be able to dynamically adapt to the required context types, that's why the loop sets them to be required.
If you simply want to have links work with React Router, you can render the markdown as usual with dangerouslySetInnerHTML then intercept internal link clicks to make them go through react-router.
Full example of loading .md externally, then catching links to process with react-router:
import React from "react"
import { withRouter } from 'react-router'
import catchLinks from 'catch-links'
import marked from "marked"
import styles from './styles.scss'
class ContentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
markdown: '',
title: ''
}
}
componentDidMount() {
if (typeof window !== 'undefined') {
catchLinks(window, (href) => {
this.props.history.push(href);
});
}
const page = location.pathname.replace('/', '');
fetch(`/assets/content/${page}.md`)
.then(response => response.text())
.then(text => {
this.setState({ markdown: marked(text, {}) })
});
const title = page_titles[page] || capitalize(page);
if (title) {
document.title = title;
this.setState({title})
}
}
render() {
const {
markdown,
title
} = this.state;
return (
<div class={styles.contentPage}>
<div class={styles.pageTop}>
{title}
</div>
<div class={styles.page}>
<div class={styles.content} dangerouslySetInnerHTML={{__html: markdown}}></div>
</div>
</div>
);
}
}
export default withRouter(ContentPage);
I'm pretty new to React, coming from an angular world. I have a scenario where I need to dynamically load a component give a searchType prop. The end user will have a dropdown of searchTypes they can pick from, this prop is passed in after they click the submit button.
I have a SearchResults component defined, which should dynamically load the appropriate component depending on the value of this.props.searchType.name
import React, { findDOMNode, Component, PropTypes } from 'react';
import Material from './Material'; // Material Search Results Component
import Vendor from './Vendor'; // Vendor Search Results Component
export default class SearchResults extends Component {
constructor(props){
super(props);
}
// searchType = {
// name: 'Material',
// id: 'MATERIAL'
// }
render() {
const { searchType, items, itemsCount } = this.props;
var ComponentName = searchType.name;
return (
<div className='row'>
<h1>Search Results ({items.length}/{itemsCount})</h1>
<ComponentName items={items} />
</div>
);
}
}
SearchResults.propTypes = {
items: PropTypes.array.isRequired
};
Now, this seems to partially work as when using the React Dev Tools in chrome I can see the provider/component show up in the DOM.. but it doesn't render.
I'm just not sure where to go next from here, or if i'm doing something wrong.
You're trying to use a string instead of the actual class. I think you want something like this:
var searchTypes = {
Material,
Vendor,
};
// ...
var Component = searchTypes[searchType.name];
return (
<div className='row'>
<h1>Search Results ({items.length}/{itemsCount})</h1>
<Component items={items} />
</div>
);
Here's a simple example.
You could try a switch statement.
render() {
//...
var component;
switch(searchType.name){
case "material":
component = <Material items={items} />
case "vendor":
component = <Vendor items={items} />
}
return (
//...
{component}
)
}