html strings array convert to JSX - html

`import React from 'react'
export default function Quiz(props){
// generate random index without duplicates
function generateRandomIndex(){
const randomNumArr=[]
for (var a = [0, 1, 2, 3], i = a.length; i--; ) {
var random = a.splice(Math.floor(Math.random() * (i + 1)), 1)[0];
randomNumArr.push(random)
}
return randomNumArr
}
let randomNumbers = generateRandomIndex()
let spreadOptions = ()=>{
let optionsHtmlArray = []
for(let i=0; i<props.answers.length; i++){
optionsHtmlArray.push(`<span className='answers' key=${i} style={${{backgroundColor: props.correct===props.answers[i] ? "green" : "red"}}}>
{ ${props.answers[i]} } </span>`)
}
return optionsHtmlArray
}
return (
<div className='Quiz'>
<h3 className='question'>{props.question}</h3>
<div className='answers_div'>
{ spreadOptions()[randomNumbers[0]] }
{ spreadOptions()[randomNumbers[1]] }
{ spreadOptions()[randomNumbers[2]] }
{ spreadOptions()[randomNumbers[3]] }
</div>
<hr className='hr'/>
</div>)
}
'
'//this is from App.js
// fetch to API when first render to save data to the state,
// and fetch depending on the sate of showOverlay
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&category=9&difficulty=easy&type=multiple")
.then(res => res.json())
.then(data => {
setQuestions(data.results)
//after set questions state that comes from fetch request
//and set the custom questions with some properties I need
setCustomQuestions(prevQuestions=>{
let newArr=[]
for(let i=0; i<data.results.length; i++){
newArr.push({question: data.results[i].question,
questionId: nanoId(),
answers: [data.results[i].correct_answer].concat(data.results[i].incorrect_answers),
correct: data.results[i].correct_answer})
}
return newArr
})
})
}, [])
// Quiz component properties
const customQuestionsArr = customQuestions.map(question => {
return < Quiz
key={question.questionId}
question={question.question}
answers={question.answers}
correct={question.correct}
/>
})'
Hi all, I am trying to render all options of the answers in Quiz component, however,
spreadOptions() returns an array of html strings for the answers
I gotta parse to JSX to make it work.
I tried to install react-html-parser, didn't work it only gave me a bunch of error every time when I try to install dependencies through npm
I tried dangerouslySetInnerHTML, but also didn't work

Would you be able to provide the props that you are trying to pass to Quiz component?
Below is a snippet of code with modified spreadOptions and jsx. I wasn't able to test this code tho but will update it if you can provide the sample props.
let spreadOptions = props.answers.map((a, i) => (
<span
key={i}
className='answers'
style={{
backgroundColor: props.correct === a ? 'green' : 'red',
}}
>
{a}
</span>
));
return (
<div className="Quiz">
<h3 className="question">{props.question}</h3>
<div className="answers_div">
{spreadOptions}
</div>
<hr className="hr" />
</div>
);

Related

React Beautiful DnD, multiple columns inside single droppable

I am trying to have a grid column layout, (2 columns) inside a single droppable container. The project is for an online menu where you can create a menu item, which goes into a droppable container, then you can drag that onto the menu that will be displayed to the user. So there is currently two columns. However the style of the menu demands two columns. Currently I am assigning different classNames to the mapped columns so I can make one of them grid but its pretty messy. Maybe there is a way I can hardcode the droppable instead of map them and run the map on the lists themselves inside each of the hardcoded droppables? Sorry if this is confusing, it sure is for me.
'results' is API data that is initially mapped into savedItems array where newly created menu items will go. Later on menuItems array will pull from the database as well. Right now just trying to have better styling control over the different droppables.
you can see where im assigning different classNames to the droppable during the mapping and its really not a reliable option.
//drag and drop states
const [state, setState] = useState({
menuItems: {
title: "menuItems",
items: []
},
savedItems: {
title: "savedItems",
items: results
}
})
useEffect(() => {
setState({ ...state, savedItems: { ...state.savedItems, items: results } })
}, [results])
// console.log("state", state)
console.log("dummy data", dummyArry)
// updating title graphql mutation
const [elementId, setElementId] = useState(" ");
const updateTitle = async () => {
//api data
const data = await fetch(`http://localhost:8081/graphql`, {
method: 'POST',
body: JSON.stringify({
query: `
mutation {
updateMenu(menuInput: {_id: ${JSON.stringify(elementId)},title: ${JSON.stringify(inputValue)}}){
title
}
}
`
}),
headers: {
'Content-Type': 'application/json'
}
})
//convert api data to json
const json = await data.json();
}
//drag end function
const handleDragEnd = (data) => {
console.log("from", data.source)
console.log("to", data.destination)
if (!data.destination) {
// console.log("not dropped in droppable")
return
}
if (data.destination.index === data.source.index && data.destination.droppableId === data.source.droppableId) {
// console.log("dropped in same place")
return
}
//create copy of item before removing from state
const itemCopy = { ...state[data.source.droppableId].items[data.source.index] }
setState(prev => {
prev = { ...prev }
//remove from previous items array
prev[data.source.droppableId].items.splice(data.source.index, 1)
//adding new item to array
prev[data.destination.droppableId].items.splice(data.destination.index, 0, itemCopy)
return prev
})
}
const columnClass = [
"menuItems-column",
"savedItems-column"
]
let num = 0
return (
<>
<div className='app'>
{results && <DragDropContext onDragEnd={handleDragEnd}>
{_.map(state, (data, key) => {
return (
<div key={key} className='column'>
<h3>{data.title}</h3>
<Droppable droppableId={key}>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={columnClass[num]}
// className="droppable-col"
><span className='class-switch'>{num++}</span>
{data.items.map((el, index) => {
return (
<Draggable key={el._id} index={index} draggableId={el._id}>
{(provided) => {
return (
<div className='element-container'
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div contentEditable="true">
{el.title}
</div>
</div>
)
}}
</Draggable>
)
})}
{provided.placeholder}
</div>
)
}}
</Droppable>
</div>
)
})}
</DragDropContext>}
</div>
</>
)
}

