I've been trying to learn React in spite of a major lack of Javascript experience recently, and I've hit a bit of an unexpected snag. I am rewriting my personal website (which is basically static HTML with some PHP on the backend), and one of the things I want to do is load the details of my CV from an external JSON file (with the next step being to move from a static JSON file to a call to an endpoint that will return the data).
In my CV, I have a list of achievements for several of the jobs, and in a few of these I want to return a small amount of markup (in one case a link to a patent I was the inventor on and in another just a span applying styling to a single sentence to call attention to some pro-bono work). I understand that I can use the dangerouslySetInnerHTML() method in React to pass in the String containing the markup and have it rendered... bu I can't seem to get it to work.
Here is the component in question:
import React, {Component} from 'react';
class WorkEntry extends Component {
constructor(props) {
super(props);
var _this = this;
this.usedSkillsTags = [];
this.responsibilitiesTags = [];
this.achievementsTags = [];
this.props.technologiesUsed.forEach(function (item) {
_this.usedSkillsTags.push(<li>{item}</li>)
});
this.props.responsibilities.forEach(function (item) {
_this.responsibilitiesTags.push(<li>{item}</li>)
});
this.props.achievements.forEach(function (item) {
if(item.indexOf("<") > -1 && item != null ) {
_this.achievementsTags.push(<li dangerouslySetInnerHTML={ { _html : item.toString() } }/>)
}
else{
_this.achievementsTags.push(<li>{item}</li>)
}
});
}
render() {
return (
<div>
<div class="row">
<div class="well">
<div class="row">
<div class="col-sm-12">
<h3>{this.props.companyName} -
<small> {this.props.jobTitle}</small>
</h3>
</div>
</div>
<div class="row">
<div class="col-sm-12">{this.props.startDate} - {this.props.endDate}</div>
</div>
<div class="row">
<div class="col-sm-4">
<h4 class="text-center">Skills Used</h4>
<ul class="wrapped-list">
{this.usedSkillsTags}
</ul>
</div>
<div class="col-sm-8">
<h4 class="text-center">Responsibilities</h4>
<ul>
{this.responsibilitiesTags}
</ul>
</div>
</div>
{ this.achievementsTags.length > 0 &&
<div class="row">
<div class="col-sm-12">
<h4 class="text-center">Notable Projects and Achievements</h4>
<ul>
{this.achievementsTags}
</ul>
</div>
</div>
}
</div>
</div>
</div>
);
}
}
export default WorkEntry;
And here is the Json that is causing the issue:
{
"companyName": "Eloquence Communications",
"jobTitle": "Lead Developer",
"startDate": "January 2013",
"endDate": "February 2014",
"technologiesUsed": [
"Java",
"SQL",
"Idea",
"Bamboo",
"Nexus",
"Maven",
"Git",
"JavaFX",
"Python"
],
"responsibilities": [
"Designed overall architecture for hospital Nurse Call System",
"Managed complex build process for multiple Java applications using Git, Maven, Nexus, and Bamboo",
"Proposed idea which was eventually patented by employer",
"Lead a small team of developers to convert an idea into a product"
],
"achievements": [
"After proposing a method of nursing staff tracking and authentication using NFC, was named as an inventor on <a href='http://patents.justia.com/patent/20140330575'>patent application #20140330575</a>"
]
}
Finally, here is the error I see:
I've obviously read the link in the error message, but I can see no difference between the code in the documentation and my own. Can anyone lend some expertise here?
dangerouslySetInnerHTML needs __html(note two underscores) and not _html
Change it to
dangerouslySetInnerHTML={ { __html : item.toString() } }
Related
I have a Vue 3 app using Pinia stores that CRUD's data from my rest API. I've just started working with Vue 3 (from smaller vue 2 projects) and this is my first time using Pinia, so I'm still learning the intricacies of both.
One resource I manage from my api is called Applications, and I have a composable that manages API calls to retrive all apps, 1 app, or update the selected app. Instead of creating a form component to UPDATE, and a form component to CREATE applications, I'd like to create a single form component that handles both. So far I can populate my form with an existing application using a route that contains an application_id, and I create a new application if no application_id is in my route.params. I'm just not sure how to tell the form "Hey lets update this application instead of creating it.". I thought of using v-if directives that each create a <button> (one to run update, one to run create method) depending on there is an application_id in my route.params, but that seems inefficient (it may be correct, I'm just lacking knowledge). Here's my code:
// ApplicationStore.js (pinia store)
import { defineStore } from "pinia";
// Composable for axios API calls
import { getApplications, getApplicationByID, createApplication } from "#/composables/applications";
export const useApplicationStore = defineStore("application", {
state: () => ({
applications: [], //list of applications from database
application: {}, //currently selected application for edit form
loading: false,
success: "Successfully Created",
error: "",
}),
getters: {},
actions: {
async fetchApplications() {
this.loading = true;
this.applications = [];
const { applications, error } = await getApplications();
this.applications = applications;
this.error = error;
this.loading = false;
},
async fetchApplicationByID(id) {
this.loading = true;
const { application, error } = await getApplicationByID(id);
this.application = application;
this.error = error;
this.loading = false;
},
async createNewApplication() {
this.loading = true;
const { application, results, error } = await createApplication(this.application);
this.application = application;
this.error = error;
this.loading = false;
if (results.status === 201) {
// show this.success toast message
}
}
}
});
Here is my ApplicationForm component. It currently looks for route.param.id to see if an application is selected, if so it populates the form:
// ApplicationForm.vue
<template>
<section class="columns">
<div class="column">
<div v-if="error" class="notification is-danger">{{ error }}</div>
<div class="field">
<label class="label">Name</label>
<input v-model="application.name" class="input" type="text" />
</div>
<div class="field">
<label class="label">Location</label>
<input v-model="application.location" class="input" type="text" />
</div>
<div class="control">
<button #click="createNewApplication" class="button">Save</button>
</div>
</div>
</section>
</template>
<script setup>
import { useRoute } from "vue-router";
import { useApplicationStore } from "#/stores/ApplicationStore";
import { storeToRefs } from "pinia";
const route = useRoute();
const { applications, application, error } = storeToRefs(useApplicationStore());
const { createNewApplication } = useApplicationStore();
//checking if there's an id parameter, if so it finds the application from the list in the store
if (route.params.id) {
application.value = applications.value.find(app => app.id === Number(route.params.id));
} else {
//form is blank
application.value = {};
error.value = "";
}
</script>
Is there a preferred way to use this single form for both create and updates? I wonder if slots would be a good use case for this? But then I think I'd still end up making multiple form components for each CRUD operation. Also, I considered using a v-if to render the buttons based on if an application is in the store or not, like this:
<button v-if="route.params.id" #click="updateApplication" class="button">Update</button>
<button v-else #click="createNewApplication" class="button">Save</button>
I can't help but feel there is a better way to handle this (it is something I'll utilize a lot in this and future projects). This is my first big vue/pinia app. I'm loving the stack so far but these little things make me question whether or not I'm doing this efficiently.
If the form's UI is mainly expected to stay the same except for a few small differences (e.g. the button text), you could make the form emit a custom "submit" event and then handle that event from the parent component where you render the form (i.e. on the update page you have <ApplicationForm #submit="updateApplication"> and on the create page you have <ApplicationForm #submit="createNewApplication" />:
// ApplicationForm.vue
<template>
<section class="columns">
<div class="column">
<div v-if="error" class="notification is-danger">{{ error }}</div>
<div class="field">
<label class="label">Name</label>
<input v-model="application.name" class="input" type="text" />
</div>
<div class="field">
<label class="label">Location</label>
<input v-model="application.location" class="input" type="text" />
</div>
<div class="control">
<button #click="$emit('submit')" class="button">{{ buttonText }}</button>
</div>
</div>
</section>
</template>
As for the text, you can pass that as a prop (e.g. buttonText) to the ApplicationForm component. If some sections of the form are more substantially different than just different text between the "Update" and "Create" form, that's when you'd use slots.
I wouldn't recommend making the <ApplicationForm /> component responsible for reading the route parameters; that should generally be done only by the Vue component responsible for rendering the page (and then it should pass that data through props so that the component is as re-usable as possible)
So your parent component could look something like this:
<ApplicationForm v-if="application" #submit="updateApplication" />
<ApplicationForm v-else #submit="createNewApplication" />
How are you?
My situation is the next. I am passing an array with information to a Component to render with a map().
The name and title are perfect for me.
But I have trouble passing the SRC of the image. I've already tried all the ways I could think of, but nothing.
In the array I leave different paths that I have tried and nothing.
Thaanks!
MY COMPONENT:
import React from 'react';
import { TeampeopleData } from '../Components/TeampeopleData';
function Aboutus() {
return (
<div className='about'>
<div className="about-text">
<h1 className='about-text-title'>ABOUT US</h1>
<h2 className="about-text-big">We work until we see the invisible</h2>
<h2 className="about-text-small">Because it is our passion to serve our clients to the utmost satisfaction, we go against all odds to meet your expectations. We can’t promise you the world but here’s one thing we can assure you: We like to be as clear as we possibly can. We’ll hound you – one, two, three meetings – as many as it takes to get it right! We’re not perfectionists. We just want to make YOU the happiest.
</h2>
</div>
<div className="about-team">
<h2 className="about-team-title">Our Team</h2>
<div className="about-team-people">
{TeampeopleData.map((item)=>{
return(
<div className='people'>
<div className="people-img">
<img src={item.photo} alt="team-people" className="team-people-photo"/>
</div>
<h2 className="people-name">{item.name}</h2>
<p className="people-title">{item.title}</p>
</div>
)
})}
</div>
</div>
</div>
)
}
export default Aboutus
MY ARRAY
import people1 from '../img/people1.jpg';
export const TeampeopleData =[
{
photo: {people1},
name: 'Blas Garcia',
title: 'Founder'
},
{
photo: '/src/img/people1.jpg',
name: 'Patrick O’donnel',
title: 'Marketing'
},
{
photo: '../img/people1.jpg',
name: 'Olivia Wralikszowa',
title: 'Art Director'
}
]
enter image description here
Just remove the bracket around the people1 and it'll work fine.
{
photo: people1,
name: 'Blas Garcia',
title: 'Founder'
},
I'm playing with Contentful! and I'm having trouble with Rich text content field.
I'm using '#contentful/rich-text-types' and #contentful/rich-text-html-renderer modules to customize the way this block is rendered and to display some assets and reference linked in Rich text content.
After calling getEntries in nuxt asyncData function, I've a description data available in my page component.
I'm using documentToHtmlString function with options.
Everything is working fine, but I would like to use a component I have already written (Post.vue), instead of returning the template in ES6 Template Strings.
I know that is possible, but I'm quite new to JS world.
I've tried to require components/post/Post.vue, but I don't know how to use it.
import { BLOCKS } from '#contentful/rich-text-types';
import { documentToHtmlString } from "#contentful/rich-text-html-renderer"
Vue component template where rich text field is rendered
<section class="container">
<div class="columns">
<div class="column">
<div v-html="formatContent(description)" />
</div>
</div>
</section>
I simply call formatContent method to call documentToHtmlString as follow (it works):
methods: {
formatContent(content) {
return documentToHtmlString(content, options)
}
}
And customize documentToHtmlString with options as described in doc:
const embeddedEntryRender = (node) => {
const { data: { target: entry} } = node
const fields = entry.fields
const sys = entry.sys
// LOOK HERE
// const postComponent = require('~/components/post/Post')
return `
<div class="column is-4">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-content">
<h3 class="title is-4">${fields.title}</h3>
<div class="subtitle is-6">${fields.description}</div>
</div>
</div>
<div class="content">
</div>
</div>
</div>
</div> `
}
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ENTRY]: (node) => embeddedEntryRender(node),
// [BLOCKS.EMBEDDED_ASSET]: (node) => `<custom-component>${customComponentRenderer(node)}</custom-component>`
}
}
No errors detected
--
Thanks a lot
yep you can have a custom vue component in there with a different npm library, I had this same problem.
npm i contentful-rich-text-vue-renderer
in template:
<rich-text-renderer :document="document" :nodeRenderers="renderNode" />
where 'document' is the data sent form contentful, looks like your calling it description. RenderNode is a method described below.
in script:
data () {
return {
renderNode: [INLINES.ASSET_HYPERLINK]: (node, key, h) => {
return h('my-vue-component', { key: hey, props: { myProp: 'blah blah' }},'what I want inside the <my-vue-component> tag'`)
}
}
this might be kind of confusing. So First imprt the richTextRenderer component from that npm library and make sure to declare it in the components section of your vue component. (or gloablly)
Next pass into its 'document' prop the contentful rich text field
if you want custom rendering, pass into the nodeRenders prop a function (I had to declare it in the data section)
My example takes any asset hyperlink type and replaces it with a component of what I want inside the tag
I only got this to work if I globally declared the my-vue-component in the main.js file.
import MyVueComponent from 'wherever/it/is';
Vue.component('my-vue-component', MyVueComponent);
there are more configurations for this, just read the npm libs documentation (though its not great docs, it took my a long time to figure out how to pass props down, I had to read their github code to figure that out lol)
I currently have jsx in my React component that will return certain features based on if there is a certain value in an object. For example in my React component:
if(customer.product.id === "543"){
return (
<div className="text-align-left">
<div className="row">
<div className="col-xs-12 font-weight-bold">
<p>Some text here</p>
</div>
</div>
</div>
);
} else {
return (
<div className="text-align-left">
<div className="row">
<p>Different text here</p>
</div>
</div>
</div>
);
'customer' is coming from my Redux state and dispatching appropriately, and is as follows:
{
"product": {
"id": "543",
},
"info": [
{
"name": {
"title": "Mr",
"first_name": "John",
"last_name": "Smith",
},
"dob": "1956-06-06",
"phone": [
{
"number": "5555555555",
"ext": "234",
}
]
}
]
}
I know it is dispatching correctly because if I try to access customer.info[0].name.last_name in the jsx, it returns the correct value.
Yet, when I try to get the value for customer.product.id I am met with the error
TypeError: Cannot read property 'id' of undefined
From reading other questions here, it seems the issue may be that the object I'm getting from the Redux state is asynchronous, but if it is able to go in and access customer.info[0].name.last_name, why can't it do the same with customer.product.id? I tried using a try catch block, but that didn't work out either (also, having an if else statement in a try catch isn't exactly dry code). Much of the other questions I found on here with this issue when using React were dealing with a component with a local state an.
I apologize if this question is a duplicate of another question, but I can't seem to find a question that fits this situation. Thank you!
Trying to figure out how can i get the link to actually render as a link. Right now after i read this line of text from my Json file, react renders the hyperlink as literal text, it doesn't render it as a link.
someData.json
[{
"about": "John has a blog you can read here."
}]
someComponent.js
const Person = Component({
store: Store('/companies'),
render(){
var company = this.store.value()[this.props.companyId];
return (
<div id='ft-interviewee className="all-100"'>
<p className="section-heading bold padding-top-20 font-22">Person</p>
<div className="column-group horizontal-gutters">
<div className="all-10">
<div>{company.people.person.about}</div>
</div>
</div>
)
}
});
You can use dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={ { __html: company.people.person.about } }></div>
Example
Another option and I think cheap to use is NPM React Plugin
https://www.npmjs.com/package/react-html-parser
I have used this and feeling good.