Rendering Multiple React Components with .map - javascript

This may be a complete noob question, but I am having trouble figuring this out. I have a React component, and inside its render method, I want to render some dynamically passed in components (through props).
Here's my return from the render method (ES6, transpiled with Babel)
return (
<div className={openState}>
/* Shortened for brevity*/
{this.props.facets.map(function(f) {
var CompName = facetMap[f.type]
return <CompName key={f.id} />
})}
/* Shortened for brevity*/
</div>
)
facetMap is simply a map to match up a string to the "type" passed in. I can't seem to get the components to actually render on the page.
I had the result of the .map() method stored in a var, and then logged that to the console, and it showed an array of components. However {facetComponents} in the render method had the same results.
EDIT, for those interested in the solution
Because I genuinely could not find an answer anywhere, here's my completed solution, thanks to #Mathletics
//Components imported using ES6 modules
import OCFacetText from "./oc-facet-text"
// Etc...
//Later on, in my render method:
let facetMap = {
"text": OCFacetText,
"number": OCFacetNumber,
"numberrange": OCFacetNumberRange,
"boolean": OCFacetBoolean,
"checkbox": OCFacetCheckbox,
// Etc, etc...
}
let facetComps = facets.map(function(f) {
var CompName = facetMap[f.type]
return <CompName key={f.id} />
})
//Finally, to render the components:
return (
<div className={openState}>
/* Shortened for brevity*/
{facetComps}
/* Shortened for brevity*/
</div>
)

From what I can gather, it sounds like your facetMap is a collection of strings:
var facetMap = {text: 'OCFacetText'}
but it should actually return component classes:
var OCFacetText = React.createClass({...});
var facetMap = {text: OCFacetText};
Then (and only then) you will need to use the createElement method to construct your component dynamically:
var CompName = facetMap[f.type]; // must be a FUNCTION not a string
return React.createElement(CompName, {key: f.id}) />

Annoying one.
If you want dynamic component names you'll have to utilize React.createElement(CompName), where CompName is a string or the class object.
By default react will look for a component named CompName in your above example.
So your render function would look like so:
return (
<div className={openState}>
/* Shortened for brevity*/
{this.props.facets.map(function(f) {
var CompName = facetMap[f.type]
return React.createElement(CompName, {key: f.id}) />
})}
/* Shortened for brevity*/
</div>
)

Related

React.js State updating correctly only once, then failing [duplicate]

This question already has answers here:
Using a Set data structure in React's state
(2 answers)
Closed 1 year ago.
I have a parent and a child component. There are 3 props the parent provides out of which 1 is not updating correctly.
Following is the parent component. The prop in question is selectedFilters (which is an object where keys are mapped to sets) and the relevant update function is filterChanged (this is passed to the child)
import filters from "../../data/filters"; //JSON data
const Block = (props) => {
const [selectedFilters, setSelectedFilters] = useState({versions: new Set(), languages: new Set()});
console.log(selectedFilters);
const filterChanged = useCallback((filter_key, filter_id) => {
setSelectedFilters((sf) => {
const newSFSet = sf[filter_key]; //new Set(sf[filter_key]);
if (newSFSet.has(filter_id)) {
newSFSet.delete(filter_id);
} else {
newSFSet.add(filter_id);
}
const newSF = { ...sf, [filter_key]: new Set(newSFSet) };
return newSF;
});
}, []);
return (
<FilterGroup
filters={filters}
selectedFilters={selectedFilters}
onFilterClick={filterChanged}
></FilterGroup>
);
};
export default Block;
The following is the child component: (Please note that while the Filter component runs the filterChanged function, I think it is irrelevant to the error)
import Filter from "./Filter/Filter";
const FilterGroup = (props) => {
const { filters, selectedFilters, onFilterClick } = props;
console.log(selectedFilters);
const filter_view = (
<Container className={styles.container}>
{Object.keys(filters).map((filter_key) => {
const filter_obj = filters[filter_key];
return (
<Filter
key={filter_obj.id}
filter_key={filter_key}
filter_obj={filter_obj}
selectedFilterSet={selectedFilters[filter_key]}
onFilterClick={onFilterClick}
/>
);
})}
</Container>
);
return filter_view;
};
export default FilterGroup;
When running the application, I find that the selectedFilters updates correctly only once. After that, it only changes temporarily in the main Block.tsx, but eventually goes back to the first updated value. Also, FilterGroup.tsx only receives the first update. After that, it never receives any further updated values.
Here are the logs:
After some experimentation, it is clear that the problem originates from the filterChanged function. But I cannot seem to figure out why the second update is temporary AND does not get passed on to the child.
Any ideas? Thanks in advance.
(If any other info is required, pls do mention it)
I don't think you actually want your filterChanged function to be wrapped with useCallback, especially with an empty deps array. with the empty deps array, I believe useCallback will fire once on initial render, and memoize the result. You may be able to add filter_key and filter_id to the dependency array, but useCallback tends to actually slow simple functions down, instead of adding any real performance benefit, so you may just want to get rid of the useCallback completely and switch filterChanged to a regular arrow function.