React loop through json object and display data

I have a demo here
I have a simple json file that I'm importing and I would like to loop through and output the json data in a div
I'll probable want to pick out parts of the json but for now I just need to be able to output the json
Do I need to create an array from the json data and then map over that.
const showProductData = Object.keys(ProductData).map(function(key) {
return <div>{ProductData[key]}</div>;
});
const App = () => {
return (
<div>
<h2>JSON</h2>
{showProductData}
</div>
);
};
If you read the error message, Objects are not valid as a React Child. To modify your current code to just show the json, you will need to convert the object into a string.
const showProductData = Object.keys(ProductData).map(function(key) {
return <div>{JSON.stringify(ProductData[key])}</div>;
});
To be more concise with what we're accessing, we can instead use Object.values() instead:
const showProductData = Object.values(ProductData).map(function(value) {
return <div>{JSON.stringify(value)}</div>;
});
To further access specific points of the data, you can use dot notation to access primitive values:
const showProductData = Object.values(ProductData).map(function(value) {
return <div>Weight: {value.ProductWeight}</div>;
});
well, when i show ur a question, immediately i thought 'recursive solution' :)
so basically this is the code, I tried to explain it, feel free to dig into it
function getAllProps(obj) {
let value = obj;
// in case it is an object / array, which true, at any json file, at least at the beginning
if (typeof obj === "object") {
value = Object.keys(obj).map(key => {
// and then check again if the value of the 'key' is an object
// because we cant just out put object in react
if (typeof obj[key] === "object") {
// then out put the key name (property, 'ProductOne' for example)
// and call the function again since we know it is an object.
// basiclly the function will call it self again and again
// until the value will be different than 'object'
return (
<div key={key}>
<div style={{ marginLeft: 20 }}>
<strong> {key} </strong>
{getAllProps(obj[key])}
</div>
</div>
);
}
return (
<div key={key} style={{ marginLeft: 20 }}>
{key}: {obj[key]}
</div>
);
});
}
return value;
}
const products = getAllProps(ProductData);
const App = () => {
return (
<div>
<h2>JSON</h2>
{products}
</div>
);
};
actually, just check that link
read the comments, try to understand my 'recursive solution'

How to access ag-Grid API in React function component (useState hook)?

