How to use axios result in various react classes - reactjs

I have basically an html document with various DOM elements that I want to fill with data coming from react.
<html> ....
<div id="value1">
....
<div id="value2">
</html>
Then I have a index.js file that associates each dom element with a (different) react class:
import ClassName1 from ?...?;
ReactDOM.render(<ClassName1 />, document.getElemnentById('value1'));
No I have a React class that does something like that:
class ClassName1 extends Component
constructor()
...
componentDidMount()
axios-call
=> return value1,value2
What I want to achieve is
render(value1 => DOM(1) , value2 => DOM(2))

What you want to do doesn't seem like something you'd want to use react for. But I'll give you two scenarios:
Scenario 1: You want two "child components" to render with different values from what you got from the axios call.
The best solution for this is just to render two different components, like so:
class Foo extends React.Component {
constructor() {
this.state = {
value1: null,
value2: null,
};
}
componentDidMount() {
axios(someUrl).then(response => {
const value1 = response.body.value1;
const value2 = response.body.value2;
this.setState({ value1, value2 });
});
}
render() {
return(
<React.Fragment>
<div id="value1">{value1}</div>
<div id="value2">{value2}</div>
</React.Fragment>
);
}
}
Scenario 2: You want to change other DOM elements that are not part of the react tree.
In this case, you could get the DOM elements by their ID and change the content when the axios call returns, something like this:
class Foo extends React.Component {
constructor() {
this.state = {
value1: null,
value2: null,
};
}
componentDidMount() {
axios(someUrl).then(response => {
const value1 = response.body.value1;
const value2 = response.body.value2;
this.setState({ value1, value2 });
const element1 = document.getElementById('value1');
element1.innerHTML = value1;
const element2 = document.getElementById('value2');
element2.innerHTML = value2;
});
}
render() {
return(null);
}
}
In scenario 2, you'd be changing stuff outside of react tree... which is unconventional, but it seems to be what you're asking for. Remember that you'll need divs with value1 and value2 ids in your DOM before the axios call returns with values.

Simple answer (but bad) - You can simply render twice in different root as shown below.
import React from "react";
import ReactDOM from "react-dom";
function App({ value }) {
return <div className="App">{value}</div>;
}
const root1 = document.getElementById("root1");
ReactDOM.render(<App value="value1" />, root1);
const root2 = document.getElementById("root2");
ReactDOM.render(<App value="value2" />, root2);
But the problem is that, now those two components aren't in the same React tree, thus can't communicate with each other.
If you need a central container that retrieves data but want to pass data, you can use React Portal in v16+.
You can do something crazy like this with Portal.
Suppose that you want to pass value1 & value2 to following divs.
<body>
<div id="root"></div>
<div id="value1">
<div id="value2">
...
</body>
where root div is main React tree is mounted on.
You can create a portal by mounting onto those value1&2 divs like following.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class ValuePortal extends Component {
constructor(props) {
super(props);
this.el = document.getElementById(props.elementID);
}
render() {
return ReactDOM.createPortal(this.props.value, this.el);
}
}
function App() {
return (
<div className="App">
<ValuePortal elementID="value1" value="for value1" />
<ValuePortal elementID="value2" value="for value2" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Your main container is mounted in the root as shown above and values are displayed using portal component, ValuePortal.
You can follow along on CodeSandBox.

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;

Why the data not displayed in nextjs?

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

How can I replace Markdown-rendered HTML tags with React components?

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

Trouble loading an objects src in ReactJS

I am still a bit new to React and posting on this forum so please bear with me. I currently have two React files which I believe are talking to each other, but there seems to be a disconnect when trying to pull information from an object. One of my React files is:
import React, { Component } from 'react';
import './App.css';
export class App extends React.Component {
render() {
const src = this.props.src;
const alt = this.props.alt;
const width = this.props.width;
const height = this.props.height;
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Will''s weird online shop thing I have no idea about</h1>
</header>
<p className="App-intro">
Click the arrows to browse through the different items.
</p>
<img src={src} alt={alt} width={width} height={height} />
</div>
);
}
}
export default App;
and the other is :
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import {App} from './App';
import registerServiceWorker from './registerServiceWorker';
import dogbag from './images/dogbag.jpg';
const DogBagObj = {
src: dogbag,
alt: 'Cute dog handbag',
height: '100px',
width: '70px'
};
const Items = [
DogBagObj,
'https://i.pinimg.com/736x/0b/f4/bd/0bf4bd031a363fc68b56afe6289f450f--random-pokemon-pokemon-stuff.jpg',
'https://pbs.twimg.com/profile_images/881211588748988416/zQL9OLuc_400x400.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Sun-crypto-accelerator-1000.jpg/1200px-Sun-crypto-accelerator-1000.jpg'
]
class OnlineStore extends React.Component {
constructor(props) {
super(props);
this.state = { currentItem: 0 };
this.interval = null;
this.changeItem = this.changeItem.bind(this);
}
changeItem() {
let current = this.state.currentItem;
let next = ++current % Items.length;
this.setState({ currentItem: next });
}
componentDidMount() {
this.interval = setInterval(this.changeItem, 1000);
}
render() {
const src = Items[this.state.currentItem];
return <App src={src} />;
}
}
ReactDOM.render(
<OnlineStore />,
document.getElementById('root'));
registerServiceWorker();
I am confident that I have correctly imported the dogbag.jpg from the image folder and the three images which have direct links to them load correctly.
I feel like my problem lies within getting the DogBagObj.src to correctly read. If I change DogBagObj in the Items array to dogbag it will load the image but I would also like the ability to control multiple tags for each image (such as the alt, height and width). Is there some minor syntax error I am over looking or is this a problem which would be much harder to remedy? Thank you for your time.
Your items array contains multiple data structure but you treat it as if it contains only one.
Either use strings only or objects only.
For example..
Strings only:
const Items = [
DogBagObj.src,
'https://i.pinimg.com/736x/0b/f4/bd/0bf4bd031a363fc68b56afe6289f450f--random-pokemon-pokemon-stuff.jpg',
'https://pbs.twimg.com/profile_images/881211588748988416/zQL9OLuc_400x400.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Sun-crypto-accelerator-1000.jpg/1200px-Sun-crypto-accelerator-1000.jpg'
]
Or use objects with similar data structure:
const Items = [
DogBagObj,
{src:'https://i.pinimg.com/736x/0b/f4/bd/0bf4bd031a363fc68b56afe6289f450f--random-pokemon-pokemon-stuff.jpg'},
{src:'https://pbs.twimg.com/profile_images/881211588748988416/zQL9OLuc_400x400.jpg'},
{src:'https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Sun-crypto-accelerator-1000.jpg/1200px-Sun-crypto-accelerator-1000.jpg'}
]
And in your render method:
render() {
const src = Items[this.state.currentItem].src;
return <App src={src} />;
}

React Loading a Dynamic Template using JSX & ES6

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

Resources