Failing to refactor a React render prop component into 2 components - reactjs

I'm trying to refactor the following component so I can separate the outer / inner components.
// outer component
<Draggable>
{(provided) => (
<div
className={`${styles.listWrapper} ${scrollbarClassName}`}
ref={provided.innerRef}
{...provided.draggableProps}
>
// This is the inner component I'd like to extract:
<div className={`${styles.column} ${scrollbarClassName}`}>
// However I'm using the provided props here:
<div {...provided.dragHandleProps}>
<ColumnHeader {...columnHeaderProps} />
</div>
</div>
</div>
)}
</Draggable>
I'd like to be able to use it this way or similar, but I am not sure how to pass the props through children so I can use the provided prop in my inner component inside DraggableColumn:
const Draggable = <DraggableContainer>
{(provided) => (
<div
className={`${styles.listWrapper} ${scrollbarClassName}`}
ref={provided.innerRef}
{...provided.draggableProps}
>
// How to pass provided to children and then use it in DraggableColumn below
{props.children}
</div>
)}
</DraggableContainer>
const DraggableColumn = () => <DraggableContainer>
<div className={`${styles.column} ${scrollbarClassName}`}>
// How to get access to provided?
<div {...provided.dragHandleProps}>
<ColumnHeader {...columnHeaderProps} />
</div>
</div>
</DraggableContainer>

