Advanced React
1. Moving state down
export default function App() {
// const [isOpen, setIsOpen] = useState(false);
return (
<div className="layout">
<Header />
{/* <button onClick={() => setIsOpen(true)}>Open Dialog</button>
{isOpen ? <ModalDialog /> : null} */}
<ButtonWithModalDialog />
<BunchOfStuff />
<OtherStuffAlsoComplicated />
<Footer />
</div>
);
}
2. Components as props / Children as props
Before
export default function MainScrollableArea() {
const [position, setPosition] = useState(300);
const onScroll = (e) => {
const calculated = getPosition(e.target.scrollTop);
setPosition(calculated);
};
const getPosition = (val) => {
return val;
};
return (
<div className="scrollable-block" onScroll={onScroll}>
<MovingBlock position={position} />
<BunchOfStuff />
<OtherStuffAlsoComplicated />
</div>
);
}
After
export default function ScrollableWithMovingBlock({ content }) {
const [position, setPosition] = useState(0);
const onScroll = () => {
setPosition();
}; // Same as before
return (
<div className="scrollable-block" onScroll={onScroll}>
<MovingBlock position={position} />
{content}
</div>
);
}
export default function App() {
const slowComponents = (
<>
<BunchOfStuff />
<OtherStuffAlsoComplicated />
<Footer />
</>
);
return <ScrollableWithMovingBlock content={slowComponents} />;
}
// Or can written as
export default function App() {
return (
<ScrollableWithMovingBlack>
<BunchOfStuff />
<OtherStuffAlsoComplicated />
<Footer />
</ScrollableWithMovingBlack>
);
}
3. Configuration concerns with elements as props
Button Example
export default function Button({ icon }) {
return <button>Submit {icon}</button>;
}
<Button icon={<Loading />} />;
<Button icon={<Warning color="yellow" />} />;
ModalExample
const ModalDialog = ({ content, footer }) => {
return (
<div className="modal-dialog">
<div className="content">{content}</div>
<div className="footer">{footer}</div>
</div>
);
};
<ModalDialog content={<SomeFormHere />} footer={<SubmitButton />}>
<ModalDialog content={<SomeFormHere />} footer={<><SubmitButton /><CancelButton /></>}>
Three Columns Layout
<ThreeColumnsLayout
leftColumn={<Something />}
middleColum={<OtherThing />}
rightColumn={<SomethingElse />}
/>
Conditional Rendering and performance
const App = () => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
//When is this one going to be rendered
const footer = <Footer />;
return isDialogOpen ? <ModalDialog footer={footer} /> : null;
};
const ModalDialog = ({ children, footer }) => {
return (
<div className="dialog">
<div className="content">{children} </div>
{/* Whatever is coming from footer prop is going to be
rendered only when this entire component renders */} {/* not sooner */}
<div className="footer">{footer}</div>
</div>
);
};
Default values for the elements from props
// primary button should have white icons
<Button appearance="primary" icon={<Loading color="white" />} />
// secondary button should have black icons
<Button appearance="secondary" icon={<Loading color="black" />} />
// Large button should have large icons
<Button size="large" icon={<Loading size="large" />} />
export default function Button({ appearance, size, icon }) {
//create default props
const defaultIconProps = {
size: size === "large" ? "large" : "medium",
color: appearance === "primary" ? "white" : "black",
};
const newProps = {
...defaultIconProps,
// make sure that props that are coming from the icon override default if they exist
...icon.props,
};
// clone the icon and assign new props to it
const clonedIcon = React.cloneElement(icon, newProps);
return <button>Submit {clonedIcon}</button>;
}
// primary button will have white icons
<Button appearance="primary" icon={<Loading />} />
// secondary button will have black icons
<Button appearance="secondary" icon={<Loading/>} />
// Large button will have large icons
<Button size="large" icon={<Loading />} />
// override the default black color with red icons
<Button
appearance="secondary" icon={<Loading color="red" />}
/>