HTML Within Loop Not Rendering in React - javascript

I'm attempting to list queried posts in the DOM using Gatsby. The small snippet below should suffice because the Graphql query is correctly fetching data. This snippet is within the component, which is also rendering correctly. I'm new to Gatsby and React, and I hope I'm missing something pretty basic.
<div className="container">
Hello! // Renders
{newsItems.map(newsItem => {
console.log(newsItem.context.id); // Logs each news item id to console correctly
Hello! // Does not render
<p>{newsItem.context.id}</p> // Does not render
})}
</div>

You've used curly braces, so this is how you return component from map function.
{newsItems.map(newsItem => {
console.log(newsItem.context.id); // Logs each news item id to console correctly
return (
<>
Hello!
<p>{newsItem.context.id}</p>
</>
)
})}
Haven't tried this, but it should work. This also only works if newsItems contains values. React Fragments <>...</> were used because return must only return single component. You could also use <div>...</div> instead of fragment.
Try this.

Related

What does <Component render={({ state }) => {}} /> do in React?

I am currently learning ReactJS, and i wanted to use fullPageJS. It's working correctly, but there is some of the syntax that i don't quite get.
The component:
function home() {
return (
<ReactFullpage
render={({ state, fullpageApi }) => {
return (
<ReactFullpage.Wrapper>
<div className="section">
<h1>Slide 1</h1>
</div>
<div className="section">
<h1>Slide 2</h1>
</div>
</ReactFullpage.Wrapper>
);
}}
/>
)
}
export default home;
Now my question, what does render={({ state, fullpageApi }) => { return(); }} /> do? I can see that it is a property, but i don't really get what it's use is.
This is a pattern known as a Render Prop. It's a way to decide what to render via some two-way communication between your code and the code in ReactFullpage.
You pass a function in as one of the props. That alone a fairly common thing to do, such as passing in a function to an onClick prop. What's special here is how that function gets used.
While ReactFullpage is rendering, it will call your function. It's basically saying "hey, here's some data (the state and fullPageApi). What would you like me to render based on that data?" Your function then calculates and returns the elements you want it to render. And then ReactFullpage will use those elements in its final output.
For more information on render props, see react's documentation on the pattern
In react, render is a method that tell react what to display( what the DOM should look like) . Return in a method or function is the output of the method or function.
I'm also learning React! But since your question is just JS related, here's your answer.
First, let's put your snippet in pieces.
render={ ({ state, fullpageApi }) => { return (/** HTML */); } }
render= is XML, we con't care about it. It is HTML: you're passing a property to the Component element. The brackets {} indicate that it's JS in the HTML code: it means that you're giving JS values to the element's render property. The remaining JS code that was between the {} is:
({ state, fullpageApi }) => { return (/** HTML */); }
Which is a function! So the render prop takes a function that'll probably get executed later.
This anonymous function takes an object as a parameter, but it destructures it and only uses the state and fullpageAPI props, which makes it possible to use them as variables: instead of writing obj.state, you'd write state, as an example. You can read more about restructuring on the MDN docs. It then returns the XML in the parenthesis.

React setState late updation with API calls [duplicate]

