Can I set onClickevents that are not yet defined? - javascript

I have a component that looks like this:
const ProductCarousel = React.createClass({
componentDidMount: function () {
this.flky = new Flickity('.carousel', flickityOptions)
//here
},
render: function () {
const item = this.props.item
return (
<div>
<div className='carousel'>
{item.get('images').map((url, i) => (
<img key={i.toString()} src={stripUrl(url)} width={520} />
))}
</div>
<div style={{marginTop: '20px', display: 'flex'}}>
{item.get('images').map((url, index) =>
<div style={{flexGrow: 1, margin: '0 1em 0 1em'}} className='hidden-xs' key={url}>
<Thumbnail src={stripUrl(url)} />
</div>
)}
</div>
</div>
)
}
})
the place where it says "here", I would like to define funcitons for <Thumbnail onClick />. Theese functions are methods for this.flky, so I can't create them before componentDidMount, but I would like to set them in the thumbnail and pass the thumbnails index to them. Is there something like a promise I could use?

If you put it in a function, it won't get executed until it gets clicked, and the methods should exist by then:
<Thumbnail src={stripUrl(url)} onClick={()=>this.flky.someMethod(index)} />

Related

React Context Provider with multiple values being updated in several places

I am new to React and Context and am trying to use a global context provider in React.
export const GlobalDataContext = React.createContext({ user: null, textSearchList:[] });
user is updated in the same file this way:
return (
<GlobalDataContext.Provider value={{ currUser: user, textSearchList: []}}>
{children}
</GlobalDataContext.Provider>
);
I want to use the same context provider to update the textSearchList for a search bar in another component like this:
<GlobalDataContext.Provider value={{textSearchList:this.state.splitSearchList}}>
<SearchBar
value={this.state.value}
onChange={(newValue) => {
this.setState({ value: newValue });
}}
onRequestSearch={() => {
this.setSplitList(this.state.value);
}}
style={{
margin: '0 auto',
maxWidth: 800
}}
/>
{children}
</GlobalDataContext.Provider>
The above code is calling this function:
setSplitList = (searchString) =>{
var splitString = this.state.value.split(" ");
this.state.splitSearchList= splitString;
}
I can see that this is not updating the global context because it does not cause a re-rendering at the consumer which is here:
<GlobalDataContext.Consumer >
{({textSearchList}) =>
<Query query={GET_POSTS} pollInterval={500}>
{({ data, loading }) => (
loading
? <Loading />
: <div>
{data.posts.filter(function(post){
console.log(`Filtering based on this: ${textSearchList}`);
return this.textContainsStrings(post.text, textSearchList)
}).map(post => (
<div key={post._id}>
<PostBox post={post} />
</div>
))}
</div>
)}
</Query>
}
</GlobalDataContext.Consumer>
Is this approach even possible? If so, then what might I be doing wrong?

ReactJS - Toggle state or property from mapped children components?

I'm trying to see if multiple mapped children components can be passed a function to change some of their props. The deal is that, I have a children rows that are representing shift periods, like this:
Which is actually this child component code:
class ShiftRow extends React.Component {
rawMarkup() {
var md = createRemarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
}
handleAvailableVal = () => {
props.onChange(event.target.checked);
}
render() {
return (
<tr className="shift" style={{ backgroundColor: this.props.availability ? 'green' : 'red', color: this.props.availability ? 'white' : 'black' }}>
{React.Children.map(this.props.children, (child, i) => {
// Ignore the first child
return child
})}
</tr>
);
}
}
The idea is, I want to be able to pass down initial values as the shifts are created in the parent ShiftList component, which are mapped out in the component like this:
if (this.props.data && this.props.data.length > 0) {
shiftNodes = this.props.data.map(shift => (
<ShiftRow key={shift.id} newInd={this.props.isNew} availability={shift.Available} ref={this.ShiftElement}>
<td>{shift.Description}</td>
<td style={{textAlign: 'center'}}>
<input
type="checkbox"
checked={shift.Available}
onChange={this.handleAvailableVal}
style={{ width: '1.5em', height: '1.5em'}}
/>
</td>
<td style={{ textAlign: 'center' }}>
<input readOnly
type="checkbox"
checked={shift.AllDay}
style={{ width: '1.5em', height: '1.5em' }}
/>
</td>
{this.determineShiftPeriod(shift)}
<td>{(shift.DayOfWeek && shift.ShiftType != 'Daily') ? shift.DayOfWeek : 'N/A (Daily)'}</td>
</ShiftRow>
));
}
Is there a way I can change the prop in a row by row fashion like this so that I can say, pass this full set of shift represented rows to save to a database? Let me know if I can clarify anything.
Example: I want to be able to click the "Available" checkbox and watch the props value of that row update for THAT row only, and then save the row as such with other rows.
Yes, the setState() function (or an anonymous function that calls it) could be passed down to child components so that they can modify parent state and by so doing, modify their props indirectly. Something like this:
class ParentComponent extends React.Component {
...
updateState = args => this.setState(args);
render() {
return (<ChildComponent key={shift.id} newInd={this.props.isNew} ... updateState={() => this.updateState()}>
</ChildComponent>);
}
}
Maybe you can do this:
<input
type="checkbox"
checked={shift.Available}
onChange={(evt) => this.handleAvailableVal({evt, shift})} // this way you'll have access to both shift and evt
style={{ width: '1.5em', height: '1.5em'}}
/>
Good Luck...
Use something like this to modify the children item's props. Hope this helps
function ANotherComponent(props) {
console.log("props", props);
return <div>Another component</div>;
}
function T(props) {
const newChildren = React.Children.map(props.children, (child, i) => {
// Ignore the first child
return React.cloneElement(
child,
{
newProps1: 1,
newProps2: 2,
},
child.props.children
);
});
return <div>{newChildren}</div>;
}
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<T>
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
</T>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<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.12.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So basically your handling of children will become something like this:
{React.Children.map(this.props.children, (child, i) => {
// Ignore the first child
return React.cloneElement(child, {
newProps1: 1,
newProps2: 2
}, child.props.children)
})}

