Reusing a modal, in a React component, with different children - javascript

Recently i had to write something in React, that required me to render different components in a modal. Being that i didn't want to repeat my self with different modals in the same parent component, i decided to reuse it, but wasn't sure how to do it "correctly". This is what i have done:
renderModalTitle = () => {
return this.state.currentModalAction === 'delete' ? `Are you sure you want to delete book "${this.state.currentBook.title}"?`
: this.state.currentBook ? `Edit book "${this.state.currentBook.title}"`
: 'Create new book'
}
renderModalBody = () => {
return this.state.currentModalAction === 'edit' ||
this.state.currentModalAction === 'new' ?
<BookForm book={this.state.currentBook} onSave={this.onBookSave}>
</BookForm>
: <ConfirmDelete onBookDeleteCancel={this.toggle} onBookDelete={()=>
{this.onBookDelete(this.state.currentBook.id)}} data=
{this.state.currentBook}></ConfirmDelete>
}
I know it's a bit hard to read, because the indentation in the code snippet is slightly messed up. But as you can see, i just have functions that return the relevant jsx, according to the "currentModalAction". Then in the modal:
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className}>
<ModalHeader className="color_main" toggle={this.toggle}>{this.renderModalTitle()}</ModalHeader>
<ModalBody>
{this.renderModalBody()}
</ModalBody>
<ModalFooter>
<Button className="square" color="default" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
So yes, i've achieved "reusability" of the modal, and didn't repeat my self, but it seems to me, that this might do more harm than good...Not vert readable, not very clear.
Is there some common approach towards this issue? Notice that i didn't use react-modal or something like that. It's just reactstrap.

