I want to get dynamic content with React js modal I am using package react-responsive-modal. First I render all the post through map. Now I want when I click the individual post the modal should pop up and show me only that particular post's title and body. Now I can't figure out how to get an individual post in modal.
Is it possible to do that via third-party package or I have to make custom modal for that?
import React from 'react';
import Modal from 'react-responsive-modal';
import Axios from 'axios';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center'
};
class App extends React.Component {
state = {
posts: [],
open: false
};
componentDidMount() {
let url = 'https://jsonplaceholder.typicode.com/posts';
Axios.get(url).then(res => {
this.setState({
posts: res.data.slice(0, 10)
});
console.log(res.data.slice(0, 10));
});
}
onOpenModal = () => {
this.setState({ open: true });
};
onCloseModal = () => {
this.setState({ open: false });
};
renderPosts() {
return this.state.posts.map(post => {
return (
<div
key={post.id}
style={{ width: 400, height: 400, backgroundColor: 'orange' }}
onClick={this.onOpenModal}
>
<h1>{post.title}</h1>
</div>
);
});
}
renderModal(id, title, body) {
return this.state.posts.map(post => {
return (
<div key={post.id} style={{ width: 400, height: 400, backgroundColor: 'orange' }}>
<h1>{post.id}</h1>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
});
}
render() {
const { open } = this.state;
return (
<div style={styles}>
<h2>react-responsive-modal</h2>
<div>{this.renderPosts()}</div>
<Modal open={open} onClose={this.onCloseModal} center>
<h2>Simple centered modal</h2>
<div>{this.renderModal()}</div>
</Modal>
</div>
);
}
}
export default App;
You'll need to introduce some additional state on your App component that keeps track of the currently selected post. In your onOpenModal() method, you can update that state with the index of the post that was clicked. Then, in renderModal(), you can check what the selected post is and only render that post instead of mapping over the entire array.
class App extends React.Component {
state = {
posts: [],
open: false,
selectedPost: null // Keep track of the selected post
};
componentDidMount() {
let url = "https://jsonplaceholder.typicode.com/posts";
Axios.get(url).then(res => {
this.setState({
posts: res.data.slice(0, 10)
});
console.log(res.data.slice(0, 10));
});
}
onOpenModal = i => {
this.setState({
open: true,
selectedPost: i // When a post is clicked, mark it as selected
});
};
onCloseModal = () => {
this.setState({ open: false });
};
renderPosts = () => {
return this.state.posts.map((post, i) => {
return (
<div
key={post.id}
style={{ width: 400, height: 400, backgroundColor: "orange" }}
onClick={() => this.onOpenModal(i)} // Pass the id of the clicked post
>
<h1>{post.title}</h1>
</div>
);
});
}
renderModal = () => {
// Check to see if there's a selected post. If so, render it.
if (this.state.selectedPost !== null) {
const post = this.state.posts[this.state.selectedPost];
return (
<div
style={{ width: 400, height: 400, backgroundColor: "orange" }}
>
<h1>{post.id}</h1>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
}
render() {
const { open } = this.state;
return (
<div style={styles}>
<h2>react-responsive-modal</h2>
<div>{this.renderPosts()}</div>
<Modal open={open} onClose={this.onCloseModal} center>
<h2>Simple centered modal</h2>
<div>{this.renderModal()}</div>
</Modal>
</div>
);
}
}
In the post onClick function set the post id/index in state along with the open flag
Inside the modal render use the saved index/id to pass that post to the modal as a param/prop.
You dont need to map over all posts inside the modal.
Sample
onOpenModal = (index) => {
this.setState({ open: true, selectedPostIndex: index });
};
onCloseModal = () => {
this.setState({ open: false, selectedPostIndex: undefined })
}
renderPosts() {
return this.state.posts.map((post, index) => {
return (
<div key={post.id} onClick={() => this.onOpenModal(index)}>
<h1>{post.title}</h1>
</div>
)
})
}
render() {
....
<Modal open={open} onClose={this.onCloseModal} center>
<h2>Simple centered modal</h2>
<div>{this.renderModal(this.state.posts[this.state.selectedPostIndex])}</div>
</Modal>
....
}
renderModal(post) {
return (
<div key={post.id} style={{ width: 400, height: 400, backgroundColor: 'orange' }}>
<h1>{post.id}</h1>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
)
}
Using React Hooks
Create a modal with dynamic props like this
export default function Modal({
open,
setOpen,
onConfirm,
title,
description,
confirmText,
})
Then render the component.
i did it like this
const getModal = () => {
return (
<Modal open={open} setOpen={setOpen} title={title} description={description} confirmText={confirmText} onConfirm={confirmAction} />
)
}
and then when you want to display your dynamic modal
use a function like this
ConfirmAction can not be a function you should call the function inside that Modal according to that confirmation
const createModal = (title, description, confirmText, confirmAction) => {
setTitle(title);
setDescription(description);
setConfirmText(confirmText);
setConfirmAction(confirmAction);
setOpen(true);
}
Initialize your state with one array which will hold
state = {
posts: [],
open: false,
modalShow: [false,false,false,false,false,false,false,false,false,false] // this is 10 as you have only 10 posts
};
Now modify render posts
onOpenModal = (id) => {
const newModalShow = [...this.state.modalShow];
newModalShow[id] = true;
this.setState({ modalShow: newModalShow});
};
renderPosts() {
return this.state.posts.map((post,index) => {
return (
<Fragement>
<div
key={post.id}
style={{ width: 400, height: 400, backgroundColor: 'orange' }}
onClick={()=>this.onOpenModal(index)}
>
<h1>{post.title}</h1>
</div>
<Modal open={this.state.modalShow[index]} onClose={this.onCloseModal} center>
<h2>post.title</h2>
<div>{this.renderModal()}</div>
</Modal>
<Fragement>
);
});
}
Related
These are my codes for the snackbar and it wasn't working whenever I'll click the button. I wanted the snackbar to appear once I'll click the button "confirm". Almost all of the examples I have seen are in a functional component, so how can I make the Snackbar work as expected in a class component?
class name extends Component {
constructor() {
super();
this.state = { orders: [], open: false };
}
handleOpen = () => this.setState({ open: true });
handleClose = () => this.setState({ open: false });
columns = [
{
name: "Confirm",
options: {
customBodyRender: (value, tableMeta) => {
return (
<FormControlLabel
value={value}
control={
<Button>
confirm
</Button>
}
onClick={(e) => {
try {
//firestore codes
);
} catch (err) {
console.log(err);
}
this.handleOpen();
}}
/>
);
},
},
},
];
//code for options
//data fetching codes
render() {
const { open } = this.state;
return this.state.orders ? (
<div>
//muidatatable codes
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
open={open}
onClose={this.handleClose}
autoHideDuration={2000}
// other Snackbar props
>
Order Confirmed
</Snackbar>
</div>
) : (
<p>Loading...</p>
);
}
}
Ignoring a few syntax errors, you should check if there are any orders by using the length and not just by mere existence of the array as you have initialized an empty array this.state.orders will always result in true. Instead use this.state.orders.length > 0 ? to check if there are any orders or not.
Snackbar's child(ren) should be wrapped in components and not just strings directly, for using string directly you can use message prop of Snackbar.
Also, it's a standard to write class's name starting with an upper-case letter.
Here's a working code: Material UI Snackbar using classes
import React, { Component } from "react";
import { FormControlLabel, Button, Snackbar } from "#material-ui/core";
import MuiAlert from "#material-ui/lab/Alert";
export default class Name extends Component {
constructor() {
super();
this.state = { orders: [], open: false };
}
handleOpen = () => this.setState({ open: true });
handleClose = () => this.setState({ open: false });
handleClick = () => this.setState({ orders: [1], open: true });
columns = [
{
name: "Confirm",
options: {
customBodyRender: (value, tableMeta) => {
return (
<FormControlLabel
value={value}
control={<Button>confirm</Button>}
onClick={(e) => {
try {
//firestore codes
} catch (err) {
console.log(err);
}
this.handleOpen();
}}
/>
);
}
}
}
];
//code for options
//data fetching codes
render() {
const { open } = this.state;
return (
<>
<Button variant="outlined" onClick={this.handleClick}>
Open snackbar
</Button>
{this.state.orders.length > 0 ? (
<div>
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
open={open}
onClose={this.handleClose}
autoHideDuration={2000}
// other Snackbar props
>
{/* <span
style={{
background: "#000",
color: "#fff",
padding: "20px 5px",
width: "100%",
borderRadius: "5px"
}}
>
Order Confirmed
</span> */}
<MuiAlert
onClose={this.handleClose}
severity="success"
elevation={6}
variant="filled"
>
Success Message
</MuiAlert>
</Snackbar>
</div>
) : (
<p>loading...</p>
)}
</>
);
}
}
The following changes are made to make it work:
Removed Order Confirmed and used message prop of Snackbar
Passed values to orders array in constructor
Passed true in open variable.
Below is the working code for snack bar.
import React, { Component } from "react";
import Snackbar from "#material-ui/core/Snackbar";
class SnackBarSof extends Component {
constructor() {
super();
this.state = { orders: [1, 2], open: true };
}
handleOpen = () => this.setState({ open: true });
handleClose = () => this.setState({ open: false });
render() {
console.log(this.state.orders);
console.log(this.state);
const { open } = this.state;
return this.state.orders ? (
<div>
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
open={open}
onClose={this.handleClose}
message="order confirmed"
autoHideDuration={2000}
></Snackbar>
</div>
) : (
<p>Loading...</p>
);
}
}
export default SnackBarSof;
When i click on the document, the data successfully fetches from, however when i go to the same page and do a refresh then click on the document fetch is no longer happening ( i have inspect it in the browser ). What could be the reason for such a behavior?
this is my componentDidMount
#inject('selectedDocsStore')
#observer
class GeneralDocPresenter extends React.Component {
state = {
metaInfoDocs: [],
docs: [],
loading: false
};
componentDidMount() {
this.props.selectedDocsStore.clear();
this.props.selectedDocsStore.setViewDocId(0);
this.setState({loading: true});
this.props.fetchMetaDocs()
.then((r) => this.setState({metaInfoDocs: r.data, loading: false}))
.catch(err => {
this.setState({loading: false});
errorWithMessage('Could not load documents');
});
this.props.eventManager.on('viewDoc', (doc) => {
this.loadDocuments(doc.id);
})
}
render() {
return <Translation>
{(t) => {
if (this.state.loading) {
return (
<div style={{display: 'flex', justifyContent: 'center'}}>
<Spin size={"medium"}/>
</div>
)
}
if (this.state.metaInfoDocs.length === 0) {
return (
<div style={{display: 'flex', justifyContent: 'center'}}>
<NoDocumentsAlert><div dangerouslySetInnerHTML={{__html: t('noDocuments')}}/></NoDocumentsAlert>
</div>
)
}
return (
<DocViewWrapper docs={this.state.docs}
metaInfoDocs={this.state.metaInfoDocs.map(doc => {
return {...doc, type: this.props.type}
})}
eventManager={this.props.eventManager}
settings={this.props.settings}
childComponents={this.props.childComponents}
/>
)
}}
</Translation>
}
loadDocuments(id) {
this.props.loadDocument(id).then(r => {
this.setState({
docs: r.data
})
});
}
}
I have small portion of my app rendering a dropdown selection so users can view some brief information about library versions within the app.
The first dropdown is for the user to pick what they want view, for brevity I just added the relevant option for this question.
The second dropdown renders a fetched list of numbers pertaining to deviceIds. These deviceIds are fetched from a child class and then passed via props up to the parent class.
Now, In Semantic-UI-React, they have a dropdown module with a clearable prop which I am using on this dropdown populated with deviceIds. Below in VersionNumber class, you can see the props defined for the aforementioned dropdown.
The clearable prop lets the user remove the selected input from the dropdown. However, in my app when the input is removed it calls the onChange function which I do not want. I tried passing a custom change function to the clearable prop where I'm attempting to reset the state variable deviceId back to a undefined value if it's "cleared" by the user.
Is this possible? I'm pretty sure I'm taking the correct approach, but may be having an issue where the onChange function is firing before the clearable passed function.
I have a codesandbox that reproduces this problem here.
import React, { Component } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import {
Dropdown,
Form,
Divider,
Input,
Message,
Loader
} from "semantic-ui-react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewSelection: "",
deviceId: "",
versionNum: "",
isLoading: true
};
}
handleViewSelection = (e, { value }) => {
this.setState({ viewSelection: value }, () => {
console.log("view election --> ", this.state.viewSelection);
});
};
onDeviceIdChange = async (e, { value }) => {
console.log("device id value -->", value);
this.setState({ deviceId: value }, () => {
console.log(
"deviceId value updated to state on select -->",
this.state.deviceId
);
});
try {
const { data } = await axios.get(`/deviceId/${value}`);
const version = data.data.versionNumber;
this.setState({ versionNum: version.version, isLoading: false }, () => {
console.log("callback from admin version fetch", this.state.versionNum);
});
} catch (error) {
console.error(Error(`Error getting the selected deviceId ${error.message}`));
}
};
handleClear = () => {
this.setState({ deviceId: "" }, () => {
console.log("handleClear function fired");
});
};
render() {
const { viewSelection, deviceId, versionNum, isLoading } = this.state;
return (
<div
style={{ display: "flex", minHeight: "100vh", flexDirection: "column" }}
>
<div style={{ flex: 1 }}>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
flex: "1"
}}
>
<div style={{ width: "1000px" }}>
<Divider style={{ marginTop: "7em" }} horizontal>
View Data
</Divider>
<Message style={{ marginTop: "2em" }} info>
<Message.Header>
Want to see more specific information? Log in
here.
</Message.Header>
</Message>
<Form.Field style={{ marginTop: "2em" }}>
<label>Select data to view</label>
<Dropdown
scrolling
placeholder="Pick an option"
value={viewSelection}
fluid
selection
multiple={false}
search
options={viewOptions}
onChange={this.handleViewSelection}
/>
</Form.Field>
{this.state.viewSelection && (
<div style={{ marginTop: "2em" }}>
{viewSelection && viewSelection === "versionNumber" ? (
<>
<VersionNumber
onDeviceIdChange={this.onDeviceIdChange}
deviceId={deviceId}
handleClear={this.handleClear}
/>
<div>
<label style={{ display: "block", marginTop: "2em" }}>
Version Number
</label>
{isLoading ? (
<Loader active inline="centered" />
) : (
<Input value={versionNum} fluid readOnly />
)}
</div>
</>
) : null}
</div>
)}
</div>
</div>
</div>
</div>
);
}
}
class VersionNumber extends Component {
constructor(props) {
super(props);
this.state = {
deviceId: "",
deviceIds: []
};
}
async componentDidMount() {
await this.getDeviceIds();
}
getDeviceIds = async () => {
try {
const { data } = await axios.get("/deviceIds");
console.log("device IDs --> ", data);
const deviceIds = data.data.deviceIds;
this.setState(
{
deviceIds: deviceIds
},
() => {
console.log(
"setState callback with updatesd deviceIds state -->",
this.state.deviceIds
);
}
);
} catch (error) {
console.error(Error(`Error getting deviceIds: ${error.message}`));
}
};
render() {
const { onDeviceIdChange, deviceId, handleClear } = this.props;
const { deviceIds } = this.state;
return (
<Form.Field required>
<label style={{ display: "block" }}>Device Id</label>
<Dropdown
id="deviceId"
value={deviceId}
onChange={onDeviceIdChange}
selection
fluid
placeholder="Please select an device id"
clearable={handleClear}
options={deviceIds.map(id => {
return {
text: id.id,
value: id.id,
key: id.id
};
})}
style={{ marginTop: ".33", marginBottom: "2em" }}
/>
</Form.Field>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
clearable prop expects a boolean value. You can change your onChange callback to consider the scenario where the value is going to be passed in as null / undefined
The function can be modified as below to reset the deviceId and prevent the API call
onDeviceIdChange = async (e, { value }) => {
// VALUE WILL BE PASSED IN AS "" WHEN THE CLEAR OPTION IS CLICKED
console.log("device id value -->", value);
this.setState({ deviceId: value }, () => {
console.log(
"deviceId value updated to state on select -->",
this.state.deviceId
);
});
// THIS IS THE CONDITION TO CATCH THE CLEARABLE INVOCATION
if(!value) {
this.handleClear();
return;
}
...
};
if(!value) return; Think of this condition as the execution that the clearable option will invoke. You can invoke anything before the return to ensure that your application handles the clear option in the way you expect it to.
I have this situation where I want to transfer userid to an array which is defined in State.
I want to do it when I click on the checkbox to select it, and I want to remove the userid from the array when I deselect the checkbox
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
AsyncStorage
} from "react-native";
import axios from 'axios';
import { Button, Container, Content, Header, Body, Left, Right, Title } from 'native-base';
import Icon from 'react-native-vector-icons/Ionicons';
import { List, ListItem, SearchBar, CheckBox } from "react-native-elements";
// const itemId = this.props.navigation.getParam('itemId', 'NO-ID');
// const otherParam = this.props.navigation.getParam('otherParam', 'some default value');
class TeacherSubjectSingle extends Component{
static navigationOptions = {
header : null
}
// static navigationOptions = {
// headerStyle: {
// backgroundColor: '#8E44AD',
// },
// headerTintColor: '#fff',
// }
state = {
class_id: null,
userid: null,
usertype: null,
student_list: [],
faq : [],
checked: [],
}
componentWillMount = async () => {
const {class_id, student_list, userid, usertype} = this.props.navigation.state.params;
await this.setState({
class_id : class_id,
student_list : student_list,
userid : userid,
usertype : usertype,
})
console.log(this.state.class_id)
var result = student_list.filter(function( obj ) {
return obj.class_section_name == class_id;
});
this.setState({
student_list: result[0]
})
}
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "100%",
backgroundColor: "#CED0CE",
}}
/>
);
};
checkItem = (item) => {
const { checked } = this.state;
console.log(item)
if (!checked.includes(item)) {
this.setState({ checked: [...checked, item] });
} else {
this.setState({ checked: checked.filter(a => a !== item) });
}
console.log(checked)
};
render(){
return (
<Container>
<Header style={{ backgroundColor: "#8E44AD" }}>
<Left>
<Button transparent onPress={()=> this.props.navigation.navigate('ClassTeacher')}>
<Icon name="ios-arrow-dropleft" size={24} color='white' />
</Button>
</Left>
<Body>
<Title style={{ color: "white" }}>{this.state.class_id}</Title>
</Body>
<Right>
{this.state.checked.length !== 0 ? <Button transparent onPress={()=> this.props.navigation.navigate('ClassTeacher')}>
<Text>Start Chat</Text>
</Button> : null}
</Right>
</Header>
<View style={{flex: 1, backgroundColor: '#fff'}}>
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.student_list.students}
extraData={this.state.checked}
renderItem={({ item }) => (
<ListItem
// roundAvatar
title={<CheckBox
title={item.name}
checkedColor='#8E44AD'
onPress={() => this.checkItem(item.userid)}
checked={this.state.checked.includes(item.userid)}
/>}
// subtitle={item.email}
// avatar={{ uri: item.picture.thumbnail }}
//containerStyle={{ borderBottomWidth: 0 }}
onPress={()=>this.props.navigation.navigate('IndividualChat', {
rc_id: item.userid,
userid: this.state.userid,
usertype: this.state.usertype,
subject_name: this.state.student_list.subject_name
})}
/>
)}
keyExtractor={item => item.userid}
ItemSeparatorComponent={this.renderSeparator}
/>
</List>
</View>
</Container>
);
}
}
export default TeacherSubjectSingle;
const styles = StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center'
}
});
this is the code for the same, I have created a function checkItem()for the same and it is working, the only problem is, when I click on the first item, it will output the blank array, I select the second item and it will return the array with first item and so on. Please help me resolve it, thanks in advance
Its because you are printing this.state.checked value just after the setState, setState is async, it will not immediately update the state value.
You can use setState callback method to check the updated state values.
Write it like this:
checkItem = (item) => {
const { checked } = this.state;
let newArr = [];
if (!checked.includes(item)) {
newArr = [...checked, item];
} else {
newArr = checked.filter(a => a !== item);
}
this.setState({ checked: newArr }, () => console.log('updated state', newArr))
};
Check this answer for more details about setState:
Why calling setState method doesn't mutate the state immediately?
Try below code:
checkItem = (e) => {
let alreadyOn = this.state.checked; //If already there in state
if (e.target.checked) {
alreadyOn.push(e.target.name); //push the checked value
} else {
//will remove if already checked
_.remove(alreadyOn, obj => {
return obj == e.target.name;
});
}
console.log(alreadyOn)
this.setState({checked: alreadyOn});
}
Like this, with an event listener and the .push method of an array.
var checkboxes = document.querySelectorAll('input[type=checkbox]');
var myArray = [];
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].addEventListener("click", function(e) {
myArray.push(e.target.value);
console.log(myArray);
});
};
<div>
<input type="checkbox" name="feature" value="scales" />
<label >Scales</label>
</div>
<div>
<input type="checkbox" name="feature" value="horns" />
<label >Horns</label>
</div>
<div>
<input type="checkbox" name="feature" value="claws" />
<label >Claws</label>
</div>
I want to use React.js to build a single page application and I want to create a list in a material-ui drawer. I want to add an element into an array every time I press a button but I don't how to write this function.
Here is my buttom:
<RaisedButton
label="Next"
primary={true}
onClick={this.onNext}
/>
Here is onNext function:
onNext = (event) => {
const current = this.state.controlledDate;
const date = current.add(1, 'days');
this.setState({
controlledDate: date
});
this.getImage(moment(date));
}
And this is the code I want to add into onNext function:
menuItems.push(<MenuItem onClick={this.handleClose}>{this.state.image.date}</MenuItem>);
This is a sample code that adds drawer menu items using state
const { RaisedButton, MuiThemeProvider, Drawer, getMuiTheme, MenuItem } = MaterialUI;
class Sample extends React.Component {
state = {
open: false,
items: [],
}
handleClose = () => {
this.setState({ open: false });
}
handleOpen = () => {
this.setState({ open: true })
}
onNext = () => {
this.setState(state => {
return Object.assign({}, state, {
items: state.items.concat([
{
// add any other button props here (date, image, etc.)
text: `Item ${state.items.length + 1}`
}
]),
});
})
}
render() {
return (
<div>
<Drawer
openSecondary={true}
width={200}
open={this.state.open}
>
{this.state.items.map(item => (
<MenuItem onClick={this.handleClose}>{item.text}</MenuItem>
))}
</Drawer>
<RaisedButton
label="Next"
primary={true}
style={{ margin: 12 }}
onClick={this.onNext} />
<RaisedButton
label="Open Drawer"
primary={true}
style={{ margin: 12 }}
onClick={this.handleOpen} />
</div>
);
}
}
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Sample />
</MuiThemeProvider>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
Try it here: https://jsfiddle.net/jprogd/eq533rzL/
Hope it should give you an idea how to go further