How to map img src with the results of an async function in React?

I'm trying to use the map function to render images with Material UI, but I have to fetch the url from the API before displaying them, that is what getFoto() is doing, but It displays nothing
return(
<div className={classes.root}>
<GridList cellHeight={180} className={classes.gridList}>
<GridListTile key="Subheader" cols={2} style={{ height: 'auto' }}>
</GridListTile>
{data && data.map((tile) => (
<GridListTile key={tile.propertyId} >
<Link to={`/AreasRegistradas/${tile.propertyId}`}>
<img src={(async () => { // <======Here is the problem
await getFoto(tile.propertyId)
})()}
alt={tile.propertyName}
className={"MuiGridListTile-tile"}
/>
</Link>
<GridListTileBar
title={tile.propertyName}
subtitle={<span> {tile.address}</span>}
actionIcon={
<div>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("edit")}>
<EditIcon />
</IconButton>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("delete")}>
<DeleteForeverIcon />
</IconButton>
</div>
}
/>
</GridListTile>
))
}
</GridList>
</div>
)
However, if I do console.log (await getFoto(tile.propertyId)) it returns the correct urls that I need
//.....
<img src={(async () => {
console.log(await getFoto(tile.propertyId)) //this returns the values that I need in the console
})()}
//.....
What can be the problem here? I'm new in this async functions world please help.
Thanks!
Im using:
-"react": "^16.13.1"
When you set src={await getFoto(...)} you are setting the src attribute (a string obviously) to a Promise, which clearly won't work. Rather, somewhere in your component code, such as the componentDidMount event, you should fetch the image and set the result to some state variable which then becomes the src:
async componentDidMount() {
const photo = await getFoto(tile.propertyId);
this.setState({photo});
}
...
render() {
...
<img src={state.photo} />
But note, this is assuming that what is returned is photo URL. If it's the image itself, you'll need to use base64. Something like src={data:image/png;base64,${state.photo}}. It also assumes title is in scope in the componentDidMount method. If it isn't, you'll need to use the correct reference (e.g. this.tile, this.props.tile?).
Thanks to see sharper for the advice!!! Here's what I did:
First I created a new component called < Tile /> , added it inside my original map function and passed the item as props:
//...
{data && data.map((tile) => (
<div key={tile.propertyId}>
<Tile tile={tile} />
</div>
))
}
//...
Then inside my new < Tile /> component I added what I had originally inside my map function plus the async function inside a useEffect hook and store the fetched url in a useState hook:
function Tile(props){
const {tile} = props
const [imgSrc, setImgSrc] = useState(''); // here is the hook for the url
useEffect(() => {
const getFoto = async (propId) =>{
try{
const url = `....url/${propId}/images`
const response = await fetch(url, {
//authorization stuff
}
});
const responseData = await response.json()
setImgSrc(responseData.items[0].imageUrl) //setting the fetched url in a hook
}catch(error){
console.log(error)
}
}
getFoto(tile.propertyId);
}, []);
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
backgroundColor: theme.palette.background.paper,
},
gridList: {
width: 500,
height: 450,
},
icon: {
color: 'rgba(255, 255, 255, 0.54)',
},
}));
const classes = useStyles();
return(
<GridListTile className={"MuiGridListTile-tile"}>
<Link to={`/AreasRegistradas/${tile.propertyId}`}>
<img src={imgSrc}
alt={tile.propertyName}
className={"MuiGridListTile-tile"}
/>
</Link>
<GridListTileBar
title={tile.propertyName}
subtitle={<span> {tile.address}</span>}
actionIcon={
<div>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("edit")}>
<EditIcon />
</IconButton>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("delete")}>
<DeleteForeverIcon />
</IconButton>
</div>
}
/>
</GridListTile>
)
}
Thanks again!

