React: Generating dynamically a nested component - javascript

I am really struggling in this problem.
I am making an admin page based with next.js, I am working for the layouts.
What I want is based of the url path, I generate the nested layouts for my web page.
For example: if the URL is home/test/route1 then I should generate in a variable something like this:
<HomeLayout>
<TestLayout>
<Route1Layout>
<main>{children}</main>
</Route1Layout>
</HomeLayout>
</TestLayout>
This is how I have implement it in the main component file (layoutsManager.js):
// we import the layout that will be persistent trought the home url and his child
import HomeLayout from "./home/HomeLayout.jsx";
// this map must containt other map or Component functions
const layoutsMap = new Map(Object.entries({
"home": new Map(Object.entries({
"index":HomeLayout,
"test": new Map(Object.entries({
"route1":HomeLayout,
"route2":HomeLayout
}))
})),
}));
export default function DisplayLayout({urlPath,children}) {
let pathArray = urlPath.slice(1).split("/");
function GenerateLayout({path, map, children}) {
let newMap = map;
let layoutsArray = [];
path.forEach((name) => {
if (newMap.has(name)) {
if (newMap.get(name) instanceof Map) {
newMap = newMap.get(name);
if (newMap.has("index")) {
let NewElement = newMap.get("index");
layoutsArray.push(NewElement);
}
}
else {
let NewElement = newMap.get(name);
layoutsArray.push(NewElement);
}
}
});
let NewLayout = ({children}) => <>{children}</>;
if (layoutsArray.length > 0) {
let LastLayout = layoutsArray.pop();
layoutsArray.forEach((Layout,index) => {
NewLayout = <NewLayout><Layout></Layout></NewLayout>;
});
NewLayout = <NewLayout><LastLayout></LastLayout></NewLayout>;
}
else {
NewLayout = <h1 className="text-red-600 font-bold text-2xl">Error, there is no layouts in the layoutsMap Map</h1>;
}
return (NewLayout);
};
return (
<GenerateLayout path={pathArray} map={layoutsMap}>{children}</GenerateLayout>
);
};
To explain it, in the local GenerateLayout component, I first retrieve all the React Component from the layoutsMap variable then I get an array full of react components.
Next I read each element of the array by adding the child component into the new parent component and when I reach the last element, I add the last child with the content of the page.
Like this for the following array [HomeLayout,TestLayout,Route1Layout]:
Index 0:
<><HomeLayout></HomeLayout></>
Index 1:
<><HomeLayout><TestLayout></TestLayout></HomeLayout></>
Index 2:
<><HomeLayout><TestLayout><Route1Layout><main>{children}</main></Route1Layout></TestLayout></HomeLayout></>
I hope you get the idea but my nightmare is that I always get stuck with the same damn error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: ...
Any help would be really appreciated.
Thanks in advance.

Related

How to pass "parameter" to react component