What is the best way of accessing ag-Grid API inside of React function component?
I have to use some of the methods from API (getSelectedNodes, setColumnDefs etc.) so I save a reference to the API (using useState hook) in onGridReady event handler:
onGridReady={params => {
setGridApi(params.api);
}}
and then I can call the API like this: gridApi.getSelectedNodes()
I haven't noticed any problems with this approach, but I'm wondering if there's more idiomatic way?
Stack:
ag-grid-community & ag-grid-react 22.1.1
react 16.12.0
We find the most idiomatic way to use a ref. As the api is not a state of our component. It is actually possible to simply do:
<AgGridReact ref={grid}/>
and then use it with
grid.current.api
Here an example:
import React, { useRef } from 'react'
import { AgGridReact } from 'ag-grid-react'
import { AgGridReact as AgGridReactType } from 'ag-grid-react/lib/agGridReact'
const ShopList = () => {
const grid = useRef<AgGridReactType>(null)
...
return (
<AgGridReact ref={grid} columnDefs={columnDefs} rowData={shops} />
)
}
The good thing here is, that you will have access to the gridApi but als to to the columnApi. Simply like this:
// rendering menu to show/hide columns:
{columnDefs.map(columnDef =>
<>
<input
type='checkbox'
checked={
grid.current
? grid.current.columnApi.getColumn(columnDef.field).isVisible()
: !(columnDef as { hide: boolean }).hide
}
onChange={() => {
if (grid.current?.api) {
const col = grid.current.columnApi.getColumn(columnDef.field)
grid.current.columnApi.setColumnVisible(columnDef.field, !col.isVisible())
grid.current.api.sizeColumnsToFit()
setForceUpdate(x => ++x)
}
}}
/>
<span>{columnDef.headerName}</span>
</>
)}
Well I am doing it in my project. You can use useRef hook to store gridApi.
const gridApi = useRef();
const onGridReady = params => {
gridApi.current = params.api; // <== this is how you save it
const datasource = getServerDataSource(
gridApi.current,
{
size: AppConstants.PAGE_SIZE,
url: baseUrl,
defaultFilter: props.defaultFilter
}
);
gridApi.current.setServerSideDatasource(datasource); // <== this is how you use it
};
I'm running into the same issue but here is a workaround that at least can get you the selected rows. Essentially what I'm doing is sending the api from the agGrid callbacks to another function. Specifically I use OnSelectionChanged callback to grab the current row node. Example below:
const onSelectionChanged = params => {
setDetails(params.api.getSelectedRows());
};
return (<AgGridReact
columnDefs={agData.columnDefs}
rowSelection={'single'}
enableCellTextSelection={true}
defaultColDef={{
resizable: true,
}}
rowHeight={50}
rowData={agData.rowData}
onCellFocused={function(params) {
if (params.rowIndex != null) {
let nNode = params.api.getDisplayedRowAtIndex(params.rowIndex);
nNode.setSelected(true, true);
}
}}
onSelectionChanged={function(params) {
onSelectionChanged(params);
params.api.sizeColumnsToFit();
}}
onGridReady={function(params) {
let gridApi = params.api;
gridApi.sizeColumnsToFit();
}}
deltaRowDataMode={true}
getRowNodeId={function(data) {
return data.id;
}}
/>);

Highlight part of Text

I was building project similar to Google Search using React.js and GoogleAPI.
Here I have got a problem.
For example, if you search "tea" on Google, you can see "Searches related to tea" at the bottom of the result, just above the pagination.
There, the words expect "tea" is bold.
How can I implement it using React?
I know the word "tea" and full text of related searches, but how can I highlight the part of text?
Many help.
Here is my code.
In the code, item.query is the full text such as "black tea", and searchValue is "tea". I just want to make "black" bold.
import React, { Component } from ‘react’;
import { connect } from ‘react-redux’;
class RelatedSearch extends Component {
render() {
var res = this.props.searchResults.related_searches;
return (
<div>
<br/><br/>
<p>Related Search</p>
{res.map((item, index) =>
<Item item={item} key={index} />
)}
</div>
);
}
}
function Item (item, key) {
return <div>{item.item.query}</div>;
}
const mapStateToProps = state => ({
searchResults: state.usersReducer.searchResults,
searchValue : state.usersReducer.searchValue,
});
export default connect(mapStateToProps)(RelatedSearch);
I have got answers here and already accepted it, but I received suggestion not to use dangerous html. Is there any other solution?
Here is what I came up with (it may not work in all edge cases but I tested it and it works in most situations)
First get a handle on the search term
let searchTerm = "Hello";
Then create a function that loops through the current string
createHighlight(text) {
// split the string at the point(s) where the search term is present.
let split = text.toLowerCase().split(searchTerm.toLowerCase());
// create a placeholder string.
let ttt = "";
// loop through the splited string and put in the search term after each one and wrap it in a span with a class 'highlight' unless it is the last one.
for (let i = 0; i < split.length; i++) {
if (i === split.length - 1) {
ttt += split[i];
} else {
ttt += `${split[i]} <span class="highlight">${searchTerm}</span>`;
}
}
//return the string as HTML.
return ttt;
}
Use it in your HTML (in place of just inserting the string)
<p dangerouslySetInnerHTML={{
__html: this.createHighlight("hello here hello is text hello")}}
/>
And remember to add a class to style the highlighted text (.highlight {bacround-color: yellow})
Here is a link to a codepen https://codesandbox.io/s/flamboyant-frost-hn47k?fontsize=14
Using javascript
var searchTerm = "hello";
function heighlightText() {
var ele= document.getElementById("sample");
var arrText = ele.innerHTML.split(searchTerm);
var rst = "";
for(i=0;i<arrText.length;i++) {
if(i === arrText.length - 1)
{
rst +=arrText[i];
continue;
}
rst +=arrText[i] + "<span class='highlight'>" + searchTerm +"</span>";
}
ele.innerHTML = rst;
}
heighlightText();
.highlight {
background-color : yellow;
}
<div id="sample">hello test search the things hello and then</div>
You can try this, see if it helps:
class App extends React.Component{
highlightText(sentence, wordToHighlight){
let highlightedText = sentence.split(" ")
.map(word => word.toUpperCase() === wordToHighlight.toUpperCase() ? `<b>${word}</b>` : word)
.join(" ");
return {__html: highlightedText};
}
render(){
return(
<div dangerouslySetInnerHTML={this.highlightText("I am a robot charlie.", "robot")} />
)
}
}
ReactDOM.render( <App />, document.getElementById('root') );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