How to map inside the TransitionMotion wrapper?

I'm trying to implement react-motion's TransitionMotion wrapper and made it to the home stretch but there's one more issue. In this example the interpolated -array consists of two elements (because chartConfigs.length is currently 2) and I've nested another map inside the first one. Everything else works fine except I obviously get two rows when I only want one. How to go around this in a clean way?
const getStyles = () => {
return chartConfigs.map(datum => ({
data: datum,
style: {
opacity: spring(1, { stiffness: 30})
},
key: datum.name
}))
}
const getDefaultStyles = () => {
return chartConfigs.map(datum =>({
data: datum,
style: {
opacity: 0
},
key: datum.name
}))
}
return (
<TransitionMotion
defaultStyles={getDefaultStyles()}
styles={getStyles()}
>
{(interpolated) => (
<div>
{interpolated.map((config) => (
<div key={config.key} style={{ ...config.style }}>
<div className='row' style={{ paddingTop: "30px" }}>
{chartConfigs.length > 1 &&
chartConfigs.map((chartConfig, i) => {
return (
<div
className={`col-lg-${columnsCount}`}
key={"chart-toggler" + i}
>
<div className='card m-b-30'>
<h4 className='card-title font-16 mt-0'>
{chartConfig.name}
</h4>
</div>
</div>
)
})}
</div>
</div>
))}
</div>
)}
</TransitionMotion>
)
EDIT:
Here's the new version of my solution but with the struggle of displaying elements next to each other on the row:
<div className='row' style={{ paddingTop: "30px" }}>
{chartConfigs.length > 1 ?
<TransitionMotion
defaultStyles={getDefaultStyles()}
styles={getStyles()}
willEnter={willEnter}
willLeave={willLeave}
>
{interpolated => (
<div id='container' style={{width: '100%', display: 'inline-block'}} >
{interpolated.map((config, i) => (
<div key={config.key} style={{ ...config.style }}>
{(selected = config.data.name === currentChartName)}
<div
className={`col-lg-${columnsCount}`}
key={"chart-toggler" + i}
>
<div
className={
selected
? "card m-b-30 text-white bg-primary"
: "card m-b-30"
}
style={{
width: '100%',
height: "calc(100% - 30px)",
}}
onClick={() => setCurrentChartName(config.data.name)}
>
<div className='card-body'>
<h4 className='card-title font-16 mt-0'>
{config.data.name}
</h4>
</div>
</div>
</div>
</div>
))}
</div>
)}
</TransitionMotion>
: null }
</div>
Additionally, I'm having trouble understanding how to use TransitionMotion when component unmounts. So basically the fade out effect when I render a different component on the page. Can I use the willLeave() function for this? Currently this is what it looks like but I don't know how to take it further:
const willLeave = () => ({
opacity: spring(0)
})
Thanks for your time and help!
TransitionMotion intentionally gives you more than the number of rows you’re currently rendering, since it remembers all the rows that are in the process of animating out.
So it depends on what you’re trying to achieve here. My hunch is that you’re probably misused chatConfigs in the inner level. You should be accessing config.data.name instead of chartConfig.name, no? You know what I mean?

Selecting the correct item using material's GridList

I have a react-mobx code that's working with Material-UI and looks something like this:
render() {
// some consts declarations
return (
<div>
<img src={selectedPhoto} alt={'image title'} />
<GridList className={classes.gridList} cols={2.5}>
{photos.map(tile => (
<GridListTile key={tile} onClick={this.selectPhoto}>
<img src={tile} alt={'image title'} />
<GridListTileBar
classes={{
root: classes.titleBar,
title: classes.title
}}
/>
</GridListTile>
))}
</GridList>
</div>
);
}
This shows a list of photos. I would like to change the selected photo when the user clicks one of the GridListTile. The key (tile) is actually an image url.
As seen in the code, I tried adding onClick={this.selectPhoto} when the selectPhoto function looks like this:
selectPhoto = (photo) => {
this.props.rootStore.selectPhoto(photo);
}
The argument photo that is sent to the function is not tile (the image url) as I would like to have. How can I pass this argument to the function correctly?
You could create an inline arrow function and pass along your tile to selectPhoto:
photos.map(tile => (
<GridListTile key={tile} onClick={() => this.selectPhoto(tile)}>
<img src={tile} alt={'image title'} />
<GridListTileBar
classes={{
root: classes.titleBar,
title: classes.title
}}
/>
</GridListTile>
))

Categories

Resources