I have a react component that has an event that gets called when a user clicks an item in a different component. The function that gets called is intended to display a Div on the web page. I can return an html string, to display the div, but I would rather but that HTML into its own component. My function gets called, but if I try to return a components (instead of raw html) it shows the div but not text. It just says [object Object]. The function that gets called looks like this:
function MyPage() {
const nodeHoverTooltip = (node) => {
return `<div>${node.name}</div>`;
//displays fine
};
const nodeClickDetails = (node) => {
if(node.nodeType === "somenode"){
return (<MyNodeDetails></MyNodeDetails>);
//this just displays [object, Object]
}else if (node.nodeType === "anotherNodeType"){
return `<div>Another Node Type Details</div>`;
//displays fine
}else{
return `<div>More Details</div>`;
//displays fine
}
};
return (
<div className="MyDiv">
<header className="header">Some Examples</header>
<section className="Main">
<ForceGraph
linksData={data.links}
nodesData={data.nodes}
nodeHoverTooltip={nodeHoverTooltip}
nodeClickDetails = {nodeClickDetails}
/>
</section>
</div>
);
}
export default MyPage;
MyNodeDetails.jsx Component:
function MyNodeDetails ({data}) {
return (
<div>
MyNodeDetails
</div>
)
}
export default MyNodeDetails
Is there a way to but the HTML into a component and not do raw HTML?
The function that's handling displaying the the HTML code is setting the innerHTML of an element to the html string being returned. When you convert a component, which is an object, to a string it gets converted to '[object Object]'. You need to use JSX instead of strings.
Here's an example to show the difference between both approaches. I doubt your rendering function is using dangerouslySetInnerHTML, but the idea of converting to string is what's important.
const { createRoot } = ReactDOM
const { useState } = React
const Test = () => <div>This is a test component</div>
const testHTML = '<div>This is a test html string</div>'
function App(){
const [component, setComponent] = useState()
const [html, setHTML] = useState();
function useJSX(){
setComponent(<Test />)
setHTML(testHTML)
}
function useHTML(){
setComponent(<div dangerouslySetInnerHTML={{__html: <Test/>}}></div>)
setHTML(<div dangerouslySetInnerHTML={{__html: testHTML}}></div>)
}
return (<div>
<button onClick={useJSX}>Use JSX</button>
<button onClick={useHTML}>Use HTML</button>
{component}
{html}
</div>)
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App tab="home" />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
I'm trying to inject multiple react components to separate parts of the page at different times without overriding or manipulating the page while sharing a React Context between all these components.
I've managed to accomplish this by turning the current HTML page's root element's outerHTML into react components that can have children inserted into them.
But I've run into an issue where injecting some components and then injecting new components at a later time unsubscribes the previously injected components to any context changes from the context provider which prevents them from interacting with the context.
I'm assuming this is because each element is already injected into the DOM as an HTML element which makes anything related to the original react component inaccessible since the functionality of the react component isn't contained in the HTML element, but there might be a way to overcome this hurdle by manipulating refs in some sort of way to reaccess and resubscribe these components to the context.
Overall, this method is not reliable and trustworthy enough as there might be better and more efficient ways of achieving this.
Here are the project files and hierarchy:
index.js (Script that adds the components to the HTML page)
// index.js
import React from 'react';
import { render, createPortal } from 'react-dom';
import { CounterContextProvider } from './CounterContext';
import Clicker from './Clicker';
let root = document.querySelector('#app-container').outerHTML;
// Convert HTMl string into a manipulatable element
const solidifyHTML = (html) => {
let div = document.createElement('div');
div.innerHTML = html;
return div;
};
// Convert children and sub-children of an HTML element into react components,
// while also injecting requested children from the "commands" parameter
const getReactifiedChildren = (element, hierarchy, commands = null) => {
let children = Array.from(element.children).map((child, index) => {
let TagName = child.tagName.toLowerCase();
let injected = null;
if (commands != null) {
commands.map((command) => {
if (child.parentElement.querySelector(command.reference) == child)
injected = command.injected;
});
}
let attributes = {};
if (child.attributes.length != 0) {
Array.from(child.attributes).map((attribute) => {
let value = attribute.nodeValue;
let name = attribute.nodeName;
if (name == 'style') {
let style = {};
value.split(';').map((styling) => {
let [property, assigned] = styling
.trim()
.split(':')
.map((e) => e.trim());
property = property.split('-');
property = [property[0]]
.concat(
property.slice(1).map((e) => e[0].toUpperCase() + e.slice(1))
)
.join('');
style[property] = assigned;
});
value = style;
}
attributes[name] = value;
});
}
return (
<TagName key={`reactified-h${hierarchy}-i${index}`} {...attributes}>
{child.children.length == 0
? child.innerHTML
: getReactifiedChildren(child, hierarchy + 1, commands)}
{injected && injected}
</TagName>
);
});
return children;
};
// Inject 3 clickers
render(
<CounterContextProvider>
// Get react children from root's outerHTML solidified as an HTML element
{getReactifiedChildren(solidifyHTML(root), 0, [
// Each item here has a .reference to the element
// which will have the .injected appended to it as a new child
{
reference: '#welcome',
injected: <Clicker info={'Injected into #welcome'} />,
},
{
reference: '#greetings',
injected: <Clicker info={'Injected into #greetings'} />,
},
{
reference: '.bar',
injected: <Clicker info={'Injected into .bar'} />,
},
])}
</CounterContextProvider>,
document.querySelector('#app-container')
);
// Re-read the HTML of the page, and inject one clicker
root = document.querySelector('#app-container').outerHTML;
render(
<CounterContextProvider>
{getReactifiedChildren(solidifyHTML(root), 0, [
{
reference: '.foo',
injected: <Clicker info={'Injected into .foo'} />,
},
])}
</CounterContextProvider>,
document.querySelector('#app-container')
);
index.html (HTML page)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<div
id="app-container"
style="width: 512px; height: 512px; background-color: silver"
>
<div id="welcome">
<div>Hey there</div>
</div>
<div>Hey there</div>
<div class="bar">
<div>Hey</div>
<div class="foo">
<div>Bye</div>
<div>Good morning</div>
</div>
</div>
<div>Hey there</div>
<div id="greetings">
<div>Hello</div>
<div>World</div>
</div>
<div>Hey Hello</div>
</div>
</body>
</html>
CounterContext.jsx (The context component)
// CounterContext.jsx
import React, { useState, createContext, useContext } from 'react';
const CounterContext = createContext({
counter: 0,
setCounter: (main) => {},
});
export function CounterContextProvider(props) {
const [counter, setCounter] = useState(0);
return (
<CounterContext.Provider value={{ counter, setCounter }}>
{props.children}
</CounterContext.Provider>
);
}
export const useCounterContext = () => useContext(CounterContext);
Clicker.jsx (The child component)
// Clicker.jsx
import React from 'react';
import { useCounterContext } from './CounterContext';
export default function Clicker(props) {
const { counter, setCounter } = useCounterContext();
return (
<div>
<button onClick={() => setCounter(counter + 1)}>
Increase Counter, {props.info}
</button>
<div>Counter: {counter}</div>
</div>
);
}
Project Hierarchy
I am totally blank on how to use a function that is inside a component and needs to be used in another component.
Here is a simple program:
Test.js
export default function Test(){
const testFunc = () => {
console.log("it is working")
}
return(
<div>
Hi
</div>
)
}
Test2.js
export default function Test2(){
return(
<button onClick={}> // Here I want to use testFunc() from Test file
Click
</button>
)
}
Could someone please explain how can it be achieved to access the function in Test2 file.
Thanks in advance!!
You will want to pass the function as a prop to the child component. You can't or I should say shouldn't pass a prop to a parent, you can do this but is not a react way and never recommended. What you would do in this case is but the logic in the parent because both siblings are needing access to it.
const App = () => {
const clickHandler = () => {
alert("Click event")
}
return (
<div className="App">
<ChildOne clickHandler={clickHandler}/>
<ChildTwo clickHandler={clickHandler}/>
</div>
)
}
}
You can either pass it down from a parent component, shown below, or you can use a custom hook
Parent Component:
import Child from './Child.js'
export default function Parent() {
const functionToBePassed = () => { ... }
return (
<Child func={functionToBePassed}>
)
}
Or you can do it via a custom hook
Two files, first one is the hook
export default function useFunc() {
const functionToBeShared = () => {...}
return { functionToBeShared }
}
//this is any component that wants to use the hook
import useFunc from ./useFunc;
export default function ComponentThatUsesHook() {
const {functionToBeShared} = useFunc();
}
Welcome to the React community.
To use a function that is inside a component and needs to be used in another component.
You need a common parent, that handles the function.
Let's say you have the following structure.
export const ParentComponent = () => {
return <>
<Test1 />
<Test2 />
<>
}
If you want some function in Test1 to affect Test2, then you do what in react is called lifting state up https://reactjs.org/docs/lifting-state-up.html
ParentComponent
export const ParentComponent = () => {
const [value, setValue] = useState('')
return <>
<Test1 setValue={setValue} />
<Test2 value={value} />
<>
}
Test1
export const Test1 = (props) => {
return <>
<input onChange={(e) => props.setValue(e.target.vale} />
<>
}
Test2
export const Test2 = (props) => {
return <p>{props.value}</p>
}
When a component renders another component, it is called the parent of the rendered child. Imagine React as a tree data structure where the App.tsx/jsx will be the tree's root.
Inspecting the code above, we can see that we have a function held in the parent. This is the function you would probably consider putting in Test1. However, if you need to use it in another component, that is not a child of the current element. You will need to find the nearest common parent and pass the functionality down like in the example above.
I hope it makes sense. If not, I recommend glancing at the Main Concepts part of the official React documentation. https://reactjs.org/docs/getting-started.html
As Konrad said in the comments, this can't be possible since these 2 components lack no relationship (Neither Components are rendering or calling each other within)
Something you could do is Render the Test2.js component within Test.js and add a callback like so:
Test.js
import Test2 from '...';
export default function Test(){
const testFunc = () => {
console.log("it is working")
}
return(
<div>
Hi
<Test2 callbackProp={testFunc} />
</div>
)
}
Test2.js
export default function Test2({callbackProp}){
return(
<button onClick={() => {callbackProp();} }> // Here I want to use testFunc() from Test file
Click
</button>
)
}
Now whenever Test.js is rendered, it will also render Test2 (Since Test is rendering a Test2 Component) but whenever you click the button within Test2, it will execute the callback which is a function passed from Test
Nonetheless though, it's impossible to call any functions from another Component without passing down a prop like this (for future reference)
Solution
Usually, context is used to share the same state between many components that aren't in parent-children relations.
codesandbox
Creating context
First, create a context:
const MyContext = createContext();
And context provider:
const MyContextProvider = ({ children }) => {
const [myState, setMyState] = useState(0);
return (
<MyContext.Provider value={{ myState, setMyState }}>
{children}
</MyContext.Provider>
);
};
And context hook (for convenience):
const useMyContext = () => useContext(MyContext);
Using context
Remember to use the provider in the common ancestor of the components:
function App() {
return (
<MyContextProvider>
<Component1 />
<Component2 />
</MyContextProvider>
);
}
Create your components:
function Component1() {
// as you can see, you can access the function here
const { setMyState } = useMyContext();
return (
<button onClick={() => setMyState((state) => state + 1)}>click me</button>
);
}
function Component2() {
// and the value here
const { myState } = useMyContext();
return myState;
}
New to RJS, Trying to access my variable "txt" outside the Data.map callback where it was originally declared, how would I be able to do this and have full access to the variable?
CodeSandbox
import Data from "./names.json";
export default function App() {
//want to access txt in here <==============
// console.log(txt) and stuff after accessing it here
return (
<div className="App">
{Data.map((post) => {
var txt = post.Name;
return <h1>{post.Name}</h1>;
})}
</div>
);
}
Thanks
Many ways, but the useState hook is pretty solid especially if you want to take advantage of React's speed.
import Data from "./names.json";
export default function App() {
const [ txt, setTxt ] = useState(""); // sets it to an empty string to begin with
useEffect(() => {
console.log(txt); // every time the 'txt' variable changes, log it
}, [ txt]); // << React calls this a dependency and will only run this function when this value changes.
console.log(txt); // also accessible here
return (
<div className="App">
{Data.map((post) => {
setTxt(post.Name); // This updates the 'txt' variable from earlier ^^
return <h1>{post.Name}</h1>;
})}
</div>
);
}
If all that is too long-winded, just keep your txt variable outside of the function component, and React won't reset it every loop. You'll still be able to access its value anywhere in the file. Example:
import Data from "./names.json";
let txt = "";
export default function App() {
return (
<div className="App">
{Data.map((post) => {
txt = post.Name;
return <h1>{post.Name}</h1>;
})}
</div>
);
}
afaik you can't because text is scoped to the map function and you can't access it outside of it. You can try putting it in a state or make a function and make it an argument of that function from inside the map function.
import Data from "./names.json";
import {useState} from 'react'
export default function App() {
//want to access txt in here <==============
// console.log(txt) and stuff after accessing it here
const [text,setText] = useState()
function getText(text) {
console.log(text) // this function gets called in every instance of the map loop
//you can run your logic here to find specific information and then set it to the state like in this example
if (text === "myText") {
setText(text)
}
}
return (
<div className="App">
{Data.map((post) => {
var txt = post.Name;
getText(txt) // will run and recieve the var txt every instance
return <h1>{post.Name}</h1>;
})}
</div>
);
}
I am trying to understand how Props work in React. The following code is giving an error - Error: Objects are not valid as a React child (found: object with keys {args})
const App = () => {
const course = 'Half Stack application development'
return (
<div>
<Header args={course}/> // Will an object be passed or just the string?
</div>
)
}
const Header = (agrs)=>{
console.log(agrs)
return (
<div>
<h1>{agrs}</h1>
</div>
)
}
When props are being passed, is an Object is passed encapsulating the fields or just the field values are passed?
why does the above code doesn't work?
Thanks
First off, you have a spelling mistake. Replace agrs with args. Secondly, props are passed as an object (dictionary), so you have one of two options:
const Header = (props) =>{
console.log(props.args)
return (
<div>
<h1>{props.args}</h1>
</div>
)
}
or object destructuring:
const Header = ({args}) =>{
console.log(args)
return (
<div>
<h1>{args}</h1>
</div>
)
}
Also, make sure to add props validation (your linter should warn you about this):
import PropTypes from "prop-types";
Header.propTypes = {
args: PropTypes.string.isRequired
};
Answer 1: Value is passed as a key with the same name as field you assigned it to in props object.
Answer 2:
const Header = (props)=>{
console.log(props.agrs)
return (
<div>
<h1>{props.agrs}</h1>
</div>
)
}
The code above will run fine.
Alternative to answer 2:
const Header = ({agrs})=>{
console.log(agrs)
return (
<div>
<h1>{agrs}</h1>
</div>
)
}
This will also run fine.
It uses object destructuring so you don't have to use props.agrs but just args works fine.
const App = () => {
const course = 'Half Stack application development'
return (
<div>
<Header args={course}/> // Will an object be passed or just the string?
</div>
)
}
const Header = ({agrs})=>{
console.log(agrs)
return (
<div>
<h1>{agrs}</h1>
</div>
)
}
Use object Destructuring like above or
const Header = (props)=>{
console.log(props.agrs)
return (
<div>
<h1>{props.agrs}</h1>
</div>
)
}
Find more here Components and Props.
Find more about Destructuring
import React from 'react';
class App extends React.Component {
render() {
return (
<div>
<h1>{this.props.headerProp}</h1>
<h2>{this.props.contentProp}</h2>
</div>
);
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App headerProp = "Header from props..." contentProp = "Content
from props..."/>, document.getElementById('app'));
export default App;
enter image description here
I'm trying to test a simple component that take some props (it have no state, or redux connection) with Enzyme, it works for the plain elements like <div /> and so on, but when i try to test if the element rendered by the child component exists, it fails.
I'm trying to use mount but it spit me a lot of errors, i'm new in this so, here is my code:
import React, { Component } from 'react';
import WordCloud from 'react-d3-cloud';
class PredictWordCloud extends Component {
render() {
const fontSizeMapper = word => Math.log2(word.value) * 3.3;
const { size, data, show } = this.props;
if (!show)
return <h3 className='text-muted text-center'>No data</h3>
return (
<section id='predict-word-cloud'>
<div className='text-center'>
<WordCloud
data={data}
fontSizeMapper={fontSizeMapper}
width={size}
height={300} />
</div>
</section>
)
}
}
export default PredictWordCloud;
It's just a wrapper for <WordCloud />, and it just recieves 3 props directly from his parent: <PredictWordCloud data={wordcloud} size={cloudSize} show={wordcloud ? true : false} />, anything else.
The tests is very very simple for now:
import React from 'react';
import { shallow } from 'enzyme';
import PredictWordCloud from '../../components/PredictWordCloud.component';
import cloudData from '../../helpers/cloudData.json';
describe('<PredictWordCloud />', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<PredictWordCloud data={cloudData} size={600} show={true} />)
});
it('Render without problems', () => {
const selector = wrapper.find('#predict-word-cloud');
expect(selector.exists()).toBeTruthy();
});
});
For now it pass but if we change the selector to: const selector = wrapper.find('#predict-word-cloud svg'); where the svg tag is the return of <Wordcloud /> component, the tests fails because the assertion returns false.
I tried to use mount instead of shallow, exactly the same test, but i get a big error fomr react-d3-cloud:
PredictWordCloud Render without problems TypeError: Cannot read property 'getImageData' of null.
This is specially weird because it just happens in the test environment, the UI and all behaviors works perfectly in the browser.
You can find your component directly by Component name.
Then you can use find inside your sub-component as well.
e.g
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.find('svg')).to.have.length(1);
});
or
You can compare generated html structure as well via
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.html()).to.equal('<svg> Just an example </svg>');
});