Dynamically rendering react components from string array - javascript

Running into an issue rendering components dynamically as the come off the CMS in the react code.
Having no problem getting & parsing the variable names into an array to be utilized in the actual rendering - but receiving errors here no matter the method I'm using:
Warning: is using uppercase HTML. Always use lowercase
HTML tags in React.
Warning: is using uppercase HTML. Always use lowercase HTML tags in React.
Which clearly shows I'm using caps :)
import React, {
Component
} from 'react';
import {
createClient
} from 'contentful';
import CtaBlock from './CTABlock';
import DeviceList from './DeviceList';
class HomeContainer extends Component {
constructor(props) {
super(props);
this.state = {
pageCont: [],
entries: []
};
}
componentDidMount() {
const client = createClient({
// This is the space ID. A space is like a project folder in Contentful terms
space: '...',
// This is the access token for this space. Normally you get both ID and the token in the Contentful web app
accessToken: '...'
});
client.getEntries({
'sys.id': '5stYSyaM8gkiq0iOmsOyKQ'
}).then(response => {
this.setState({
mainCont: response
});
});
}
getEntries = pageCont => {
var linkedEntries = pageCont.includes.Entry;
console.log(linkedEntries);
return linkedEntries;
};
render() {
var formattedComponents = [];
var renderedComponents = [];
if (this.state.mainCont) {
//getting the type of component from the Contetful API (String)
var componentList = this.getEntries(this.state.mainCont).map(entries => entries.sys.contentType.sys.id);
//converting the component names to upper case
formattedComponents = componentList.map(comps => comps.charAt(0).toUpperCase() + comps.slice(1));
renderedComponents = formattedComponents.map(MyComponent => {
return <MyComponent / >
});
}
return (
<div>
<h1> Dynamically Generated Components Div </h1>
{renderedComponents}
</div>
);
}
}
export default HomeContainer;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Appreciate any insight!

When I understand you right, what you want to archive is, to map a string key to a certain component, right?
So that entries.sys.contentType.sys.id contains a string like "ctaBlock"or "deviceList"?
I would suggest using a map as follows:
import CtaBlock from './CTABlock';
import DeviceList from './DeviceList';
import FallbackComponent from './FallbackComponent';
const keyMap = {
ctaBlock : CtaBlock,
deviceList : DeviceList,
default: FallbackComponent,
};
...
componentList.map( entries => {
const key = entries.sys.contentType.sys.id;
const Component = keyMap[ key ] || keyMap.default;
return <Component />;
} );
See an example on:
https://jsfiddle.net/v7do62hL/2/

Related

How do I load and run external Javascript code in React that have their definitions in the application and not in the imported file?

