ReactJs How to dynamically render components based on value - javascript

I have been asked to solve a problem using any front-end framework ( reactjs in my case ) . Basically there is a form for adding a car and there can be say a type of car like 'SUV','semitruck','racecar' and so on...
In the form there needs to be different components rendered based on the type of car you want to add ( e.g. SUV has 2 inputs , racecar has 5 inputs ) how can i dynamically render these components and get their input values without doing if statements?
So pretty much i want to avoid doing this :
{typeSelection == "SUV" && (
<SVUInput
size={sizeInput}
changeSize={(str) => setSizeInput(str)}
/>
)}
{typeSelection == "Bus" && (
<WeightInput
Weight={weightInput}
changeWeight={(str) => setWeightInput(str)}
/>
)}
{typeSelection == "Semitruck" && (
<DimensionInput
wheels={wheels}
length={length}
changeWheels={(n) => setWheels(n)}
changeLength={(str) => setLength(str)}
/>
)}
I tried doing this but doesnt work ( im guessing react doesnt re-render here )
const [dynamicInputs, setDynamicInputs] = React.useState<any>({
SUV: <SUVInput size={sizeInput} changeSize={(str) => setSizeInput(str)} />,
bus: <BusInput weight={weightInput} changeSize={(n) => setSizeInput(n)} />,
semitruck: <SemitruckInput wheels={wheelsInput} changeWheels={(n) => setWheelsInput(n)}
color={colorInput} changeColor={(n) => setColorInput(n)} />,
});
Instead the above code although renders the component , the input does not change when i type into it, it remains blank , i assume it doesnt trigger react to re-render the state
So pretty much instead of making many if statements that would slow down the front-end , Im trying to make it dynamic so that it only takes O(1) time to render the correct form inputs.

I pretty like your solution because that's the thing I'm used to do really often. Your issue is about storing the components inside useState, because they are initialized there when component mounts and they just stay in the same state for the whole component lifetime. The solution is pretty simple as well - just move it out of the state so they do react to state and props changes.
https://codesandbox.io/s/frosty-wozniak-xcy2bv?file=/src/App.js:114-536
export default function App() {
const [size, setSize] = useState(0);
const [currentComponent] = useState('SUV');
const components = {
SUV: <SuvComponent size={size} />,
};
return (
<div className="App">
<input onChange={(e) => setSize(e.target.value)} />
{components[currentComponent]}
</div>
);
}

it works if i implement the UseState method and then have a useEffect to update it like so :
React.useEffect(() => {
setDynamicInputs({
SUV: (
<SUV size={sizeInput} changeSize={(str) => setSizeInput(str)} />
),
bus: (
<WeightInput
Weight={weightInput}
changeWeight={(str) => setWeightInput(str)}
/>
),
});
}, [dynamicInputs]);

Related

React table library that supports mutable data ( useRef )