Display different data from json file, depending on link clicked

React.js
On the main page example.js there are 4 links: link1, link2, link3, link4
When the user clicks on one of the links they are sent to a site called template.js.
Each link sends the user to the same site, template.js, however the data is different depending on what link was clicked.
I have tried just to display the entire data from one of my .json files, without any functionality and style -- but I didn't get any response whatsoever...
I have tried:
var data = require(url);
for(var i = 0; i < data.length; i++) {
var obj = data[i];
console.log("Name: " + obj.first_name + ", " + obj.last_name);
}
OR
fetch(url)
.then(response => response.json().then(data => ({status:
response.status, body: data})))
.then(object => console.log(object));
OR
fetch(url)
.then(response = response.json())
Question:
How would I tell the template.js file to display the relevant information.
You can pass your query through the link and then read it directly from the url.
I do it this way:
Your links
// Here we want to send our search terms, this is just an example with 'someId'
<a src="/template?first_name=john"></a>
<a src="/template?first_name=jenny"></a>
<a src="/template?first_name=gabriel"></a>
<a src="/template?first_name=jose"></a>
You can read the search values with window.location.search or window.location.hash depending on your router.
I prefer use the parse function from the query-string module
Your template
import React, { Component } from 'react';
import * as qs from 'query-string';
class Dashboard extends Component {
render() {
const {
location,
} = this.props;
const { search } = location;
const query = qs.parse(search, { ignoreQueryPrefix: true });
const info = YOURJSONDATA.filter(data => (
// Here we compare the field we want with the query search
data.first_name === query.first_name
));
return (
<div>
{
!!(info) && info.map(o => (<div>{o.first_name}</div>))
}
</div>
);
}
}
Here's how I did it....
In Learn.js__
//reading url
componentDidMount() {
const values = queryString.parse(this.props.location.search)
console.log(values.filter)
console.log(values.origin)
}
//redirection
redirect = (url) => {
this.props.history.push(url)
console.log(this.props)
}
<LearnCard onClick={() => this.redirect("/learn/Template/Cooks")} name="Cooks" image={process.env.PUBLIC_URL + '/image/cook.jpg'}/>
<LearnCard onClick={() => this.redirect("/learn/Template/Websites")} name="Websites" image={process.env.PUBLIC_URL + '/image/website.jpg'}/>
<LearnCard onClick={() => this.redirect("/learn/Template/Tv-Series")} name="Tv-Series" image={process.env.PUBLIC_URL + '/image/tv_series.jpg'}/>
<LearnCard onClick={() => this.redirect("/learn/Template/Cookbooks")} name="Cookbooks" image={process.env.PUBLIC_URL + '/image/cookbook.jpg'}/>
In Template.js__
componentDidMount () {
const url_name = this.props.match.params.name
console.log(this.props.match.params.name)
if (url_name === "Cooks") {
this.setState({data: cooks})
console.log(cooks)
}
if (url_name === "Cookbooks") {
this.setState({data: cookbooks})
console.log(cookbooks)
}
if (url_name === "Tv-Series") {
this.setState({data: tv_series})
console.log(tv_series)
}
if (url_name === "Websites") {
this.setState({data: websites})
console.log(websites)
}
}
render () {
return (
<div>
<div className="templateWrapper">
{
this.state.data && this.state.data.map((data, key) => {
return <TemplateCard className="templateCard" name={data.name} description={data.description} image={data.image} cuisine={data.cuisine} author={data.author} channel={data.channel} href={data.web_url} href={data.chef_url}/>
})
}
</div>
</div>
);
}