Basically, I'm trying to run a function that creates and adds a Recipe class to an array in React based on an external javascript file that is hosted online - but all the definitions are inside my React app.
The external file looks like (Recipes.js) this:
function LoadRecipes(){
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
The way I attempt to go on with this follows:
import React, {useState, useEffect} from 'react';
import RecipeManager from "../logic/RecipeManager.js";
const Recipe = RecipeManager.Recipe;
const recipesList = RecipeManager.recipesList;
const AddToRecipes = RecipeManager.AddToRecipes;
function RecipeController() {
const [loadingRecipes, setLoadingRecipes] = useState(true);
useEffect(() => {
const script = document.createElement("script");
script.src = "https://raw.githack.com/Soralei/extern/main/Recipes.js";
script.async = true;
script.onload = () => {
setLoadingRecipes(false);
}
document.body.appendChild(script);
}, []);
useEffect(() => {
if(!loadingRecipes){
window.LoadRecipes();
}
}, [loadingRecipes]);
return (
<div>
{loadingRecipes ? <p>Loading recipes...</p> : (
<>
<p>Recipes:</p>
{/*recipesList.map((a, index) => <p key={"r"+index}>{a.name}</p>)*/}
</>
)}
</div>
)
}
export default RecipeController
Note that I try to run the function using window.LoadRecipes() once the script has been imported. However, I get undefined errors when the function is run:
Recipes.js:3 Uncaught ReferenceError: AddToRecipes is not defined
at LoadRecipes (Recipes.js:3)
I'm also adding the content of RecipeManager.js for clarity. This is local logic, and the goal is to have the external function make use of it:
class Recipe{
constructor(options = {}){
this.name = options.name || "Unnamed Recipe";
this.components = options.components || [];
this.requirements = options.requirements || [];
this.craftedAt = options.craftedAt || "handcrafted";
}
}
const recipesList = [];
function AddToRecipes(Recipe){
recipesList.push(Recipe);
console.log(Recipe.name, "was added to the recipes list.");
}
const exported = {
Recipe: Recipe,
recipesList: recipesList,
AddToRecipes: AddToRecipes
}
export default exported;
Is this not possible, or am I just doing this entirely wrong?
Why am I doing this? The idea is to host the recipes online in a way that allows for other people to easily view, edit, and have the changes affect my app directly, while keeping most of the work in the React app.
You have to export the function to be able to access it.
Default export (only one per file):
function LoadRecipes(){
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
export default LoadRecipes; // export
You should import it like this:
import LoadRecipes from 'pathtofile';
Named export (multiple ones):
export function LoadRecipes() {
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
export const add (a, b) => a + b; // another one
Import like this (using { }):
import {
LoadRecipes,
add
} from 'pathtofile';
Named exports are useful to export several values. During the import, one will be able to use the same name to refer to the corresponding value. Concerning the default export, there is only a single default export per module. A default export can be a function, a class, an object or anything else. This value is to be considered as the “main” exported value since it will be the simplest to import.
You can read about JavaScript modules here

Render HTML for conditional routes in Next Js

I am trying to figure out, why my HTML is not rendering for conditional routes, actually, I have three components, one is parent index.js inside slug directory and two are child components country.js, profile.js inside components directory.
I want to keep same URL pattern for country and profile component
domain.com/[countryCode] or domain.com/[profileUsername]
Please have a look at the code below.
index.js
import React from 'react'
import { withRouter } from 'next/router'
import Country from '../../components/Country'
import Details from '../../components/Details'
class Type extends React.Component {
constructor(props){
super(props)
this.state = {
isCountry: ''
}
}
checkCountry = (type) => {
let isCountry = ''
if(type){
const countries = JSON.parse(localStorage.getItem('countries'))
let countryCodes = []
isCountry = countries.map(item => item.country.toLowerCase()).includes(type)
this.setState({isCountry:isCountry})
}
}
componentDidUpdate(prevProps){
if(this.props.router.query.slug !== prevProps.router.query.slug){
this.checkCountry(this.props.router.query.slug)
}
}
componentDidMount(){
this.checkCountry(this.props.router.query.slug)
}
render() {
const {isCountry} = this.state
return (
<React.Fragment>
{isCountry ? (
<Country/>
) : (
<Details/>
)}
</React.Fragment>
)
}
}
the above code is working fine as expected, but the problem is, It is not returning HTML into the view source code. Please suggest how can I sort out the above issue
You should use getStaticProps to provide next.js information about your request to API so next.js could cache your API responses (or similar functions, find what you need in the documentation: https://nextjs.org/docs/advanced-features/automatic-static-optimization).
Could be useful: https://nextjs.org/docs/advanced-features/static-html-export.

Getting an weird while following React tutorial

I was following an Youtube tutorial for React.js project, and the tutor didnt face any error and I am facing one for exact same code, any help would be appreciated - error after compiled
my react code - https://pastebin.com/htzES36G
import React, { Component } from 'react'
import items from './data'
const RoomContext = React.createContext();
//
class RoomProvider extends Component {
state={
rooms:[],
sortedRooms: [],
featuredRooms: [],
loading: true
};
componentDidMount(){
let rooms = this.formatData(items);
}
formatData(items) {
let tempItems = items.map(item => {
let id = item.sys.id
let images = item.fields.items.map(image => image.fields.file.url);
let room = {...item.fields, images, id};
return room;
});
return tempItems;
}
render() {
return <RoomContext.Provider value={{...this.state}}>
{this.props.children}
</RoomContext.Provider>
}
}
const RoomConsumer = RoomContext.Consumer;
export {RoomProvider, RoomConsumer, RoomContext};
please tell me if you need to take a look at other files
Yes, you're trying to access a field called items on the fields object, but it doesn't exist. I think it's a simple typo and you're trying to access images, as that lines up with the data you're accessing. I know it's easy to overlook things, but always doublecheck complex data types!
let images = item.fields.images.map(image => image.fields.file.url);

Onepage with Gatsby JS and Contentful, how to import my first simple string

I am trying to useStatic Query and GraphQL to get a simple title from
Contentful, pass it to state and then show in the render. I cant make it work. I am attaching an image showing my current setup and errors.
Possible problems: 1. the query returns an array, and I need to change it into a string, or access 0 element, the first one, because my content type is just one, as it is a onepage.
Placing of the query in the component, I am not sure if it can be in the constructor of an component
For comparison: in the screen from my file you can see a variable name showing Josh Perez, when I uncomment it and add it to this.state = { dataTest: name}, then in RENDER: this.state.dataTest returns the name Josh Perez well, so passing a variable to state works, but passing a string from graphql query is not possible for me...
I have a limitation which is that I need to create my page component with a class, because of the fact that in the Component did mount I am placing some JQuery, which works well for me.
THIS IS MY TEST CODE
1. In Constructor
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
const name = 'Josh Perez';
this.state = { dataTest: name };
}
In render
{this.state.dataTest}
This works, the variable name is passed to state and shown in render.
However, I want to show in this way a simple text string from Contentful. So I am trying code like this (error message is shown in the screens):
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
//const name = 'Josh Perez';
const data = useStaticQuery(graphql`
query {
allContentfulHomepage (limit: 1) {
edges {
node {
section1Title
}
}
}
}
`)
this.state = { dataTest: data };
It turns out, that the below suggested solution works. I am putting below
my attempt at callingfurther content. It does not work. It displays the following error "Cannot read property 'map' of undefined". I would be very grateful for a suggestion how to improve it, how to make it work.
export default class Test extends Component {
state = {
dataTest: this.props.data.test.edges.map(({ node: test }) =>
test.section1Title),
dataTest2: this.props.data.test.edges.map(({ node: test }) =>
test.section2Lead),
dataTest3: this.props.data.test.edges.map(({ node: test }) =>
test.section1Text.json)
}
render() {
return <div>
<h1>{this.state.dataTest}</h1>
<h1>{this.state.dataTest2}</h1>
{documentToReactComponents(this.state.dataTest3)}
</div>
}
}
export const query = graphql`
{
test:allContentfulHomepage(limit: 1) {
edges {
node {
section1Title
section2Lead
section1Text {
json
}
}
}
}
}
`
If you're writing a page component as a class, you don't need to use the UseStaticQuery, you can use the simple PageQuery for this purpose.
To loop through arrays, the map() method works as well.
UPDATE
import React, { Component } from 'react';
import { graphql } from 'gatsby';
export default class Test extends Component {
render() {
const { edges } = this.props.data.test;
return (
<div>
{edges.map(({ node: itemFromContentful }) => (
<h1>{itemFromContentful.section1Title}</h1>
<h1>{itemFromContentful.section2Lead}</h1>
{documentToReactComponents(section1Text.json)}
))}
</div>
);
}
}
export const query = graphql`
{
test:allContentfulHomePage(limit: 1) {
edges {
node {
section1Title
}
}
}
}
`
Whats happening:
The GraphQL query you're using is bringing the data you want from the Contentful;
The React Stateful Component (class Test) is receiving all the data available from the query as a prop;
We're accessing this data on the render() method using the destructing assignment;
we're accessing the data nodes through the map method (the one I suggested you to take a look;
The curly braces into the JSX allows you to use JS to manipulate what you want - In this case, to render the information.

ReactJS values of Const on div

I'm new on react and have a simple application as this:
My purpose for this app it's to consume an Spring Boot REST Service and print the data json on my react app.
I achieve this following this tutorial: https://github.com/marmelab/restful.js/tree/master
But now I'm stuck on a simple problem, don't know how to print the values on a html tag, this is an example of my code:
import React from 'react';
import request from 'request';
import restful, { requestBackend } from 'restful.js';
const api = restful('http://someUrl/v1/mobile', requestBackend(request));
const totals = api.one('statusOrders',1); //organizationID = 1
totals.get().then((response) => {
const requestBody = response.body();
const totalsOrders = requestBody.data(); /*Need to print this on the <div>**/
})
class RestGps extends React.Component {
render(){
return(
<div className="container">
<p>Hello World</p>
//Here I want to print the values.
</div>
)
}
}
export default RestGps
The const totalsOrders has the values of the request, the request structure it's like this:
{
"remissionOk": 109,
"remissionAlert": 5,
"remissionError": 17,
"remissionOutOfTime": 82
}
¿Can someone please tell me how can I print this totalsOrders on my html as my text "Hello World"? Regards.
First you need to change some things around.
Your totalOrders object needs to be within the scope of your RestGps class.
You aren't using states, which can cause a lot of weird behaviour!
I suggest doing the following:
import React from 'react';
import request from 'request';
import restful, { requestBackend } from 'restful.js';
const api = restful('http://someUrl/v1/mobile', requestBackend(request));
const totals = api.one('statusOrders',1); //organizationID = 1
class RestGps extends React.Component {
state = { text: "loading ..." };
componentDidMount = () => {
totals.get().then((response) => {
const requestBody = response.body();
const totalsOrders = requestBody.data(); // assuming this is object
this.setState({ text: JSON.stringify(totalsOrders) });
})
}
render = () => {
return(
<div className="container">
<p>{this.state.text}</p>
//Here I want to print the values.
</div>
)
}
}
export default RestGps
Why are using states?
Well. Initially, your component won't have any data to show. This is because get takes time to fetch remote resources.
So what will tell react to re-render your component, to show the text once the resource gets loaded? Nothing! This is why you need to use states.
What is componentDidMount?
componentDidMount is a function part of the react lifecycle. It is called when the component initially gets rendered. When it renders, you want to fetch the resource, then update our state.
How do we pass the string into the paragraph element?
This is simple, you can just reference your state in the render function, using this.state.text, then add it as a text node in <p>
You can make use of life cycle methods.
class RestGps extends React.Component {
state = {
totalsOrders : null,
};
componentDidMount() {
const api = restful('http://someUrl/v1/mobile', requestBackend(request));
const totals = api.one('statusOrders',1); //organizationID = 1
totals.get().then((response) => {
const requestBody = response.body();
const totalsOrders = requestBody.data(); /*Need to print this on the <div>**/
this.setState({totalsOrders: totalsOrders });
})
}
render(){
const {totalsOrders} = this.state;
return(
<div className="container">
<p>Hello World</p>
totalsOrders.map(item => {
{item}
});
</div>
)
}
}

Categories

Resources