Basically i'm looking for react table library that can take a mutable object ( to be specific an useRef object) as the main source of data to be displayed.
Basically i want to do something like this:
const TableViewComponent = () =>{
const tableData = useRef([{ rowName: 'test', value:0}] -> basically an array of objects that represents data for every row (the structure doesnt matter)
# code that receives data from server and updates the tableData.current with the data needed
return(
<Table data={tableData.current}/>
)
}
Basically, since i get a bunch of messages from the server and i update the data constantly (the number of rows stays the same), i don't want to rerender the table everytime. So i want to use the useRef to change the data thats being displayed on the table without triggering a rerender from react.
Im not sure if its something that can be done but any help is appreciated :). I tried react-table, rc-table but they didnt seem to work.
Basically, it looks to me like you'll have to do it yourself.
There's some libraries that might help you (like useTable which focuses on headless components) but I don't think they offer what you're looking for.
So let's quickly do a mock-up! (note: this is a quick sketch, assume that the undefined variables are stored somewhere else or are given from the fetch)
function useTableData({ initialData, itemsPerPage, ...etc }) {
const data = useRef(initialData);
const [loading, setLoading] = useState(false);
useEffect(() => {
data.current = fetchFromSomeWhere(...etc);
() => (data.current = null);
}, [etc /* Place other dependencies that invalidate out data here*/]);
const handleNewPage = useCallback(
async ({ pageIndex }) => {
if (!data.current[pageIndex * itemsPerPage]) {
setLoading(true);
data.current = [...data.current, await fetchMoreData(pageIndex)];
}
setLoading(false);
return data.current;
},
[itemsPerPage, data, setLoading],
);
return [data, handleNewPage, loading];
}
Notice how every single thing returned from this hook is a constant reference except for the loading! Meaning, that we can safely pass this to a memoized table, and it won't trigger any re-renders.
const TableContainer = memo(etc => {
const [data, handleNewPage, loading] = useDataForTable(...etc);
return (
<>
{loading && <Spinner />}
{/* Look ma, no expensive-renders! */}
<Body {...{ data }} />
<Pagination {...{ handleNewPage }} />
<OtherActions />
</>
);
});
However, you might have noticed that the body won't re-render when we click on the next page! Which was the whole point of pagination! So now we need some sort of state that'll force the Body to re-render
const TableContainer = memo(etc => {
const [currentPage, setPage] = useState(0);
const [data, handleNewPage, loading] = useDataForTable(...etc);
return (
<>
{loading && <Spinner />}
{/* We're still re-rendering here :( */}
<Body {...{ data, currentPage }} />
<Footer {...{ handleNewPage, setPage }} />
<OtherActions />
</>
);
});
And so we're left with a table that to use properly we need to:
Exercise restraint and properly invalidate old data. Otherwise, we'd be displaying incorrect data.
'Unpack' the data from the current ref on the Body component, and then render it.
In essence, after all that work we're still left with a solution isn't particularly attractive, unless you have some really complicated actions or some expensive scaffolding around the TableComponent itself. This might however be your case.

Is there a way to generalize a component to have the props/onclick function without adding the same thing for others component

I use React + styled and my question is about the following code
<MobileButton
onClick={props.handleMobileDropdownElementClicked}
padding={isSmallDevice ? 1 : 0}
>
Name
</MobileButton>
Is there a way to generalise this component to write like this without having props and onclick()? like the following code.
<MobileButton>
Name
</MobileButton>
I was just curious if there is things in react or styled component that could do that if we are repeating the same component with the same props/onclick function.
Thank you
Sure, you can make a new component that doesn't require said props. But you'll still have to pass the props when creating that component initially.
For example:
const YourComponent = (props) => {
const NewMobileButton = (newProps) => {
return (
<MobileButton
onClick={props.handleMobileDropdownElementClicked}
padding={isSmallDevice ? 1 : 0}
>
{newProps.children}
</MobileButton>
)
}
return (
<>
<NewMobileButton>1</NewMobileButton>
<NewMobileButton>2</NewMobileButton>
</>
)
}

React uses Garbage Collector too much and slows down my app

I am building a app. and I came to a point where i need to render about 1000 complex components, one after another. Here is the code that does this:
function App() {
var [records, setRecords] = useState([])
useEffect(() => {
fetch(api + sessionStorage.getItem("batch") +"/" + sessionStorage.getItem("page"))
.then((response) => response.json())
.then((data) => {
setRecords(data);
});
} , [] )
return (
<div className="App">
<PrimarySearchAppBar />
<Toolbar />{" "}{/* removing this will cause an overlay between the first card and the AppBar */}
{records.map((record) => {
return(
<RowCard
key={record.code}
str={record.str}
code={record.code}
translateButton={false}
saveButton={true}
val={record[languageCode]}
/>
);
})}
<Footer/>
</div>
);
}
export default App;
The "records" variable holds an array with 1000 objects, with 4 fields for each object
The problem is, when react tries to render these components, there is a big delay. After some debugging, i noticed that there is a big time gap between the "setRecords(data)" statement and the actual render of the components.
Later, i found out this:
And it goes on and on with the "Minor GC" things.
The "Promise Callback" that's on the big yellow bar refers to my "setRecords(data)" statement.

How to pass React Component as props to another component

