How to update react context in child elements - javascript

Hi i have problem with context in react. I need to update context data after i fetch some data in child component:
Here is my app code
export default class App extends Component {
constructor(props) {
super(props);
this.state = { loggedInUser: null };
this.onLogin = this.onLogin.bind(this);
this.onLogout = this.onLogout.bind(this);
}
onLogin(value) {
this.setState({ loggedInUser: value });
}
onLogout(value) {
this.setState({ loggedInUser: null });
}
render() {
let content = null;
if (this.state.loggedInUser == null) {
content = <div> <LoginScreen onLogin={this.onLogin} /> </div>
}
else {
content = <div> <Application onLogout={this.onLogout} /> </div>
}
return (
<MuiThemeProvider theme={ApplicationTheme}>
<Context.Provider value={{
user:this.state.loggedInUser,
updateUser: (user) =>{this.user=user},
company: null,
updateCompany:(company) => {this.company=company},
role: null,
updateRole:(role) => {this.role = role},
}}>
{content}
</Context.Provider>
</MuiThemeProvider>
);
}
From this component i go to login screen and AFTER that in component where i should select role. In this component i need update role data in context. How to do this please?
export class RoleSelector extends Component {
static displayName = RoleSelector.name;
static contextType = Context;
render() {
console.log(this.context);
this.context.updateCompany(1);
console.log(this.context);
console.log("Afterext)");
let companies = this.context.userCompanies.map(u =>
<Grid item lg={6} xs={12} key={u.company.id}>
<UserCompany userCompany={u}> </UserCompany>
</Grid>
);
return (
<Container className="roleSelector">
<Context.Consumer>
<Grid container direction="column" justify="center" alignItems="center" alignContent="center"
spacing={1}>
<Box my={2}>
<Typography variant="h1" align="center">Vyberte typ uživateľa</Typography>
</Box>
<Grid container className="company" justify={"center"}>
{companies}
</Grid>
</Grid>
</Context.Consumer>
</Container>
);
}
}
I tried to pass method to update data in context but it return unchanged context. (Data has same values after using updateCompany in my example)
Thx for help

Assuming that you have const Context = React.createContext(), I'll do something like this:
will add company and role to App state
will add methods for updating company and role and bind them in App constructor.
pass all 4 items to Context.Provider value. It would look like something like this:
value={{
user: this.state.loggedInUser,
updateUser: this.updateUser,
company: this.state.company,
updateCompany: this.updateCompany,
role: this.state.role,
updateRole: this.updateRole,
}}
in the RoleSelector component, you'll have something like this:
<Context.Consumer>
{({user, updateUser, company, updateCompany, role, updateRole}) => (...your jsx)}
</Context.Consumer>

Related

saga fetch data after component rendered

hi sorry for my bad english.i am using react and redux.i dispatch getTags action in layout component.problem is after getData action called,getDataSuccess action called after my components rendered.so my data is null.
how i can be sure that data is fetched and render my components?
layout:
function DashboardLayout({
children,
showSideBar,
backgroundColor,
getTagsFromServer,
getCategoriesFromServer,
}) {
getTagsFromServer();
getCategoriesFromServer();
return (
<StyledDashbordLayout>
<NavBar />
<SideBarDrawer />
<Grid container className="container">
{showSideBar && (
<Grid item className="sidebar-section">
<SideBar />
</Grid>
)}
<Grid item className="content">
{children}
</Grid>
</Grid>
</StyledDashbordLayout>
);
}
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired,
showSideBar: PropTypes.bool.isRequired,
backgroundColor: PropTypes.string,
getTagsFromServer: PropTypes.func,
getCategoriesFromServer: PropTypes.func,
};
function mapDispatchToProps(dispatch) {
return {
dispatch,
getTagsFromServer: () => dispatch(getTags()),
getCategoriesFromServer: () => dispatch(getCategories()),
};
}
const withConnect = connect(
null,
mapDispatchToProps,
);
export default compose(withConnect)(DashboardLayout);
saga:
import { call, put, takeLatest } from 'redux-saga/effects';
function* uploadVideo({ file }) {
try {
const { data } = yield call(uploadVideoApi, { file });
yield put(uploadFileSuccess(data));
} catch (err) {
yield put(uploadFileFail(err));
}
}
function* getTags() {
const { data } = yield call(getTagsApi);
console.log(data, 'app saga');
yield put(getTagsSuccess(data));
}
function* getCategories() {
const { data } = yield call(getTCategoriesApi);
yield put(getCategoriesSuccess(data));
}
// Individual exports for testing
export default function* appSaga() {
yield takeLatest(UPLOAD_VIDEO, uploadVideo);
yield takeLatest(GET_TAGS, getTags);
yield takeLatest(GET_CATEGORIES, getCategories);
}
this is my select box component which gets null data from store:
import React, { useState } from 'react';
function UploadFileInfo({ tags, categories }) {
return (
<Paper square className={classes.paper}>
<Tabs
onChange={handleChange}
aria-label="disabled tabs example"
classes={{ indicator: classes.indicator, root: classes.root }}
value={tab}
>
<Tab
label="مشخصات ویدیو"
classes={{
selected: classes.selected,
}}
/>
<Tab
label="تنظیمات پیشرفته"
classes={{
selected: classes.selected,
}}
/>
</Tabs>
{tab === 0 && (
<Grid container className={classes.info}>
<Grid item xs={12} sm={6} className={classes.formControl}>
<label htmlFor="title" className={classes.label}>
عنوان ویدیو
</label>
<input
id="title"
type="text"
className={classes.input}
onChange={e => setValue('title', e.target.value)}
defaultValue={data.title}
/>
</Grid>
<Grid item xs={12} sm={6} className={classes.formControl}>
<SelectBox
onChange={e => setValue('category', e.target.value)}
value={data.category}
label="دسته بندی"
options={converItems(categories)}
/>
</Grid>
<Grid item xs={12} className={classes.textAreaWrapper}>
<label htmlFor="info" className={classes.label}>
توضیحات
</label>
<TextField
id="info"
multiline
rows={4}
defaultValue={data.info}
variant="outlined"
classes={{ root: classes.textArea }}
onChange={e => setValue('info', e.target.value)}
/>
</Grid>
<Grid item xs={12} sm={6} className={classes.formControl}>
<SelectBox
onChange={e => {
if (e.target.value.length > 5) {
console.log('hi');
setError(
'tags',
'تعداد تگ ها نمی تواند بیشتر از پنج عدد باشد',
);
return;
}
setValue('tags', e.target.value);
}}
value={data.tags}
label="تگ ها"
options={converItems(tags)}
multiple
onDelete={id => deleteTagHandler(id)}
error={errors.tags}
/>
</Grid>
</Grid>
)}
{tab === 1 && 'دومی'}
<Dump data={data} />
</Paper>
);
}
const mapStateToProps = createStructuredSelector({
tags: makeSelectTags(),
categories: makeSelectCategories(),
});
const withConnect = connect(
mapStateToProps,
null,
);
export default compose(withConnect)(UploadFileInfo);
Question Summary
If I understand your question correctly, you are asking how to guard the passing of options, options={converItems(tags)}, in the SelectBox in UploadFileInfo against null or undefined values when the data hasn't been fetch yet.
Solutions
There are a few options for either guarding against null or undefined values.
Easiest is to provide a default fallback value for tags. Here I am making an assumption the tags are an array, but they can be anything, so please adjust to match your needs.
Inline when passed options={converItems(tags || []) or options={converItems(tags ?? [])
In the function signature function UploadFileInfo({ tags = [], categories })
As part of a fallback return value in makeSelectTags
Another common pattern is conditional rendering where null may be the initial redux state value, so you simply wait until it is not null to render your UI.
Early return null if no tags
function UploadFileInfo({ tags, categories }) {
if (!tags) return null;
return (
<Paper square className={classes.paper}>
...
Conditional render SelectBox
{tags ? (
<SelectBox
...
/>
) : null
}
Side Note about fetching data calls in DashboardLayout
When you place function invocations directly in the function body they will be invoked any time react decides to "render" the component to do any DOM diffing, etc..., pretty much any time DashboardLayout renders the data fetches are made, which could have unintended effects. For this reason, react functional component bodies are supposed to be pure functions without side effects. Place any data fetching calls in an effect hook that is called only once when the component mounts (or appropriate dependency if it needs to be called under other specific conditions).
useEffect(() => {
getTagsFromServer();
getCategoriesFromServer();
}, []);
Use your functions to call the API inside React.useEffect.
All your API calls should be inside the useEffect hook.
For more on useEffect, read this
function DashboardLayout({
children,
showSideBar,
backgroundColor,
getTagsFromServer,
getCategoriesFromServer,
}) {
React.useEffect(() => {
getTagsFromServer();
getCategoriesFromServer();
}, []);
return (
<StyledDashbordLayout>
<NavBar />
<SideBarDrawer />
<Grid container className="container">
{showSideBar && (
<Grid item className="sidebar-section">
<SideBar />
</Grid>
)}
<Grid item className="content">
{children}
</Grid>
</Grid>
</StyledDashbordLayout>
);
}

passing image from list(stateless) component to display(stateful) component --react

//ImagePlayer component
class ImagePlayer extends Component {
constructor(props) {
super(props)
this.state = {
image: [],
selectedImage: '',
}
this.handleImageSelection = this.handleImageSelection.bind(this);
}
handleImageSelection(source){
this.setState({ImageList : source})
}
render() {
return (
<Grid container spacing={3}>
<Grid item xs={8}>
<Paper>
{/* this is the larger div where I want to render the image clicked on the list */}
<ImageList handleImageSelection={this.handleImageSelection}/>
</Paper>
</Grid>
<Grid item xs={4}>
<Paper>
<ImageList />
</Paper>
</Grid>
</Grid>
);
}
}
//ImageList component
onst ImageList = (handleImageSelection) =>{
handleImageSelection=(image)=>{
console.log(image);
}
return(
images.map((image, id) =>
<List>
<ListItem key={id} >
<div>
<ListItemAvatar>
{<img src= {require(`../assets/${image.name}.jpeg`)} alt="thumbnail" onClick={()=>handleImageSelection(require(`../assets/${image.name}.jpeg`))}/>}
</ListItemAvatar>
</div>
<div >)
How to render the image from List component to Class component in React? My list component is list of images and that should appear enlarged in class component when I click on any image on the list.
I first defined the state: this.state ={ imageSelected: ''}
then, setState for the same.
Also passed handleImageSelection as a function in list component, but it says
'handleImageSelection' is not a function
onClick={()=> props.handleImageSelection()} //errr: not a function
If both your list and display component are wrapped by common parent, you may lift necessary state (e.g. chosen image id) as follows:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const imageList = [
{id:0, name: 'circle', imgSrc: `data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI1MCIvPjwvc3ZnPg==`},
{id:1, name: 'triangle', imgSrc: `data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNTAsMCBMMTAwLDEwMCBMMCwxMDAgeiIvPjwvc3ZnPg==`},
{id:2, name: 'square', imgSrc: `data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCwwIGgxMDAgdjEwMCBoLTEwMCB6Ii8+PC9zdmc+`},
]
const List = ({images, onSelect}) => (
<ul>
{
images.map(({imgSrc, name, id}) => (
<li key={id} onClick={() => onSelect(id)}>
<img className="thumbnail" src={imgSrc} alt={name}/>
</li>
))
}
</ul>
)
class Display extends Component {
render (){
const {imgSrc,name} = this.props.image
return (
<img className="fullsize" src={imgSrc} alt={name} />
)
}
}
class App extends Component {
state = {
chosenImg: null
}
images = imageList
onSelect = _id => this.setState({
chosenImg: this.images.find(({id}) => id == _id)
})
render(){
return (
<div>
<List images={this.images} onSelect={this.onSelect} />
{ this.state.chosenImg && <Display image={this.state.chosenImg} />}
</div>
)
}
}
render (
<App />,
rootNode
)
.thumbnail {
max-width: 50px;
cursor: pointer;
}
.fullsize {
width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

How can I set a component initial state with another state before render?

I am having a json file with data
{
data = [ {name: 'abc", label: "ABC",type: "hetero", path:"/assets/data/source1.jpg" },
{name: 'pqr", label: "PQR",type: "homo", path:"/assets/data/source2.jpg" },
{name: 'xyz", label: "XYZ",type: "hetero", path:"/assets/data/source3.jpg" },
]
}
This is the Parent class which is displaying the cards. On click of cards, It will navigate to another component which is DataForm.
class MainDataCard extends React.Component{
onClickForm = card => {
const {action} = this.props; // action value can be add/update. Currently action = "add"
this.props.history.push(`/user/${action}/${card.label}`);
};
render() {
{data.map((card, index) => {
return (
<Card
key={index}
className="cardStyle"
onClick={() => {
this.onClickForm(card);
}}
>
<CardContent className="cardContent">
<Grid container>
<Grid item md={12}>
<img src={card.path} alt="card" />
</Grid>
<Grid item md={12}>
<Typography color="textSecondary" gutterBottom>
{card.name}
</Typography>
</Grid>
</Grid>
</CardContent>
</Card>
)
}
}
In DataForm component, I am having a state for the form fields as:
state = {
dataForm: {
name: "",
type: this.tempData.type,
userName: "",
password: ""
},
tempData: {}
}
componentDidiMount(){
const {label} = this.props.match.params;
let temp ={};
{data.map((card) => {
if(data.label === label) {
temp =data;
}
})};
this.setState({ tempData : temp })
}
In DataForm , I want type field to have a default value as "hetero" or "homo". But there its showing an error as :
CANNOT READ PROPERTY TYPE OF UNDEFINED.
How can I get the default value in type field??
Thanks in advance.
Your component probably do not have access to this.tempData.type. Please provide full code of your components.

How to transfer data from react child component to parent component

I need to figure out how to transfer the data i receive in a child component to the one in parent component. I need to set the console log i receive in the child to transfer to the parent component state.
Currently I have:
Child Comp:
import Picker from 'react-giphy-component'
import React, { Component, PropTypes } from 'react'
class AddGif extends Component {
log (gif) {
console.log(gif.original.mp4)
}
returnedGifObject = gif ;
render () {
return (
<div>
{/* <button></button> */}
<Picker onSelected={this.log.bind(this)} />
</div>
)
}
}
export default AddGif;
parent element
class PostBox extends Component {
constructor(props){
super(props)
this.state = {
title: null,
postBody: null,
giphyUrl: null,
displayGifPicker: false
}
}
getGifState = (selectedUrl) => {
this.setState({ giphyUrl: selectedUrl})
}
render () {
const {title, postBody} = this.state
const displayGifPicker = this.state.displayGifPicker
return (
<Grid item xl={8}>
{/* <Card className={classes.card} style={mt4}> */}
<Card style={mt4}>
<CardContent >
<PostInput onChange={this.handleInputChange} onSubmit={this.handleSubmit}/>
{displayGifPicker ? (<AddGif selectedGif = {this.getGifState} />) : (<Button size="small" onClick={this.displayGifPicker} ><button>Add Gif</button></Button>)}
<CardActions>
{/* <Button size="small">Submit VH5</Button> */}
</CardActions>
</CardContent>
</Card>
</Grid>
)
}
}
You passed the function prop to children component. Then In Children component just call it :
log = (gif) => {
const { selectedGif } = this.props
console.log(gif.original.mp4)
selectedGif(gif.original.mp4)
}

React: How to add an infinite an infinite amount of components with onClick?

I am new to React and am building a form. The form consists of a collection made up of several Components. One of the Components is textfield.
I want to create a button that simply adds an infinite amount of the same textfield component on click. I'm stumped on how to do this and cannot find any information online.
My code thus far is:
constructor(props) {
super(props);
this.handleClickDestination = this.handleClickDestination.bind(this);
}
static defaultProps = {
}
static propTypes = {
}
handleClickDestination() {
console.log('click');
}
render() {
const {
className
} = this.props;
return (
<div className={className}>
<DestinationSearchInput />
<Grid item margin="normal">
</Grid>
<Grid container spacing={12} alignItems="flex-end">
<Button onClick={this.handleClickDestination} color="primary">
Add another destination
</Button>
</Grid>
<div>
// extra <DestinationSearchInput /> components to go here
</div>
<DatePicker />
<TravellerCounter />
</div>
);
}
}
Would anyone be able to point me in the right direction on how to achieve this?
You can use states to let your component know how many destination fields are to be rendered
In this case I have just used an array of dummy items to render the fields for that many times.
constructor() {
this.state = {
items: ['dummy']
}
}
handleClickDestination() {
this.setState({items: this.state.items.concat('dummy') })
}
render() {
const {
className
} = this.props;
return (
<div className={className}>
<DestinationSearchInput />
<Grid item margin="normal">
</Grid>
<Grid container spacing={12} alignItems="flex-end">
<Button onClick={this.handleClickDestination} color="primary">
Add another destination
</Button>
</Grid>
<div>
// use this block here
{
this.state.items.map((_, index) =>
<DestinationSearchInput
key={index}
/>
)
}
// use this block here
</div>
<DatePicker />
<TravellerCounter />
</div>
);
}
or simply use a number and then render using it
// in constructor
this.state = {items: 0}
// inside handle click
this.setState({items: this.state.items + 1})
// in render
new Array(this.state.items).fill(0).map((_,index) =>
<DestinationSearchInput
key={index}
/>
)
I think you can do like:
onClick(){this.setState({textFields: [...text}); onClick(); }
On Render method you can use:
const TextComponent = this.state.textFields.map(item => . . . .
Your component here)
You can put random position for you component as well.

Categories

Resources