I made some code representing your case.
Your can use a function prop like renderBodyComponent that will render your modal body.
class FlexibleModal extends React.Component {
render() {
if (!this.props.isOpen) {
return null;
}
return (
<div className="flexible-modal">
<div className="flexible-modal-header">
{this.props.headerTitle}
</div>
<div className="flexible-modal-body">
{this.props.renderBodyComponent()}
</div>
</div>
);
}
};
const BodyCase1 = () => (
<div>
Modal Body Case 1
</div>
);
const BodyCase2 = () => (
<div>
Modal Body Case 2
</div>
);
class App extends React.Component {
state = {
showModal: false,
case: 1,
}
toggleModal = () => {
this.setState({ showModal: !this.state.showModal });
}
toggleCase = () => {
const nextCase = this.state.case === 1 ? 2 : 1;
this.setState({ case: nextCase });
}
render() {
return (
<div>
<button
onClick={() => this.toggleModal()}
>
Toggle modal
</button>
<button
onClick={() => this.toggleCase()}
>
Toggle next case
</button>
<FlexibleModal
isOpen={this.state.showModal}
headerTitle="Customizable Modal Header Title"
renderBodyComponent={
this.state.case === 1
? () => (<BodyCase1 />)
: () => (<BodyCase2 />)
}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react'));
.flexible-modal {
margin: 15px;
border: 1px solid #ddd;
background: #fff;
}
.flexible-modal-header {
border-bottom: 1px solid #ddd;
padding: 10px;
background: #e7e7e7;
}
.flexible-modal-body {
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>

Related

How to Handle multiple radio button inputs with one onChange function handler

i have a scenario where based on a number(say numberOfFlags) i want to render numberOfFlags times an radio button group.Each group has two radio buttons approve and reject as per screenshot attached how to get values of all inputs when they change?
An lastly i have to store result of all radio buttons (approve/reject) in an array and send to API
You need to use two parameters on onChange function. One is for current index and another is for Approve/Reject.
Like below code snippet
onchange = handleOnChage(index, isApproveClicked)
You can achive this in many different ways, but I would probably simple create a state with an array of values in the parent component and pass it to each and every item to toggle its own state depending action.
App.js
export function App() {
const [list, setList] = useState([false, false, false]);
const updateItem = (value, index) => {
let copyList = [...list];
copyList[index] = !value;
setList(copyList);
};
console.log(list)
return (
<div className="App">
{list && (
<>
{list.map((value, index) => (
<Item values={[value, index]} updateItem={updateItem} key={index+"_check"} />
))}
</>
)}
</div>
);
}
Item.js
export default function Item({ values, updateItem }) {
return (
<>
<input
onChange={() => updateItem(values[0], values[1])}
type="checkbox"
checked={values[0] ? "checked" : ""}
/>
</>
);
}
Presented below is one possible way to achieve the desired objective.
Code Snippet
const {useState} = React;
const Thingy = ({...props}) => {
// num-of-flags is obtained from props
const { numOfFlags: nf} = props || {};
// if it is null or not above 0, return "invalid" message to parent
if (!(nf && nf > 0)) return "invalid num-of-flags";
// state variable to store approve/reject status
const [statusObj, setStatusObj] = useState({});
// JSX to render the UI & handle events
return (
<div>
{([...Array(nf).keys()].map(grpIdx => (
<div className="grpBox">
Group num {grpIdx+1} <br/>
<input type='radio' name={grpIdx} id={grpIdx} value={'approve'}
onChange={() => setStatusObj({
...statusObj, [grpIdx]: 'approve',
})}
/>
<label for={grpIdx}>Approve</label>{" "}
<input type='radio' name={grpIdx} id={grpIdx} value={'reject'}
onChange={() => setStatusObj({
...statusObj, [grpIdx]: 'reject',
})}
/>
<label for={grpIdx}>Reject</label>
</div>
)))}<br/>
<button
onClick={() => {
// make API call here
// for verification, displaying an alert-message showing the status
const displayMsg = [...Array(nf).keys()].map(
gi => "Group num " + (+gi+1) + " : " + (gi in statusObj ? statusObj[gi] : '__')
).join(', ');
alert(`Approve-Reject status is: ${JSON.stringify(displayMsg)}`);
}}
>
Submit
</button>
</div>
);
};
ReactDOM.render(
<div>
<div className="demoTitle">DEMO</div>
<Thingy numOfFlags={5} />
</div>,
document.getElementById("rd")
);
.demoTitle {
margin-bottom: 5px;
font-size: 20px;
text-decoration: underline;
}
.grpBox {
margin: 5px; padding: 10px;
border: 2px solid purple;
width: max-content;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="rd" />
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,

how can I use if-else statement inline in reactJS?

I've got this code from a tutorial video , but first of all I didn't get the purpose of clk function and how it is related to h1 tag and that trinary operator.
second , how can I use normal if-else instead of ternary operator and not only for adding class but changing its style too.
import React, {useState} from 'react';
import "./App.css";
function App(){
let [isRed,setRed] = useState(false);
function clk(){
setRed(true);
}
return(
<div>
<h1 className={isRed?"red":""}>Change My Color</h1>
<button onClick={clk}>ClickHere</button>
</div>
);
}
export default App;
You can do that by applying this
<h1 className={`${isRed ? "red" : ""}`}>Change My Color</h1>
Or
{
isRed ? (
<h1 className={"red"}>Change My Color</h1>
) : (
<h1 className={"other"}>Change My Color</h1>
)
}
Enclose your elements inside of a {} makes it interpreted as js code
`
{if(isRed) return < h1>...< /h1> else return < h1>...< /h1>}
`
should work.. Maybe you can use the same inside the class attribute, but it will be hard to read.
As for the click function, it is setting the value of isRed to true. This will create the reactive change to your style.
You can bind a memo to your <h1> element that gets calculated when that particular state changes. Please note that JSX is not JavaScript. It may appear similar, and you may be able to use 99% of the syntax, but there are differences.
You can learn more about memoized values here: React / Docs / Hooks / useMemo
const { useMemo, useState } = React;
const App = () => {
let [isRed, setRed] = useState(false);
let [isBlue, setBlue] = useState(false);
const onClickRed = (e) => setRed(!isRed); // onclick callback
const onClickBlue = (e) => setBlue(!isBlue); // onclick callback
const headerPropsRed = useMemo(() => {
console.log('Red updated!');
let props = {};
if (isRed) {
props = {
...props,
className: 'red',
style: {
...props.style,
fontStyle: 'italic'
}
}
}
return props;
}, [ isRed ]);
const headerPropsBlue = useMemo(() => {
console.log('Blue updated!');
let props = {};
if (isBlue) {
props = {
...props,
className: 'blue',
style: {
...props.style,
fontStyle: 'italic'
}
}
}
return props;
}, [ isBlue ]);
return (
<div>
<h1 {...headerPropsRed}>Change My Color</h1>
<button onClick={onClickRed}>Click Here</button>
<h1 {...headerPropsBlue}>Change My Color</h1>
<button onClick={onClickBlue}>Click Here</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('react'));
.as-console-wrapper { max-height: 3em !important; }
h1 { font-size: 1em; }
.red { background: red; }
.blue { background: blue; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can use ternary operator working like this:
if(condition) ? "True part here" : "else part here (false part)"
Use case example :
const id = 1;
if(id === 1) ? "You are on right place" : "Sorry please check"
You can't use if-else in inline jsx but there exists some workarounds and you can choose whichever you want.
variant 1:
if(isRed){
const header = <h1 className='red' style={{ backgroundColor: 'red' ... }}>Change My Color</h1>
} else {
const header = <h1 style={{ backgroundColor: 'green' ... }}>Change My Color</h1>
}
return (
<div>
{header}
<button onClick={clk}>ClickHere</button>
</div>
);
variant 2: (but still ternary opeartor used)
if(isRed){
} else {
const header =
}
return (
<div>
{isRed ? (
<h1 className='red' style={{ backgroundColor: 'red' ... }}>Change My Color</h1>
) : (
<h1 style={{ backgroundColor: 'green' ... }}>Change My Color</h1>
)}
<button onClick={clk}>ClickHere</button>
</div>
);
There is no other way to replace ternary operator with if-else statement

Using state variable in styled components

Im trying to make a button that changes state depending on the value of a property on an object.
here is the styled component
const Btn = styled.button`
border-radius: ${props => props.theme.radius};
padding:5px 10px;
background-color:${ sub.saved ? 'green' : 'red'}
&:hover{
cursor:pointer;
}
`
and here is the component that it is being used inside of
const DisplaySubs = ({ queryResults, setSavedFunction, saved }) => {
return (
<>
<Total>{queryResults.length} results found </Total>
<UL>
{queryResults.map((sub, index) => {
return (
<LI onClick={() => {
window.open(`https://www.reddit.com/${sub.title}`)
}
}>
<H4>{sub.title}</H4>
<P>{sub.description}</P>
<Btn onClick={(e) => {
e.stopPropagation()
sub.saved = !sub.saved
}}>Save</Btn>
</LI>
)
})}
</UL >
</>
)
}
Instead of creating a component like this, you can write a simple functional component like
const Btn = ( props ) => {
<div style={{
borderRadius: {props.theme.radius},
padding:"5px 10px",
backgroundColor: {props.sub.saved ? 'green' : 'red'}
}}>
{props.children}
</div>
}
You can call this in your Main Container by
<Btn props={props}>Button<Btn>
For a javascript project:
// you "add" the interface to button, so you can access this custom properties
export const Btn = styled.button`
padding: 5px 10px;
background-color: ${(props) => (props.isSaved ? "green" : "red")};
&:hover {
cursor: pointer;
}
`;
For a typescript project, you can use interface to set custom properties, like:
// Create an interface defining custom properties to button
interface BtnProps {
isSaved: boolean;
}
// you "add" the interface to button, so you can access this custom properties
export const Btn = styled.button<BtnProps>`
padding: 5px 10px;
background-color: ${(props) => (props.isSaved ? "green" : "red")};
&:hover {
cursor: pointer;
}
`;
and your button:
// set isSaved property to button component,
// so you will have access to it on styled-components.
<Btn isSaved={sub.saved}
onClick={(e) => {
e.stopPropagation()
sub.saved = !sub.saved
}}>Save</Btn>

react.js antd modal wrong behaviour

So I have this big messy component, I will try to slim it down, however keep most of it since I am unsure at this point what could be cause.
The issue is, that the game works as expected. When it is time for the modal to render, it appears at the bottom left of the page, with no styling floating left. The functionality however works as expected, the buttons work and it displays the raw content.
import { Modal } from 'antd';
//rest of imports
const initialState = {
visible: false,
streak: 0,
score: 0,
turn: 0,
previousPicks: [],
result: { result: "", player: "", computer: "" }
};
class Game extends React.Component {
constructor(props) {
super(props);
this.turnLimit = 10;
this.state = initialState;
}
componentWillUnmount() {
this.setState(initialState)
}
updateScore = () => {
//handles score
}
updatePreviousPicks = () => {
//update game data
}
onClickHandler = async (choice) => {
//fetching data from backend
self.showModal();
}
getAIResult = () => {
//
}
showModal = () => {
if (this.state.turn === 10) {
this.setState({
visible: true,
});
}
}
handleOk = () => {
this.setState(initialState)
}
handleCancel = () => {
this.setState(initialState)
}
render() {
return (
<div>
<div>
<Modal
title="Basic Modal"
centered={true}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}></Modal>
</div>
<div className="container">
<div id="rockDiv" className={`choice`} onClick={() => this.onClickHandler("rock")}>
<Choices choice="rock"></Choices>
</div>
<div id="paperDiv" className={`choice`} onClick={() => this.onClickHandler("paper")}>
<Choices choice="paper"></Choices>
</div>
<div id="scissorsDiv" className={`choice`} onClick={() => this.onClickHandler("scissors")}>
<Choices choice="scissors"></Choices>
</div>
<Score score={this.state.score} bonus={this.state.streak} turn={this.state.turn} />
<div id="PlayerResult" className={this.state.result.result} >
{this.state.turn >= 1 ? <p>You</p> : <p></p>}
<Answer choice={`${this.state.result.player}`} />
</div>
<div id="AIResult" className={this.getAIResult()} >
{this.state.turn >= 1 ? <p>AI</p> : <p></p>}
<Answer choice={`${this.state.result.computer}`} />
</div>
</div>
</div>
)
}
}
export default Game
I have tried removing all CSS from the component, and still the modal does not show with the default antd design?
As I understand that current style you have doesn't like example of Antd.
Missing is you didn't import styles of Antd like this.
import { Modal, Button } from "antd";
import "antd/dist/antd.css";
Just need import style you will have what you need.
You can check my example here https://codesandbox.io/embed/8lr93mw8yj
<Modal
title="Basic Modal"
centered="true"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}></Modal>
You do not need to wrap the "true" in brackets here as you are not calling a variable.

React.js - Render Components with Different Styles based on props

So let's say I am creating a simple web layout, where I have a feedback message component above the MainContent component, as so:
class WebLayout extends Component {
render() {
<div>
<Header />
<FeedBackMessage
shouldRenderMessage={true}
typeMessage={"error"}
message={"Wrong input!"}
/>
<MainContent />
</div>
}
}
And let's assume that I have different types of messages such as error, warning, success.
Inside the FeedBackMessage, I may have something as so:
class FeedBackMessage extends Component {
renderMessage(){
const {shouldRenderMessage, typeMessage, message } = this.props;
if (shouldRenderMessage === true){
<div>
{message}
</div>
}
}
render(){
return (
<div>
{this.renderMessage().bind(this)}
</div>
)
}
}
I am stumped on how I can render FeedBackMessage styling based on typeMessage prop value.
For instance, if I pass typeMessage with 'error', I like to have the FeedbackMessage component with a red border styling. Or if I pass confirm, I'd like to render with green border.
This all is very dependent on your styling solution.
If you want to use inline styles it might look something like this:
class FeedBackMessage extends Component {
renderMessage(){
const {shouldRenderMessage, typeMessage, message } = this.props;
if (shouldRenderMessage === true){
<div>
{message}
</div>
}
}
render(){
const componentStyle = {
error: { border: "1px solid red" },
confirm: { border: "1px solid green" }
}[this.props.typeMessage];
return (
<div style={componentStyle}>
{this.renderMessage().bind(this)}
</div>
)
}
}
If you want to style with stylesheets, you can use something like classnames to toggle classes based on some logic and then add the class your component.
class FeedBackMessage extends Component {
renderMessage(){
const {shouldRenderMessage, typeMessage, message } = this.props;
if (shouldRenderMessage === true){
<div>
{message}
</div>
}
}
render(){
const componentClass = classNames('FeedBackMessage', {
"error": this.props.typeName === 'error',
"confirm": this.props.typeName === 'confirm'
});
return (
<div className={componentClass}>
{this.renderMessage().bind(this)}
</div>
)
}
}
And have a stylesheet like so:
.FeedBackMessage .error {
border: 1px solid red;
}
.FeedbackMessage .confirm {
border: 1px solid green;
}
The official documentation will help you. Please check here
render() {
let className = 'menu';
if (this.props.isActive) {
className += ' menu-active';
}
return <span className={className}>Menu</span>
}

Categories

Resources