I am trying to call PopupDialog.tsx inside Content.tsx as a sibling of Item.tsx.
Previously PopupDialog.tsx is called inside C.tsx file but due to z index issue i am trying to bring it out and call it in Content.tsx
Is it possible to somehow pass the whole component(popupDialog and its parameters) in Content.tsx so that i could avoid passing back and forth the parameters needed for popupdialog in content.tsx.
Code in C.tsx where PopupDialog component is called.
const C = (props: Props) => (
<>
{props.additionalInfo ? (
<div className="infoButton">
<PopupDialog // need to take this code out and want to add in Content.tsx
icon="info"
callback={props.callback}
position={Position.Right}
>
<div className="popuplist">{props.additionalInfo}</div>
</PopupDialog>
</div>
) : (
<Button className="iconbutton"/>
)}
</>
);
Content.tsx where i would like to call PopupDialog.tsx with its parameters
const Content = (props: Props) => {
const [componentToRender, docomponentToRender] = React.useState(null);
const [isAnimDone, doAnim] = React.useState(false);
return (
<div className="ContentItems">
<PWheel agent={props.agent} />
{isAnimDone && (
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog/> //want to call here with all its parameters to be passed
}
</>
)}
</div>
);
};
Folder Structure
App.tsx
->ViewPort.tsx
->Content.tsx
->PWheel.tsx
->Item.tsx
->A.tsx
->B.tsx
->C.tsx
{props.additionalinfo &&
->PopupDialog.tsx
->PopupDialog.tsx
So if I understand the question correctly you want to pass one component into another so that you can use the properties or data of the passed componenet in your current component.
So there are three ways to achieve this.
1)Sending the data or entire component as prop.This brings disadvantage that even though components which don't require knowledge
about the passed component will also have to ask as a prop.So this is bascially prop drilling.
2)The other is you can use context api.So context api is a way to maintain global state variale.so if you follow this approach you don't need to pass data or componenet as props.Wherever you need the data you can inport context object and use it in componenet.
3)Using Redux library.This is similar to context api but only disadavantage is that we will have to write lot of code to implement this.Redux is a javascript library.
Let me know if you need more info.
You need to :
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog abc={componentToRender} /> //you must call in this component, in this case i name it is abc , i pass componentToRender state to it
}
</>
and then PopupDialog will receive componentToRender as abc, in PopupDialog , you just need to call props.abc and done .
If you need to know more about prop and component you can see it here
I think what you want to use is Higher-Order-Components (HOC).
The basic usage is:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Below is such an implementation that takes a component (with all its props) as a parameter:
import React, { Component } from "react";
const Content = WrappedComponent => {
return class Content extends Component {
render() {
return (
<>
{/* Your Content component comes here */}
<WrappedComponent {...this.props} />
</>
);
}
};
};
export default Content;
Here is the link for higher-order-components on React docs: https://reactjs.org/docs/higher-order-components.html
Make use of
useContext()
Follow this for details:
React Use Context Hook

Checkbox in react functional component causes rerender

I'm using gatsby and have a functional component that loops through some data to create radio button group with an onchange event and checked item. When i update the state whole page component rerenders. i though adding memo was meant to stop this but it doesn't seem to work.
here is the code
const BikePage = React.memo(({ data }) => {
console.log("page data", data)
const [selectedColor, setColor] = useState(data.bike.color[0])
const onColorChange = e => {
setColor(e.target.value)
}
return (
<div>
{data.treatment.price.map((value, index) => {
return (
<div>
<input
id={`bike-option-${index}`}
name="treatment"
type="radio"
value={value}
checked={selectedColor === value}
onChange={e => onColorChange(e)}
/>
<label
htmlFor={`treatment-option-${index}`}
>
{value}
</label>
</div>
)
})}
<Link
to="/book"
state={{
bike: `${data.bike.title}-${selectedColor}`,
}}
className="c-btn"
>
Book Now
</Link>
</div>
)
});
If you update the state the component will re-render, that's fundamentally how react works. the memoised data prop is coming from outside of the component.
"If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result" react.memo
You're not changing the incoming props though, you're changing the state
Side note: i imagine that on changing this value you probably want to be changing the state of the data on the server through some means also ( REST POST / graphql mutation). Subsequent refetches of this data would re-render this component as well. It depends what you're trying to ultimately achieve.

Categories

Resources