I have a hook, Prompt. one of the props is an Object containing some field names as keys.
I create a form using these fields and some <input>s. This is what the hook Fields returns.
It is rendered from an array. I log this array at log-A. Here is what I get:
This makes me believe that this is an array of react components.
There is a function that I am trying to make that gets the results of this form.
To do this, I want to go through the children of the <div> with ref={fieldsRef}. Since this just contains a list of react components, I thought I could just get the children of it and use React.Children.toArray().
This does not work.
const Prompt = (props) => {
//...
const Fields = () => {
let fieldKeys = Object.keys(props.fields);
let fields = [];
for ( let fieldKey of fieldKeys ) {
fields.push(
<div key={fieldKey.toString()}>
<div className={"field-name"}>
{props.fields[fieldKey]}
</div>
<input spellCheck="false"/>
</div>
);
}
console.log(fields); //log-A
return (
<div ref={fieldsRef}>
{fields}
</div>
);
}
const getForm = () => {
console.log(fieldsRef); //log-B
let current = fieldsRef.current.children;
let children = React.Children.toArray(current);
return 0;
}
//...
}
at log-B, the output is a div. It's current has children that are in a .
I get an error when trying to access the children of this.
Uncaught Error: Objects are not valid as a React child (found: [object HTMLDivElement]). If you meant to render a collection of children, use an array instead.
What am I doing wrong, to not be able to access these children and convert to an array using React.Children.toArray(<children>)?
It looks like getForm() is considering {fields} itself to be an object, rather than the array you are trying to reference. Try moving your fields variable declaration into the Prompt() scope instead of the nesting it under Fields().
Also, consider using the React Developer Tools Extension for Chrome, it can really help with debugging issues with props and hooks.
Related
For an example if we made a App component and we needed to create an element each time a button was clicked:
function App() {
const handleClick = () => {
// Code here
}
return (
<div id="app">
<button onClick={handleClick}>Click here</button>
</div>
)
}
Is it ok if i used document.createElement("div") and document.getElementById("app").append() in that case?
function App() {
const handleClick = () => {
let div = document.createElement("div")
div.innerHTML = "Hi!"
document.getElementById("app").append(div)
}
return (
<div id="app">
<button onClick={handleClick}>Click here</button>
</div>
)
}
It's fine to use the document object for certain things in React code, but not in this case. That's not how you'd add an element to your #app div. Instead, you'd use state for it. When switching to an MVC/MVVM/whatever tool like React, you need to stop thinking in terms of modifying the DOM and start thinking in terms of component states.
In your case, for instance, you'd either want a boolean state member telling you whether to render that Hi! div, or perhaps an array state member of messages you might display.
Here's an example of the former:
const { useState } = React;
const App = () => {
// The state information
const [showHi, setShowHi] = useState(false);
const handleClick = () => {
// Set the state to true on button click
setShowHi(true);
};
return <div>
{/* Check the state and conditionally render */}
{showHi && <div>Hi!</div>}
<button onClick={handleClick}>Click here</button>
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I suggest working through the tutorial on the React website for a solid introduction to React.
Can you?
You can, but this goes against the idea of React in the first place and is advised against. Updating the DOM this way can cost you performance and even introduce bugs in your code.
The point of React is to handle updating the DOM in a performant way that is cross-browser compatible. In fact, behind the scenes, React is going to create the <div> element and place it in the DOM, but it is going to do so in a less costly and better-managed way by using a virtual representation of the DOM and everything in it. This is not as expensive as directly building, destroying and rebuilding elements on the page, because, for one, not everything on the page needs to be changed each time a user interaction that changes something of the page happens. React will react to parts of the page that have changed and keep parts that have not changed improving the performance on your web app. (See
Reactive Updates), This is one of the reasons the React library was built.
React keeps its own internal registry of elements it renders on the page, in the virtual DOM. It updates and tracks them all through their lifecycle on the page. Because of this, it knows which elements to replace, keep or tear down during user interaction. So creating an element by using the document object directly circumvents the creation (and registration) of a representation of the element in the virtual DOM making React unaware of the element - leaving the lifecycle handling of the element to the browser.
The React way
Because of the above, anything that has to do with the UI; including rendering, updating and destroying is best left to React.
The way to build (thanks to JSX and this is an improvement to #yanir's answer) your element is by simply writing out the element where you need it (or storing it in a variable first and using embedded JSX). The innerHTML attribute can be an embedded expression that is computed in the div element. Don't worry, this operation won't cost as much as using the document object to create elements directly. We'll just need a form of state to track how many times the user has clicked and create a plain JavaScript object then use the map() method to create as many <div>s as needed.
function App() {
const [items, setItems] = useState([]);
const handleClick = () => {
let obj = { value: "Hi!" };
setItems([...items, obj]);
};
return (
<div id="app">
<button onClick={handleClick}>Click here</button>
{items.map((item, index) => {
return <div key={index}>{item.value}</div>;
})}
</div>
)
}
If you need to interact with the DOM directly, through the document object for example, as #Kaushik suggested, you can track the element through the use of hooks like useRef(), to get a reference to the object, useId() for unique stable Ids across server and client, useLayoutEffect() to hook into the browser after DOM updates but before it paints and so on. That being said, there are some APIs and attributes that you may need to access from the document object like events, title, URL, and several other attributes. In as much, as these do not mutate the UI, you can use these when needed, while the UI operations are left to React to handle.
Yes, you can. But the React way to do this is to keep track of this in state. You can keep count of the number of divs you want to render, and update that. State update will trigger a rerender, and your view will be updated.
Below I am using a state variable count and initializing an Array using the Array() constructor of size equal to count.
function App() {
const [count,setCount] = 0;
const handleClick = () => {
setCount(count => count+1);
}
return (
<div id="app">
<button onClick={handleClick}>Click here</button>
{Array(count).map((x) => <div>{/**YOUR DIV CONTENT **/}<div>)}
</div>
)
}
If you need to access the DOM element, the recommended approach is to use the useRef, useImperativeHandle, useLayoutEffect, or useid hooks, as these allow React to be aware of when you are using the DOM elements, and allows for future React updates to not break your existing behavior. In your example though, I would argue that you do not need access to the DOM element, and that you should instead can let React handle the rendering of the element via declarative JSX.
You don't you document.getElementById in reactjs
All point of react is to use jsx (dynamic HTML)
What you can to it's to create an array that you append item to this array each click and use map to render the new item each time :
function App() {
const [items,setItems] = useState([])
const handleClick = () => {
// Code here
setItems(prev => {
let arr = []
//change obj each time to your need.
let obj = {label : "test" , value : "test"}
arr.push(obj)
setItems(arr)
})
}
return (
<div id="app">
<button onClick={handleClick}>Click here</button>
{items.map((item,index) =>{
return <p key={index}>{item.label}</p>
})
</div>
)
}
I'm successfully fetching from an API but having trouble rendering the data I want to the front end in React.
I'm trying to return the entire contents of the 'model' object within Article component which is a set of key/value pairs. The ArticleList component maps the body key representing an array and the type and model are passed as props to the Article component.
The JSON of the mock API being accessed is here for reference of the structure: https://www.mocky.io/v2/5c6574b33300009010b99de4
I can't use map on the inner objects because they are not arrays. The console.log in my code is correctly returning the contents of the model object for each entry in the array within the inspection window. However, I can't get it to display in the browser view.
In the Article component the use of Object.toString(model) is temporary code I added in to allow my browser window to render and it displays the following in the browser view:
Object: function Object() { [native code] }
So, to be clear my app is returning the full model object to the browser and not the contents within as desired.
ArticleList component is:
import Article from './Article';
const ArticleList = ({article}) => {
const articleNodes = article.body.map((section)=>{
return (
<>
<Article type={section.type} model={section.model} />
</>
)
})
return (
<>
{articleNodes}
</>
);
}
export default ArticleList;
Article component which is receiving the props of type and model is:
function Article({type, model}) {
console.log(model);
if (!type) return null;
return(
<>
<h1>type: {type}</h1>
<h1>Object: {Object.toString(model)}</h1>
</>
);
}
export default Article;
Please advise? I believe the fix could be simple within my <h1> tag of Article component but I have tried to no avail.
Edit: I want to be able to semantically tag each key value pair to render the images within the "url" key within img tags etc. Therefore I ideally need to have the ability to return individual JSX elements representing the properties in the object such as I have done with the map in ArticleList.
A component for every type
You'll need to create multiple components for each type of data you're receiving. That means a component for the heading type, for the paragraph type, etc.
const Paragraph = ({ text }) => (
<p>{text}</p>
);
export default Paragraph
Finding the right component for the right type.
Now that you've created multiple components for each type your API returns, you'll need to find a way to select the component that corresponds with the current type value.
An easy way to do this is to create a map object. The keys will represent the possible types and the values a function that return the component.
const typesMap = {
'paragraph': (props) => <Paragraph {...props}/>
}
The example above shows a single option called paragraph. (Other types heading, image, etc. are yours to add.) The value is a function with a single parameter called props. The function returns a Paragraph component and passes all available props to the component with the ... spread syntax.
The spread syntax allows us not to hardcode our props like the example below,
<Paragraph text={text} someprop={somevalue} />
but passes all the properties and values in the props object to the component, saving us effort and time.
Selecting the component
We have an object of keys that represent types and values that represent the components. All you have to do now is to select the component based on the type value. And we can do that like this:
const TypeComponent = typesMap[type];
Remember, the selected value is a function that returns a component, like <Paragraph/>. Which would be the same as something like this:
const ExampleComponent = ({ props }) => (
<Paragraph {...props}/>
);
In React (functional) components are nothing more than actual functions that return values. That's why we can write TypeComponent like a component and call it like this:
<TypeComponent {...props} />
The name TypeComponent can be anything you'd like. I thought that it was appropriate for the context it is in.
Now all you have to do is pass the model object to the dynamically created TypeComponent with the spread syntax, saving you the trouble of writing all the props and values for each type.
import Paragraph from './Paragraph';
const typesMap = {
'paragraph': props => <Paragraph {...props} />,
//'heading': props => ...
//'image': ...
};
function Article({ type, model }) {
if (!type) return null;
const TypeComponent = typesMap[type];
return <TypeComponent {...model} />;
}
export default Article;
I'm having an array data.info that is being updated over time and I'm trying to replace placeholder rendered elements with another. So by default app.js looks like this
return (
<Fragment>
{data.info.map((index) => {
return <Typography key={index} variant="h6" className={classes.title}>Demo</Typography>
})}
</Fragment>
)
Also I have a hook with async function to subscribed to data.info.length.
useEffect(
() => {
if (!initialRender.current) {
if (data.info.length!==0) {
for (let i = data.info.length-iScrollAmount+1 ; i < data.info.length+1; i++) {
firstAsync(i)
}
}
} else {
initialRender.current = false
}
},
[data.info.length]
)
async function firstAsync(id) {
let promise = new Promise(() => {
setTimeout(() => console.log(document.getElementById(id)), 500)
});
}
With document.getElementById() and id I can get to every element that was rendered and change it. And here goes the problems.
I'm using material-ui so I can't get to <Typography/> because it is transformed into <h6/>. Probably that is not a problem since I need to replace contents, so I can find parent element and remove all children. Is that way correct?
After I delete children how do I add content using jsx? What I mean is that in async function I'll get an array that I want to use in new element <NewCard/> to dynamically put into <Fragment/>. Yet I did not find any example how to do that.
It is not a good practice to change DOM Nodes directly in React, and you need to let React do the rendering for you and you just tell react what to do.
in your case you need to define a React State for your data and set your state inside your firstAsync function and then use your state to render whatever html element or React component which you want
React does not encourage the practice of manipulating the HTML DOM nodes directly.
Basically you need to see 2 things.
State which is a special variable whose value is retained on subsequent refresh. Change in reference in this variable will trigger component and its children a refresh/re-render.
Props which is passed to every Component and is read only. Changing in props causes refresh of component by default.
In your example, based on data.info you want to render Typography component.
Solution
First thing is your map function is incorrect. First parameter of map function is item of list and second is index. If you are not sure if info will always be present in data, you may want to have a null check as well.
{(data.info || []).map((info, index) => {
return <Typography key={index} variant="h6" className={classes.title}>{info.text}</Typography>
})}
You should be passing info from map to Typography component. Or use info value in content of Typography as shown above.
Update data.info and Typography will update automatically. For this, please make sure, data.info is a component state and not a plain variable. Something like
const [data, setData] = React.useState({});
And when you have value of data (assuming from API), then
setData(responseApi);
I have an react app, and using redux and props to get array of objects into my component, and i am getting them. But i can't access particular property inside of one of objects that are in that array.
With this:
console.log(this.props.users)
I get listed array with all objects inside it. But when i need to access particular object or property of that object, for example:
console.log(this.props.users[0])
console.log(this.props.users[0].name)
I am getting error:
Cannot read property '0' of undefined
But when I iterate through array with map() method i have access to it, it works. Why can't i access it normally?
You are trying to access properties of this.props.users before it has loaded. Your component renders without waiting for your data to fetch. When you console.log(this.props.users) you say that you get an array, but above that, it probably logs undefined at least once when the component renders before this.props.users has loaded.
You have a couple of options. You can either do this at the very top of your render method to prevent the rest of the code in the method from executing:
if (!this.props.users) return null;
Once the data is fetched and props change, the render method will be called again.
The other option is to declare a default value, of an empty array for users in your reducer.
Might be when you are executing that line this.props.users is undefined. Check the flow where you have added console.log(this.props.users[0])
const App = () => {
const example = () => {
const data =[{id:1 ,name: "Users1", description: "desc1"},
{id:2 ,name: "Users2", description: "desc2"}];
return (
<div>
{data.map(function(cValue, idx){
console.log("currentValue.id:",cValue.id);
console.log("currentValue.name:",cValue.name);
console.log("currentValue.description:",cValue.description);
return (<li key={idx}>name = {cValue.name} description = {cValue.description}</li>)
})}
</div>
);
}
return(
<p style = {{color:'white'}}>
{example()}
</p>
);
}
export default App;
In my React app, I want to render a prop value, but it does not exist until the props have been updated which occurs after the render is complete.
this.props.users is an object, so I use Object.keys() to translate into an array, and then map through the child elements using the keys to target them:
// ...
render() {
return {
<div className='users-list'>
<h4>Users List</h4>
{!isLoaded(users) ? '' :
Object.keys(users).map( (key, i) => <p>{users[key].email}</p> )
}
</div>
}
}
This works, but it took me a lot of hacking and adapting the code to get there. Is there a better/more eloquent way to go about this? Because I imagine this is really common case! Does the component lifecycle come into it?
Any help/suggestions appreciated.
Give it a default?
const users = this.props.users || {};
Using ES6 and destructuring:
const { users = {} } = this.props;