An object component

I'm learning React and I'm trying to render a form with a few different form-steps (details, payments, confirm...) from a UI library that has Form component ready, which iterates through those form-steps.
The issue is that each 'step' component looks more like an actual JS object:
export const DetailStep = (
<div>Details screen with inputs, texts and images...</div>
);
It doesn't have the = () => so it's not an actual functional component, so I can't add hooks or functions into it.
That's the array of steps:
const stepPages = [
DetailStep,
PaymentStep,
ConfirmStep
];
And that's the actual Form component that iterates through the array of steps:
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => {stepPages[step]} }
/>
If I'll change any step to a functional component (DetailStep = () => ...), the step will be blank and won't be rendered.
How can I fix that / just add hooks to each 'step' component?
JSX declarations are converted into React.createElement function calls, this does mean that the stepPages in your example are objects (just that they are react objects)
The magic that turns them into function components happens in the render prop of the form:
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => {stepPages[step]} }
/>
You can see that it's passing a function in here, that returns the jsx objects. This is kinda the same as putting the jsx directly in the render function.
Although I notice that in your example, the jsx isn't being returned, so I would expect this example to not work correctly.
The longform equivalent would be something like this:
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => {
if (step === 0) {
return (
<div>Details screen with inputs, texts and images...</div>
)
} else if (step === 1) {
return (
<div>Details screen with inputs, texts and images...</div>
)
} // etc
}}
/>
EDIT to answer question in comments
You're right, so how can I add hooks into those object components?
If each of the form steps needs to use hooks and manage it's internal state, then I would make them functional components instead of JSX objects, then pick the right component from the stepPages map and render it like so:
export const DetailStep = (props) => {
// hooks and stuff here
return (
<div>Details screen with inputs, texts and images...</div>
);
}
const StepComponent = stepPages[step];
return (
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => <StepComponent {...formRenderProps} />}
/>
);

How to Pass React form state from app to multiple components (functional)