This question already has answers here:
if-else statement inside jsx: ReactJS
(17 answers)
Closed 2 years ago.
I am new to React and as I was creating a project, I came across a rather peculiar event with my code.
It is with the async nature of the setState which doesn't let me render data on my page like i want it to.
I am trying to display files which i have in my database already, onto my webpage. But since the state is set after a while therefore i am not able to render my files onto the screen using the map function, since when it is called it is undefined.
I implplemented a get method in so as to get the json response of the files that i want to display. But as I mount my component and setstate of the Files, it shows that it doesn't have any value in it.I know it is async but i have no idea how to handle it so that i can use map to display onto the webpage.
My code is as follows:
import React, {Component} from "react";
// import axios from 'axios';
import {Link} from 'react-router-dom';
import {Styles} from '../Styling/Styles';
class FileView extends Component {
state = {
fileViewData : {}
}
// viewFunction(){
componentDidMount(){
fetch('http://127.0.0.1:8000/api/uploads/', {
method: 'GET'
})
.then((response) => {
let data = response.json();
return data;
})
.then((data) => {
this.setState({fileViewData: data}, ()=>{
console.log("hi");
});
}).catch(error => {console.log(error)})
// console.log(fileViewData);
}
render(){
return (
<React.Fragment>
<Styles>
<div className = "appbar">
<Link to='/dashboard'>
<button className="homeBtn" label="Back" >
Back
</button>
</Link>
</div>
{/* <button label="View" onClick={this.viewFunction} >
View
</button> */}
</Styles>
//.....section not working since map is not a function of undef......//
{
this.state.fileViewData.map(item =>{
return (
<h2>{item.fields.file}</h2>
);
})
}
//.......section not working ends.........//
{console.log(this.state.fileViewData)}
</React.Fragment>
);
}
}
export default FileView;
The console output is something like this:
The empty object is returned twice and then the data is returned twice.I am not running any kind of loop either.
How should I set the value so that i am able to display my files onto the screen? TIA!
Looks like your data is an array, so your initial state should also be an array
state = {
fileViewData: [],
}
Then your check for array length will be correct, but regular javascript doens't quite work the same in JSX. You'll need to conditionally render JSX.
Conditional Rendering
{this.state.fileViewData.length
? this.state.fileViewData.map(...)
: null
}
Since it seems you don't really render anything if there is no data, you can simply map the data as array::map correctly handles empty arrays.
{
this.state.fileViewData.map(...)
}
Set state to an empty array
Remove the if condition and anonymous function declaration from your map statement
You declare that function but never invoke it also you don't need it.
if you insist on checking the length
{
this.state.fileViewData.length &&
this.state.fileViewData.map(item =>{
return (
<h2>{item.fields.file}</h2>
);
})
}
You are getting multiple console logs because when you set state in React you cause the component to render again.
As for your implementation, you probably want the initial value of fileViewData to have the same data structure as what your back end hands back. Currently you start with a plain javascript object, and then turn it into an array of objects once the response comes from your back end.
Without any consideration for a loading period, a simple thing to do to make your application not crash until data loads in would be to make fileViewData an empty array when it is initialized, not an object with no keys. Then it would have the correct prototype to have the method map.

Refactoring logic outside of Context.Consumer

To get my desired behavior I ended up nesting ternarys inside of my Context.Consumer and it looks pretty ugly.
I have tried refactoring out some of the logic, but I have been unable to get rerenders to trigger when the props/state change unless I use Context.Consumer.
Working on an app for a project. It uses SWAPI to search for pretty much anything in the Star Wars universe. I have a that changes depending on the user's input, and then loads the results of their search.
I started out just trying to get the results to render after the fetch finished and was having some issues of handling the props/state update of the component not triggering a re-render. We recently learned about React's Context api so I decided to try using that in this simple project even though it's 100% not necessary. I did some reading and ended up with the Context.Consumer. It works great for what I want, and triggers re-renders when I need it to.
render() {
return (
<AppContext.Consumer>
// value here is: characterData: this.state.characterData from App.js
// characterData is in App.js' state and is updated after the fetch is successful.
{value => (
<section className="results-container" style={this.styles}>
{/* FIX refactor this \/ */}
{/*
If they haven't searched for anything, characterData doesn't exist yet,
well it's an empty {object} so it renders the 'Enter a name and hit submit!'
If they search and the results are empty, it tells them to try another name.
If the search works it renders the results.
*/}
{value.characterData.results ? (
value.characterData.results.length > 0 ? (
value.characterData.results.map(item => (
<p style={this.styles} key={item.url} className={item.index}>
{item.name ? item.name : item.title}
</p>
))
) : (
<span>Coulnd't find anything! Try another name or topic.</span>
)
) : (
<span className="default-results-text">
Enter a name and hit submit!
</span>
)}
{/* FIX refactor this /\ */}
</section>
)}
</AppContext.Consumer>
);
}
I am not getting any errors, just trying to figure out a way to clean this up.
When trying to move logic outside the Context.Consumer I have to fall back to using state/props and then it never rerenders.
I've tried using componentWillReceiveProps() and static getDerivedStateFromProps() but again couldn't get rerenders to trigger.
Hosted on Zeit: https://starwars-search-nm7mk0268.now.sh/
I ended up refactoring all of the logic to the parent component and just returning the value of all the logic. This allowed me to get away from nested ternarys and clean up this component's render method

Render custom React component within HTML string from server

