React Loading a Dynamic Template using JSX & ES6 - reactjs

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}
)
}

Related

Getting React component instance from ReactDOM.render() return

I have custom component Slider for Formio. Function attachReact() is attaching my custom component inside element provided by Formio. After attaching is done I am supposed to get an instance for that component from ReactDOM.render() returned right away but I am not. Later that instance is used by Formio to change field values which I need.
How can I make sure I get an instance from ReactDOM returned right away? I realise that feature will be removed in future from React but I still need it while Formio is using it.
import { ReactComponent } from "#formio/react";
import React from "react";
import ReactDOM from "react-dom";
import settingsForm from "./Slider.settingsForm";
class SliderCustomComp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
}
setValue = (v) => {
this.setState({ value: v }, () => this.props.onChange(this.state.value));
};
render() {
return (
<div className="w-full" id="custcomp">
<input
className="w-full focus:outline-none"
type="range"
min={this.props.component.minRange}
max={this.props.component.maxRange}
value={this.state.value}
onChange={(e) => {
this.setValue(e.target.value);
}}
/>
<span>{this.state.value}</span>
</div>
);
}
}
export default class Slider extends ReactComponent {
constructor(component, options, data) {
super(component, options, data);
}
static schema() {
return ReactComponent.schema({
type: "sliderCustomComp",
label: "Default Label",
});
}
static editForm = settingsForm;
attachReact(element) {
console.log(element);
const instance = ReactDOM.render(
<SliderCustomComp
component={this.component} // These are the component settings if you want to use them to render the component.
value={this.dataValue} // The starting value of the component.
onChange={this.updateValue} // The onChange event to call when the value changes.}
/>,
element
);
console.log("instance in attachReact", instance);
return instance;
}
}
Use prop ref to get the instance:
const instanceRef = React.createRef();
ReactDOM.render(
<SliderCustomComp
ref={instanceRef}
component={this.component} // These are the component settings if you want to use them to render the component.
value={this.dataValue} // The starting value of the component.
onChange={this.updateValue} // The onChange event to call when the value changes.}
/>,
element
);
const instance = instanceRef.current;

How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?

So I have a SSR app using Next.js. I am using a 3rd party component that utilizes WEB API so it needs to be loaded on the client and not the server. I am doing this with 'two-pass' rendering which I read about here: https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
I'm trying to figure out why when 'ssrDone' state changes in the next.js page state the entire <Layout> component unnecessarily re-renders which includes the page's Header, Footer, etc.
I've read about React.memo() as well as leveraging shouldComponentUpdate() but I can't seem to prevent it from re-rendering the <Layout> component.
My console.log message for the <Layout> fires twice but the <ThirdPartyComponent> console message fires once as expected. Is this an issue or is React smart enough to not actually update the DOM so I shouldn't even worry about this. It seems silly to have it re-render my page header and footer for no reason.
In the console, the output is:
Layout rendered
Layout rendered
3rd party component rendered
index.js (next.js page)
import React from "react";
import Layout from "../components/Layout";
import ThirdPartyComponent from "../components/ThirdPartyComponent";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
ssrDone: false
};
}
componentDidMount() {
this.setState({ ssrDone: true });
}
render() {
return (
<Layout>
{this.state.ssrDone ? <ThirdPartyComponent /> : <div> ...loading</div>}
</Layout>
);
}
}
export default Home;
ThirdPartyComponent.jsx
import React from "react";
export default function ThirdPartyComponent() {
console.log("3rd party component rendered");
return <div>3rd Party Component</div>;
}
Layout.jsx
import React from "react";
export default function Layout({ children }) {
return (
<div>
{console.log("Layout rendered")}
NavBar here
<div>Header</div>
{children}
<div>Footer</div>
</div>
);
}
What you could do, is define a new <ClientSideOnlyRenderer /> component, that would look like this:
const ClientSideOnlyRenderer = memo(function ClientSideOnlyRenderer({
initialSsrDone = false,
renderDone,
renderLoading,
}) {
const [ssrDone, setSsrDone] = useState(initialSsrDone);
useEffect(
function afterMount() {
setSsrDone(true);
},
[],
);
if (!ssrDone) {
return renderLoading();
}
return renderDone();
});
And you could use it like this:
class Home extends React.Component {
static async getInitialProps({ req }) {
return {
isServer: !!req,
};
};
renderDone() {
return (
<ThirdPartyComponent />
);
}
renderLoading() {
return (<div>Loading...</div>);
}
render() {
const { isServer } = this.props;
return (
<Layout>
<ClientSideOnlyRenderer
initialSsrDone={!isServer}
renderDone={this.renderDone}
renderLoading={this.renderLoading}
/>
</Layout>
);
}
}
This way, only the ClientSideOnlyRenderer component gets re-rendered after initial mount. 👍
The Layout component re-renders because its children prop changed. First it was <div> ...loading</div> (when ssrDone = false) then <ThirdPartyComponent /> (when ssrDone = true)
I had a similar issue recently, what you can do is to use redux to store the state that is causing the re-render of the component.
Then with useSelector and shallowEqual you can use it and change its value without having to re-render the component.
Here is an example
import styles from "./HamburgerButton.module.css";
import { useSelector, shallowEqual } from "react-redux";
const selectLayouts = (state) => state.allLayouts.layouts[1];
export default function HamburgerButton({ toggleNav }) {
let state = useSelector(selectLayouts, shallowEqual);
let navIsActive = state.active;
console.log("navIsActive", navIsActive); // true or false
const getBtnStyle = () => {
if (navIsActive) return styles["hamBtn-active"];
else return styles["hamBtn"];
};
return (
<div
id={styles["hamBtn"]}
className={getBtnStyle()}
onClick={toggleNav}
>
<div className={styles["stick"]}></div>
</div>
);
}
This is an animated button component that toggles a sidebar, all wrapped inside a header component (parent)
Before i was storing the sidebar state in the header, and on its change all the header has to re-render causing problems in the button animation.
Instead i needed all my header, the button state and the sidebar to stay persistent during the navigation, and to be able to interact with them without any re-render.
I guess now the state is not in the component anymore but "above" it, so next doesn't start a re-render. (i can be wrong about this part but it looks like it)
Note that toggleNav is defined in header and passed as prop because i needed to use it in other components as well. Here is what it looks like:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
I'm using an id and fn because all my layouts are stored inside an array in redux, but you can use any logic or solution for this part.