I think what you're looking for is something like the following:
const DraggableContainer = props => {
const provided = {
foo: "bar",
baz: () => console.log("hello world")
};
return props.children(provided);
};
const Draggable = props => {
return (
<DraggableContainer>
{provided => props.children(provided)}
</DraggableContainer>
);
};
const DraggableColumn = props => {
console.log('DraggableColumn.props', props)
return (
<div>
<h2>DraggableColumn</h2>
<p>provided: {JSON.stringify(props.provided)}</p>
</div>
);
};
const App = () => {
return (
<Draggable>{provided => <DraggableColumn provided={provided} />}</Draggable>
);
}
ReactDOM.render(
<App />,
document.getElementById("app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Basically, what you're doing here is recreating the same functionality that DraggableContainer provides by utilizing the children prop pattern:
reactjs.org/docs/render-props.html#using-props-other-than-render
One caveat is that Draggable will always be expecting props.children to be a function, meaning that the following will work:
<Draggable>
{(provided) => {
return <DraggableColumn provided={provided} />;
}}
</Draggable>
But this will not because props.children is not a function, unlike the example above:
<Draggable>
<p>Hello World</p>
</Draggable>
Hope this helps.

To pass a prop you need to declare it inside of the <>
For example
<Draggable provided={this.state.provided}>
<div>
Some Stuff
</div>
</Draggable>
Then you can do this.props.provided inside the draggable component.

Related

How to change a style of an HTML element in React?

I have two React components
class App extends React.Component {
render() {
return (
<div id="appWrapper">
<ConfigureWindow />
<button id="configureClocksButton">Configure clocks</button>
<section id="clocksHere"></section>
</div>
);
}
}
const ConfigureWindow = () => (
<div id="configureWindowWrapper">
<div id="configureWindow">
<section id="addCitySection">TODO: adding a city</section>
<div id="verticalLine"></div>
<section id="listOfCities">
<header>
<h1>Available cities</h1>
<div id="closeConfigureWindowWrapper">
<img src="..\src\images\exit.png" id="closeConfigureWindow" alt="" />
</div>
</header>
<section id="availableCities"></section>
</section>
</div>
</div>
);
I want "ConfigureWindow" to be shown when "configureClocksButton". I tried to execute it with props, state and a function but got errors. It also would be nice if you explain me how to create new React components with React functions?
You probably want to use the React.JS event onClick (https://reactjs.org/docs/handling-events.html), and a state to store the action. To create a function component, you just have to return the JSX you want to render, and use hooks (https://reactjs.org/docs/hooks-intro.html) and then do a conditional rendering (https://reactjs.org/docs/conditional-rendering.html):
const App = () => {
const [toggleConfiguration, setToggleConfiguration] = useState(false)
return (
<div id="appWrapper">
{toggleConfiguration && <ConfigureWindow />}
<button onClick{() => setToggleConfiguration(true)} id="configureClocksButton">Configure clocks</button>
<section id="clocksHere"></section>
</div>
);
}
It's a bit difficult to understand your post, but I gather you want to click the button with id="configureClocksButton" and conditionally render the ConfigureWindow component.
You can accomplish this with some boolean state, a click handler to toggle the state, and some conditional rendering.
class App extends React.Component {
this.state = {
showConfigureWindow: false,
}
toggleShowConfigureWindow = () => this.setState(prevState => ({
showConfigureWindow: !prevState.showConfigureWindow,
}))
render() {
return (
<div id="appWrapper">
{showConfigureWindow && <ConfigureWindow />}
<button
id="configureClocksButton"
onClick={this.toggleShowConfigureWindow}
>
Configure clocks
</button>
<section id="clocksHere"></section>
</div>
);
}
}
A function component equivalent:
const App = () => {
const [showConfigureWindow, setShowConfigureWindow] = React.useState(false);
const toggleShowConfigureWindow = () => setShowConfigureWindow(show => !show);
return (
<div id="appWrapper">
{showConfigureWindow && <ConfigureWindow />}
<button
id="configureClocksButton"
onClick={toggleShowConfigureWindow}
>
Configure clocks
</button>
<section id="clocksHere"></section>
</div>
);
}

How to pass Mobx store as props to react compoent

I have this app that uses mobx, in it there is a component called "Listings" that uses some state from mobx to render a list of items.
The way it is right now, is that the Listings component gets the data it needs(store.restaurantResults[store.selectedFood]) from inside of it by using the mobx store like so:
const Listings = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="pa2">
{store.restaurantResults[store.selectedFood] &&
store.restaurantResults[store.selectedFood].map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<p>{rest.name}</p>
</div>
);
})}
</div>
));
};
But i think this is wrong, as it couples the component with the data, I want instead to pass that data via props so it can be reusable.
What is the correct way to do this? Right now my App looks like this, where it's being wrapped around a storeProvider:
function App() {
return (
<StoreProvider>
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way"/>
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings />
</div>
</StoreProvider>
);
}
My idea is to extract everrything inside the StoreProvider into another component that has a store and returns the jsx via useObserver so that I can acces the store and then pass what i need as props to the other components. like this:
const Wapper = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way" />
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings listings={store.restaurantResults[store.selectedFood]} />
</div>
))
}
And then on the listings component change the hard coded store.restaurantResults[store.selectedFood] inside to use the props that is being passes now, that is called listigs like so:
const Listings = ({listings}) => {
const store = React.useContext(StoreContext);
return useObserver(() => (
store.loading
? <Loading />
: <div className="pa2">
<div className="flex flex-wrap">
{listings &&
listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
));
};
And this works, but is this the right way to go about this?
As <Listings/> can be provided with listing and loading you can:
const Listings = ({listings, loading}) => {
if(loading) return <Loading />
return (
<div className="pa2">
<div className="flex flex-wrap">
{listings && listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
);
}
No observables used, no useObservable required.
You want to useObservables on store for listings then no reason to wrap all components with useObservable. You should wrap <Listings/> only.
I usually define my store as a global, so every component has visibility of it:
class Store {
#observable myVar
}
global.store = new Store()
And in my components i just use it:
#observer
export default class MyComponent extends React.Component {
constructor () {
super()
store.myVar = 0
}
setMyVar (a) {
store.myVar += 1
}
render () {
return <button onClick={this.setMyVar}>
Clicked {store.myVar} times
</button>
}
}

Elegant way to render props regardless of their type

Considering I have a component which accepts one prop, what would be the easiest and most elegant way for both of these to work:
<MessageBlock message="Some message goes here" />
and
<MessageBlock message={() => (
<>
Some message <strong>goes</strong> here
</>
} />
What first comes to my mind is checking the prop type with typeof and rendering the prop according to that like this:
class MessageBlock extends React.Component {
render() {
const { message: Message } = this.props;
return (
<div className="message-block">
{typeof Message === "function" ? (
<Message />
) : (
<>
{message}
</>
)}
</div>
)
}
}
It depends on what your component should be able to do.
If you want your component to be able to display anything inside of it, I would recommend using its children prop as a render function :
const MessageBlock = props => (
<div className={'add the stuff you want to wrap'}>
{props.children()}
</div>
)
const App = props => (
<React.Fragment>
<MessageBlock>
{() =>
<React.Fragment>
Some message <strong>goes</strong> here
</React.Fragment>
}
</MessageBlock>
<MessageBlock>
{() => <p>Another message</p>}
</MessageBlock>
</React.Fragment>
)
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'>
But if you want it to display a string and nothing else, use the propTypes with the first solution you proposed :
MessageBlock.propTypes = {
message: PropTypes.string
};
If you want it to do both, you could put a || condition with it, if the message is defined. The message will be shown by default if it exists, and otherwise the children function will be executed :
const MessageBlock = ({ children, message }) => (
<div className={'add the stuff you want to wrap'}>
{message || children()}
</div>
)
/*MessageBlock.propTypes = { //Proptypes are undefined in SO snippets :(
message: PropTypes.string
};*/
const App = props => (
<React.Fragment>
<MessageBlock>
{() =>
<React.Fragment>
Some message <strong>goes</strong> here
</React.Fragment>
}
</MessageBlock>
<MessageBlock message={'A string message'} />
</React.Fragment>
)
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'>

Map over Fragment in React 16

I am trying to map over the children of a fragment that is in turn a child of a component. For example:
const Frag = () => (
<React.Fragment key="test-key">
<div>test1</div>
<div>test2</div>
</React.Fragment>
);
const Outer = ({ children }) => (
<div>
{
React.Children.map(children, (child) => (
<a>
{child}
</a>
))
}
</div>
);
// Usage
<Outer>
<Frag />
</Outer>
This will result in a single a tag even though the fragment has multiple divs inside of it. The docs (https://reactjs.org/docs/react-api.html#reactchildrenmap) seem to indicate that this should work with a keyed fragment and I think I am creating a keyed fragment correctly (https://reactjs.org/docs/fragments.html#keyed-fragments). Any help would be appreciated!
Yes, but as far as I can see - you are iterating over Outer children.
Try to
React.Children.map(children[0].props.children, (child) => (
But, looking closely at your links - I see that documentation says something wrong here
the function will never be passed the container objects
Here is an example, which clearly shows that container is passed to the function:
<script src="https://unpkg.com/#babel/standalone/babel.js"></script>
<script src="https://unpkg.com/react#16.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.2.0/umd/react-dom.development.js"></script>
<div id="root" />
<script type="text/babel">
const Frag = () => (
<>
<div>test1</div>
<div>test2</div>
</>
);
const Outer = ({ children }) => (
<div>
{
React.Children.map(children, (child) => console.log(`Child type is ${child.type.name || child.type.toString()}`) || (
<a>
{child}
</a> ))
}
</div>
);
ReactDOM.render((
<div>
<Outer>
<>
<div>test1</div>
<div>test2</div>
</>
</Outer>
<Outer>
<Frag />
</Outer>
</div>
), document.getElementById('root'));
</script>

Unwrap a div in react, something similar to jQuery unwrap method

I am using React JSX. I have a div with className="shadow" as shown below.
<div className="main">
<div className="shadow" style={{backgroundColor: "#FFFFFF"}}>
<div id="wrapper">
Hello
</div>
</div>
</div>
Based on a certain condition being true or false, I want to remove the div with className="shadow", but want to keep every div including the div with id="wrapper" intact. Something like unwrap() method of jQuery.
Something to the effect of what is written below, but without so many lines of code.
if ( currentPage==="login") {
<div className="main">
<div id="wrapper">
Hello
</div>
</div>
}
else {
<div className="main">
<div className="shadow" style={{backgroundColor: "#FFFFFF"}}>
<div id="wrapper">
Hello
</div>
</div>
</div>
}
I checked React.js: Wrapping one component into another and How to pass in a react component into another react component to transclude the first component's content?, but didn't get what I am looking for.
Maybe what can help you is to change the className based on the page you are on, because what you try to do would be better using react-router to display different components based on the path you are on.
Use something similar to this code, I hope I can help you.
const App = React.createClass({
changePage(nextPage) {
this.setState({ page: nextPage })
},
getInitialState() {
return({
page: 'Login'
})
},
render() {
return(
<div className="main">
<div className={ this.state.page === 'Login' ? 'shadow' : '' }>
<div id="wrapper">
Hello from { this.state.page } page.
</div>
<button onClick={ this.changePage.bind(null, 'Login') }>Go to Login page.</button>
<button onClick={ this.changePage.bind(null, 'Home') }>Go to Home page.</button>
</div>
</div>
)
}
})
ReactDOM.render(<App />, document.getElementById('app'))
div.shadow{ background-color: #000000; color: #ffffff}
<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='app'></div>
This is a great use case for Higher Order Components!
const HOC = (currentPage, Shadow, Wrapper) => (
() => <div className="main">
{
currentPage === 'login'
? <Shadow {...this.props} />
: <Shadow {...this.props}><Wrapper {...this.props}>{this.props.children}</Wrapper></Shadow>
}
</div>
)
Usage:
render () {
const Shadow = props => <div className="shadow" style={{backgroundColor: '#FFFFFF'}}>{props.children}</div>
const Wrapper = props => <div id="wrapper">Hello</div>
const Example = HOC(
currentPage,
Shadow,
Wrapper
)
return (
<Example />
)
}
Update:
To render the children of <Wrapper />, use {this.props.children} and use the class syntax in HOC:
const HOC = (currentPage, Shadow, Wrapper) => (
class extends Component {
render () {
return (
<div className="main">
{
currentPage === 'login'
? <Shadow />
: <Shadow><Wrapper>{this.props.children}</Wrapper></Shadow>
}
</div>
)
}
}
)
If you needed to capture props on <Shadow /> and <Wrapper />, then do something like the following. Note: I don't think you can pass props into normal DOM elements like <div> tags. But if they were other components with a Capital starting letter, then I believe passing props through with {...this.props} would work nicely.
const HOC = (currentPage, Shadow, Wrapper) => (
class extends Component {
render () {
return (
<div className="main">
{
currentPage === 'login'
? <Shadow {...this.props} />
: <Shadow {...this.props}><Wrapper {...this.props}>{this.props.children}</Wrapper></Shadow>
}
</div>
)
}
}
)

Resources