I have a HTML string that comes from the server, for example:
const myString = '<p>Here goes the text [[dropdown]] and it continues</p>`;
And I split this string into 3 parts so the result is the following:
const splitString = [
'<p>Here goes the text ',
'[[dropdown]]',
' and it continues</p>'
];
Then I process those 3 parts in order to replace the dropdown with a React component:
const processedArr = splitString.map((item) => {
if (/* condition that checks if it's `[[dropdown]]` */) {
return <Dropdown />;
}
return item;
}
So after all, I get the processed array, which looks like this:
['<p>Here goes the text ', <Dropdown />, ' and it continues</p>']
When I render that, it renders the HTML as a text (obviously) with the Dropdown component (that renders properly) in between the text. The problem here is that I cannot use { __html: ... } because it has to be used such as <div dangerouslySetInnerHTML={{ __html: ... }} />. I cannot add <div> around the string because that would cut out the <p> tag.
I thought about splitting the parts into tags and then in some sort of loop doing something like:
React.createElement(tagName, null, firstTextPart, reactComponent, secondTextPart);
but that would require fairly complex logic because there could be multiple [[dropdown]]s within one <p> tag and there could be nested tags as well.
So I'm stuck right now. Maybe I'm looking at the problem from a very strange angle and this could be accomplished differently in React. I know that React community discourages rendering HTML from strings, but I cannot go around this, I always have to receive the text from the server.
The only stackoverflow question I found relevant was this one, however that supposes that content coming from backend has always the same structure so it cannot be used in my case where content can be anything.
EDIT:
After some more digging, I found this question and answer which seems to be kinda solving my problem. But it still feels a bit odd to use react-dom/server package with its renderToString method to translate my component into a string and then concatenate it. But I'll give it a try and will post more info if it works and fits my needs.
So after playing with the code, I finally came to a "solution". It's not perfect, but I haven't found any other way to accomplish my task.
I don't process the splitString the way I did. The .map will look a bit different:
// Reset before `.map` and also set it up in your component's constructor.
this.dropdownComponents = [];
const processedArr = splitString.map((item) => {
if (/* condition that checks if it's `[[dropdown]]` */) {
const DROPDOWN_SELECTOR = `dropdown-${/* unique id here */}`;
this.dropdownComponents.push({
component: <Dropdown />,
selector: DROPDOWN_SELECTOR
});
return `<span id="${DROPDOWN_SELECTOR}"></span>`;
}
return item;
}).join('');
Then for componentDidMount and componentDidUpdate, call the following method:
_renderDropdowns() {
this.dropdownComponents.forEach((dropdownComponent) => {
const container = document.getElementById(dropdownComponent.selector);
ReactDOM.render(dropdownComponent.component, container);
});
}
It will make sure that what's within the span tag with a particular dropdown id will be replaced by the component. Having above method in componentDidMount and componentDidUpdate makes sure that when you pass any new props, the props will be updated. In my example I don't pass any props, but in real-world example you'd normally pass props.
So after all, I didn't have to use react-dom/server renderToString.
How about break the text apart and render the component separately?
your react component should look like this (in JSX):
<div>
<span>first text</span>
{props.children} // the react component you pass to render
<span>second part of the text</span>
</div>
and you would just call out this component with something like:
<MessageWrapper>
<DropdownComponent/> // or whatever
</MessageWrapper>

Mithril.js: What does "Component template must return a virtual element, not an array, string, etc." mean?

Trying out mithril.js for the first time, so apologies if this is simple question. I'm getting an error message,
Component template must return a virtual element, not an array,
string, etc.
when I nest a map() derived components in another component.
Essentially, I was trying to create a layout like the following simplified example, where the address tags are derived from a .map() off of the ctrl.list() prop.
<Layout>
<Left/>
<Right>
<Address>
<Address>
</Right>
</Layout>
Here's a jsfiddle that shows this. If you mount the "Right" component on its own, it renders fine. It's only when that component is nested in another component where it fails. What am I missing, and how do I debug this in the future?
https://jsfiddle.net/uLmt9qq7/
In Mithril v0.2.X, a nested component (ie a component inside another) can't return an array of nodes1: it must return a single wrapping node with multiple children.
It looks like your code violates that rule by accident as a result of a typo in the Right view:
return m('.right-col'),
ctrl.list().map(function(addr){
return m(Address, {addr: addr});
})
In pseudo-code this is return {node}, [list], where the {node} is ditched and the view returns the unwrapped [list] - which triggers the error. It should be:
return m('.right-col',
ctrl.list().map(function(addr){
return m(Address, {addr: addr});
})
)
Here's a fixed fiddle.
1 This will no longer be the case in the rewritten Mithril v1.X, where component views can return lists of nodes directly.

Categories

Resources