I have a quick question, I am attempting to refactor my code in a existing codebase, this codebase uses a render function, however I would like to replace this with a react functional component instead.
However I am getting a error when I am passing in a parameter called "searchResults" which is a list.
Here is the previous non-refactored code
searchResults.sort((a, b) => new Date(b[0].created) - new Date(a[0].created));
const Rows = [];
searchResults.forEach((appResults, i) => {
Rows.push({
cells: [
appResults[0].applicationId,
<>{renderSuccessCount(appResults)}</>,
prettifyDate(appResults[0].created)
],
isOpen: false
});
This is my refactored code
const newRows = [];
searchResults.forEach((appResults, i) => {
newRows.push({
cells: [
appResults[0].applicationId,
<SuccessCount>{appResults}={appResults}</SuccessCount>,
prettifyDate(appResults[0].created)
],
isOpen: false
});
This is the function SuccessCount (in the non refactored code, renderSuccessCount was just renamed to SuccessCount in the refactored version
const SuccessCount = (appResults: AppResult[]) => {
const nSuccesses = appResults.filter((result: AppResult) => result.pulseStatus === SUCCESS).length;
return Something;
};
My question is relatively simple how do I pass appResults into my functional component, my current refactored code gives me this error
TS2740: Type '{ children: any[]; }' is missing the following properties from type 'AppResult[]': length, pop, push, concat, and 26 more.
This is in the linter, how do I get rid of it?
Now that SuccessCount is not a regular function but a react functional component, it'll not receive regular arguments. You'll have to access them via props
const SuccessCount = ({appResults}: {appResults: AppResult[]}) => {
Same as
const SuccessCount = (props) => {
const {appResults} = props; // or const appResults = props.appResults;
}
Usage as functional component
<SuccessCount appResults={appResults} />

Using React Hooks, when I pass down a prop from parent to a child component, the prop in the child component is undefined

What am I trying to do?
I'm trying to set an array of objects in which the value within the array is dependent on the parent component.
What is the code that currently tries to do that?
Here are the different files simplified:
// Parent.
export default function Parent() {
const [filePaths, setFilePaths] = useState();
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
<Child filePaths={filePaths}/>
)
}
// Child.
export default function Child({filePaths}) {
var links = [
{
path: filePaths[0].Link1,
},
{
path: filePaths[0].Link2,
},
]
return (
<div>Nothing here yet, but I would map those links to front-end links.</div>
)
}
// config.json
{
"url": "http:localhost:3000",
"FilePaths": [
{
"Link1": "C:\Documents\Something",
"Link2": "C:\Documents\SomethingElse"
}
]
}
When I render the "filePaths" in the return() of the Child component, the "filePaths" is able to be rendered, but I wish to set the "filePaths" to the variable "links".
What do I expect the result to be?
I expect the variable "links" to be fine in the child component, being able to be used within the child component.
What is the actual result?
When starting the app I get a TypeError: Cannot read property '0' of undefined.
What I think the problem could be?
I think the child component renders without the parent component finishing the useEffect(). I'm wondering if there's a way to tell the child component to wait for the parent component to finish, then proceed with setting the variable of "links".
filePaths will be undefined because you call useState() with empty input.
There are two options (you can choose one) to solve this:
Initialize filePaths inside the useState()
Return the Child component if the filePaths is not null/undefined.
export default function Parent() {
const [filePaths, setFilePaths] = useState();
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
// return the Child component if the filePaths is not null/undefined
{filePaths && <Child filePaths={filePaths}/>}
)
}
I personally prefer the second one because we can add a loading component when the filePaths is still null/undefined.
You are right, that's why you should change your child component. It renders the filePaths whether it is defined or not.
Try to do as follows.
export default function Child({filePaths}) {
const [links, setLinks] = useState(filePaths);
useEffect(()=>{
setLinks(filePaths);
},[filePaths])
return (
<div>Nothing here yet, but I would map those links to front-end links.</div>
)
}
I think you're right on your guess about the sequence of methods calling:
According to this, when you use useEffect the method gets called after the rendering, as if it was a componentDidMount lifecycle method, which is supported by the React official lifecycle diagram and React Documentation. And that is the reason because the props.filePaths whithin the Child component is undefined.
To avoid this, you should try to set an initial value (in the useState method).
something like the following (maybe extracting the repetition as a function):
// Parent.
export default function Parent() {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
const [filePaths, setFilePaths] = useState(tempFilePaths);
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
<Child filePaths={filePaths}/>
)
}
const [filePaths, setFilePaths] = useState();will initialize filePaths as undefined.
so you can check firstly like
if (!filePaths) return null in the parent;
or set initState in useState like
#see https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [filePaths, setFilePaths] = useState(()=> {
var fileContent = JSON.parse(fs.readFileSync("./config.json");
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
return tempFilePaths;
});

Creating a var referencing a store from redux

I am currently working on creating a var that references a store from redux. I created one but within the render(). I want to avoid that and have it called outside of the render. Here is an example of it. I was recommended on using componentWillMount(), but I am not sure how to use it. Here is a snippet of the code I implemented. Note: It works, but only when I render the data. I am using double JSON.parse since they are strings with \
render() {
var busData= store.getState().bus.bus;
var driverData= store.getState().driver.gdriveras;
var dataReady = false;
if (busData&& driverData) {
dataReady = true;
console.log("========Parsing bus data waterout========");
var bus_data_json = JSON.parse(JSON.parse(busData));
console.log(bus_data_json);
console.log("========Parsing driver data waterout========");
var driver_data_json = JSON.parse(JSON.parse(driverData));
console.log(driver_datat_json);
busDatajson.forEach(elem => {
elem.time = getFormattedDate(elem.time)
});
driverDatajson.forEach(elem => {
elem.time = getFormattedDate(elem.time)
});
...
}
}
Here is an example of react-redux usage that will probably help you.
Don't forget to add StoreProvider to your top three component (often named App).
I warned you about the fact that React and Redux are not meant to be used by beginner javascript developer. You should consider learn about immutability and functional programming.
// ----
const driverReducer = (state, action) => {
switch (action.type) {
// ...
case 'SET_BUS': // I assume the action type
return {
...state,
gdriveras: JSON.parse(action.gdriveras) // parse your data here or even better: when you get the response
}
// ...
}
}
// same for busReducer (or where you get the bus HTTP response)
// you can also format your time properties when you get the HTTP response
// In some other file (YourComponent.js)
class YourComponent extends Component {
render() {
const {
bus,
drivers
} = this.props
if (!bus || !drivers) {
return 'loading...'
}
const formatedBus = bus.map(item => ({
...item,
time: getFormattedDate(item.time)
}))
const formatedDrivers = drivers.map(item => ({
...item,
time: getFormattedDate(item.time)
}))
// return children
}
}
// this add bus & drivers as props to your component
const mapStateToProps = state => ({
bus: state.bus.bus,
drivers: state.driver.gdriveras
})
export default connect(mapStateToProps)(YourComponent)
// you have to add StoreProvider from react-redux, otherwise connect will not be aware of your store

Is it possible to hold a list of react components in state and render them?

I've been attempting to learn some React programming recently, and I ran into some confusion when learning about rendering lists.
The React documentation describes this method for rendering lists:
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
Out of curiosity, I decided to try holding a list of React elements in the state of one of my components and render that list directly. I figured that it might be able to avoid the need of mapping the data to an element every time a render occurred.
I was able to make this work:
'use strict';
class List2 extends React.Component {
constructor(props) {
super(props);
let nums = [];
for(let i = 0; i < 5; i++) {
nums.push(React.createElement('div', null, i));
}
this.state = {
numbers : nums,
}
}
render() {
return (
React.createElement('div', null, this.state.numbers)
)
}
}
However, I tried to add a button to my window that added elements to the element list, the new elements added by the button's onCLick function don't render. This is the code that doesn't work:
'use strict';
class List3 extends React.Component {
constructor(props) {
super(props);
this.state = {
nextNum : 1,
numbers : [],
}
this.buttonAction = this.buttonAction.bind(this);
}
buttonAction() {
let numElement = React.createElement('h1', null, this.state.nextNum);
let newNumber = this.state.numbers;
newNumber.push(numElement);
this.setState(
{ nextNum : (state.nextNum + 1),
numbers : newNumbers,
}
);
}
render() {
return (
React.createElement('div', null,
this.state.numbers,
React.createElement('button', {onClick : this.buttonAction}, 'clicketh')
)
)
}
}
When I click the button, I don't see new numbers render on the screen.
Can someone help me explain what's going on here?
Is the mapping method from above the only reliable way to render lists with react?
React only renders those elements which got change from last render, If you want to force render then you have to force react to do it otherwise it will not render new elements.
Arrays are reference type so if will add or remove elements to it, it will not create a new copy and react will consider it as unchanged.
For your render issue you need to create a new copy of "numbers" each time you add element so react will consider it as changed state and render as new.
you can achieve this by using map function in your render method which will provide react a new copy of array or use "slice" in your button click event so while setting new numbers state it will create a new shallow copy of "numbers" each time.
below are snippets for doing it in both ways.
buttonAction() {
let numElement = React.createElement('h1', { key: this.state.nextNum },
this.state.nextNum);
let newNumbers = this.state.numbers;
newNumbers.push(numElement);
this.setState(
{
nextNum: (this.state.nextNum + 1),
numbers: newNumbers.slice(),
}
);
}
Or
render() {
return (
React.createElement('div', null,
this.state.numbers.map(item=>item),
React.createElement('button', { onClick: this.buttonAction }, 'clicketh')
)
)
}
for more info on array please follow below link.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
Really not sure why you are doing this.
But maybe you can try something like this ?
this.setState({
numbers :[...this.state.numbers, numElement],
});
Creating a copy of numbers instead of reusing the old reference.

Searching huge object by property performant caching

i have a huge js object which hold all user posts,
some users might have more than 300+ post in this object, so i want to implement a search mechanism
example:
{
postById:{
1:{ id:1, title:'varchar 250 string' },
2:{ id:2, title:'varchar 250 string' },
...etc for 300+ items
}
}
ps.: i' using ES6, its a react-native project.
a simple approach for search can be:
_handlSearch(keyword){
const key= keyword.toLowerCase();
return Object.keys(postById).filter(id=>posts[id].title.indexOf(key)>-1)
.map(id=>postById[id])
}
now this function is fine, by question is how often should i trigger search ?
and lets say user type "var" this will trigger search, then he add a new letter "varc" so it would make sence to not filter the master object buy rather search the already short list which came from "var".
is there a already existent solution that optimize such autocomplete/search functionality ?
Updated.
You can set master list as list state property and then repeatedly search in it until you don't have some keyword which doesn't contain your current keyword in component.
Basically, this version:
import React, { Component } from 'react';
class ShortList extends Component {
constructor(props){
super(props);
this.state = {list:props.master,keyword:String(props.keyword||'').toLowerCase()};
this._hadleSearchMaster = this._hadleSearchMaster.bind(this)
this.trigger = this.trigger.bind(this)
this.extract = typeof props.getValue==='function' ? props.getValue : f=>String(f);
this.minLength = props.minLength||3;
this.debounce = 0;
}
_hadleSearchMaster(){
const list = Object.keys(this.props.master).map(id=>this.extract(this.props.master[id]).indexOf(this.state.keyword)>-1);
console.log('searched master and returned'+this.state.keyword,Object.keys(this.props.master),list);
this.setState({list});
}
trigger(){
clearTimeout(this.debounce);
this.debounce = setTimeout(this._hadleSearchMaster, 200);
}
componentWillReceiveProps(props){
this.extract = typeof props.getValue==='function' ? props.getValue : f=>String(f);
if(props.getValue!==this.props.getValue)this.extract = props.getValue;
if(props.minLength!==this.props.minLength)this.minLength = props.getValue;
if(!props.keyword || props.keyword.length < this.minLength)return;
if(props.keyword===this.props.keyword)return;
const keyword = String(props.keyword||'').toLowerCase();
const stateToSet = {keyword};
if (keyword.substr(0, this.state.keyword.length) !== this.state.keyword) {
stateToSet.list = props.master;
}
this.setState(stateToSet,
this.trigger)
}
render() {
return this.props.render(this.state.list);
}
}
//<Shortlist master={{}} style={{}} getValue={f=>f.title.toLowerCase()} keyword='search' render={idList=>null} minLength={3} />
export default ShortList

Categories

Resources