I'm still fairly new to React, so I'm sorry if this is a repetitive post. I'm working with React to create a form that spreads across different pages. The idea is to have the type of form you'd receive from a job application. One that has multiple steps.
I have different components made for each step of the form. The first page is a home page. Imagine a button to take you to the create page. Then you see the Create page. There is a small form with an amount and name. When you click the next page, you'll see the Customize page. There you can edit your budget by priority.
Here in my poorly drawn picture, I have the app obviously, and a budget homepage to manage state across it's children. I figured I should do it this way because, well it's the only way I know how. The problems I have, are one: I don't know how to properly pass state throughout the components. Two: I don't even know if what I'm doing is correct. The React docs use forms in Class based components, which sort of helps, but I can't configure it to my problem.
Any help or suggestions are greatly appreciated. Also I want to show code from the Budget Page.
import React, { useState, useEffect, useRef } from "react";
import BudgetResultsPage from './BudgetResultsPage';
const [count, setState] = useState(0);
const handleStateCount = () => {
if(count>3) {count = 0;}
return setState(count + 1);
}
if(count === 0) {
console.log(`homepage state ${count}`)
return (
<div className={Styles.Calculator}>
<BudgetHomePage setState={count} handleStateCount={handleStateCount}/>
</div>
)
} else if(count===1) {
console.log(`budget create state ${count}`)
return (
<div className={Styles.Calculator}>
<CalculatorHeader />
<CreateBudget setState={count} handleStateCount={handleStateCount} setAmount={amount} handleAmount={handleAmount}/>
</div>
)}
There are obviously more imports for the other pages and more code passed in the component. This is just a snippet of the way I'm handling the components.
It's probably silly, but I'm creating a count in state, then when part of the form is submitted, the count goes up and it changes the page based on what the count is at. Works great for just one state, but I'm having problems with adding more state to it.
Thanks again!
I have written an example functional components illustrating some of your questions. I wrote this at home and only had notepad++ so might not compile if you copy paste. The comments will explain your questions
import React from 'react';
import Create_budget_page from './Create_budget_page.js';
const BudgetPage = props => {
// State 1: assigning one value to the state.
// State 2: assinging a list to the state.
// State 3: assinging a object to the state.
const [simple_state, set_simple_state] = useState(false);
const [list_state, set_list_state] = useState(["Hello","World","!"]);
const [object_state, set_object_state] = useState(
{
curmenu: "human_bios",
height: "196cm",
weight: "174lbs",
eye_colour: "blue",
hair_colour: "dirty_blonde"
}
);
// there are several possiblities, here's one with a list of objects
const [list_objects, set_list_objects] = useState(
[
{count: 69, wasClicked: false},
{count: 420, wasClicked: true},
// endless possibilities, the tricky part is properly correctly updating your states deep in the list
{integers: [1,5,2,3], keys: {isAlive: false, cur_menu: "config_menu"}}
]
);
// this function updates the state that stores an object
// arguments:
// new_values_object: the programmer passes in a object to the function with the values they want to update
// an example of calling this function in a child functional component:
// props.update_object_state({height: "165cm", weight: "143lbs", hair_colour: "black"});
function update_object_state(new_values_object){
set_object_state(prevState => {
const new_obj = prevState;
// loop through object keys and update the new_obj with the object passed in as a argument
for (var key in new_values_object){
new_obj[key] = new_values_object[key];
}
// returning the new_object will update the object_state
return new_obj;
})
}
// conditionally render based on state
switch(object_state["curmenu"]){
case "home_page":
return (
// pass in
// all props, 1 prop, state
<Create_budget_page {...props} parent_id={prop.parent_id} simple_state={simple_state} list_objects={list_objects}/>
) ;
break;
// pass in function
case "human_bios":
return (
<div className="error_page" onClick={() => {props.update_parent_state({error_occured: true})}}>This is an Error Page, click me to report diagnostics</div>
);
break;
// if none of cases are met default code executes
default:
// renders nothing
return null;
}
}
Which kind of problems are you experiencing? I assume that the part of code you left there is inside a component body (a function, as you are using functional way).
You should have no problems passing multiple props to child components even though you have to keep in mind other approaches (excesive number of props means something is wrong in general...)
Let me know if you have more questions.
EDIT:
Suppose you have that ResultsPage child component and in your parent component you do something like:
<ResultsPage someProp={someValue} someOtherProp={someOtherValue} />
Then in your child component you can manage those props like this:
const ResultsPage = (props) => {
// props is an object like this: { someProp: someValue, someOtherProp: someOtherValue}
... // and in the body of the function you can manipulate it however you want
}
Generally it is better to destructure your props variable in the param directly instead of always do props.someProp, etc. like this:
const ResultsPage = ({someProp, someOtherProp}) => { ... }
More info about destructure here