String from mobx store used as a react component name?

Using a map feels a bit repetative.
Any way to render react component using string passed down from mobx?
because when i have like 20 different dynamic components its quickly getting messy and repetative.
currently I'm using:
function ParentComponent(){
const compNames = {
component1: <component1/>,
component2: <component2/>,
}
const component = compNames[store.name];
return(
<div>
<MyComponentName type={type}/>
</div>
)
}
is anything shorter possible? for example
function ParentComponent(){
const {name: MyComponentName, type } = store
return(
<div>
<MyComponentName type={type}/>
</div>
)
}
the Parent component then imported into the index page.
// MobX store
import UserList from "./UserList";
import UserView from "./UserView";
#observable
component = {
"user-list": UserList,
"user-view": UserView
};
// observer component
#observer function UserComponent({type}) {
const Component = store.component[type];
return <Component />;
}
// display the component
<UserComponent type="user-list" />

passing an event to a child component in React

I'm new to React and this is a very noob question, but I don't understand why this is not working.
I'm trying to build a simple todo List.
My TodoList.js Component looks like this:
import React, {Component} from 'react';
import TodoItem from './TodoItem';
export default class TodoList extends Component{
constructor(props){
super(props);
this.state = {
todos:[
{
title:"todo1"
},
{
title:"todo3"
},
{
title:"todo2"
}
]
}
}
handleRemove(idx){
alert('works');
}
render(){
var todos = this.state.todos.map(function(t,idx){
return(<TodoItem
remove={this.handleRemove.bind(this,idx)}
title={t.title}
/>)
})
return (
<div>
<h1>To do</h1>
<div>{todos}</div>
</div>
)
}
}
My child Component looks like this:
import React, {Component} from 'react';
export default class TodoItem extends Component{
render(){
return (
<div>{this.props.title}
<button onClick={this.props.remove}>X</button>
</div>
)
}
}
But I get a TypeError with "Cannot read property 'handleRemove' of undefined". I'm wondering why inside the map function {this} is undefined?
I tried to put this this.handleRemove = this.handleRemove.bind(this) into the constructor.
Didn't change anything. Shouldn't this also be defined inside the .map() ?
You need to put this as the second argument
If a thisArg parameter is provided to map, it will be used as
callback's this value. Otherwise, the value undefined will be used as
its this value. The this value ultimately observable by callback is
determined according to the usual rules for determining the this seen
by a function.
on map:
render(){
var todos = this.state.todos.map(function(t,idx){
return(<TodoItem
remove={this.handleRemove.bind(this,idx)}
title={t.title}
/>)
}, this)
return (
<div>
<h1>To do</h1>
<div>{todos}</div>
</div>
)
}
}
Alternatively, you can use an ES6 arrow function to automatically preserve the current this context:
var todos = this.state.todos.map((t,idx) => {
return(<TodoItem
remove={this.handleRemove.bind(this,idx)}
title={t.title}
/>)
})

Render React Component assigned from an import to a variable.. how?

So, I have a need for dynamic determination of which component to show.. so, for example. I have:
import Component1 from '..somepath/Component1'
import Component1 from '..somepath/Component2'
var P = {
red: Component1,
blue: Component2
}
render() {
var newComponent = P[color];
return (
<newComponent /> // not working
{newComponent} // not working
newComoponent // not working
)
}
this mapping could be huge, thus not doing a switch or if/else.
how do I get this to return in another component?
As per the convention the component name must be with the first letter capitalised:
render() {
var NewComponent = P[color];
return (
<NewComponent />
);
}
References:
https://facebook.github.io/react/docs/jsx-in-depth.html#html-tags-vs.-react-components

Resources