ReactJS JSX toString()

I'm writing a documentation website based on React. I want to show the code that is necessary to use a given component from my framework. At the same time I would like to show the actual component running, like a side-by-side view.
Currently, I'm adding the component as a String for the reference implementation and the component as JSX for the running scenario. Something like this:
var ButtonDoc = React.createClass({
render: function () {
let buttonComponent = (
<Button label="Add" />
);
let buttonCode = `<Button label="Add" />`;
return (
<div>
{buttonComponent}
<pre><code>{buttonCode}</code></pre>
</div>
);
}
});
Question: Is there a way that I can get the string representation of the given React component without the need to replicate the code?
I'm expecting something like this:
var ButtonDoc = React.createClass({
render: function () {
let buttonComponent = (
<Button label="Add" />
);
let buttonCode = `${buttonComponent}`;
return (
<div>
{buttonComponent}
<pre><code>{buttonCode}</code></pre>
</div>
);
}
});
The output of the given code is object [object].
As I did not find anything that solved my problem, I ended up creating a npm repository to achieve this task.
https://github.com/alansouzati/jsx-to-string
Usage:
import React from 'react';
import jsxToString from 'jsx-to-string';
let Basic = React.createClass({
render() {
return (
<div />
);
}
}); //this is your react component
console.log(jsxToString(<Basic test1="test" />)); //outputs: <Basic test1="test" />
This is super late but in case anyone read this half a decade later (React v17, Native 0.68), you can also just use curly braces inside of backticks: `${integerToString}`. This will convert your embedded value to string.

How can I pass props/context to dynamic childrens in react?

I am using react, and I am trying to pass props/context to my dynamic childrens,
by dymamic childrens I mean childrens are render using
{this.props.children}
How can I pass to this children (In my code I know it's type) context/props?
In this jsbin there is an example that it dosen't work on dynamic childrens.
http://jsbin.com/puhilabike/1/edit?html,js,output
Though #WiredPrairie's answer is correct, the React.addons.cloneWithProps is deprecated as of React v0.13RC. The updated way to do this is to use React.cloneElement. An example:
renderedChildren = React.Children.map(this.props.children, function (child) {
return React.cloneElement(child, { parentValue: self.props.parentValue });
});
There's not a a great way to do this that is clear and passing all the properties of the parent isn't a great pattern and could lead to some very difficult to follow code if not done carefully (and with excellent documentation). If you have a subset of properties though, it's straightforward:
JsFiddle
Assuming you're using React with Addons, you can clone the children of a React component and set new property values on them. Here, the code just copies a property called parentValue into each child. It needs to create a clone of each element as the child element had already been created.
var Hello = React.createClass({
render: function() {
var self = this;
var renderedChildren = React.Children.map(this.props.children,
function(child) {
// create a copy that includes addtional property values
// as needed
return React.addons.cloneWithProps(child,
{ parentValue: self.props.parentValue } );
});
return (<div>
{ renderedChildren }
</div>)
;
}
});
var SimpleChild = React.createClass({
render: function() {
return <div>Simple { this.props.id }, from parent={ this.props.parentValue }</div>
}
});
React.render((<Hello parentValue="fromParent">
<SimpleChild id="1" />
<SimpleChild id="2" />
</Hello>), document.body);
Produces:
Simple 1, from parent=fromParent
Simple 2, from parent=fromParent
Spreading props on DOM elements
https://github.com/vasanthk/react-bits/blob/master/anti-patterns/07.spreading-props-dom.md
When we spread props we run into the risk of adding unknown HTML
attributes, which is a bad practice.
const Sample = () => (<Spread flag={true} domProps={{className: "content"}}/>);
const Spread = (props) => (<div {...props.domProps}>Test</div>);

Categories

Resources