ReactJS: Two props and checking propType - reactjs

Situation:
<Component>
child
</Component>
What I want to do:
<Component>
<div></div>
{this.function()} // loads array of components
</Component>
Problem:
I can only specify one propType? How can I allow two different propTypes into one?
Thanks!

I believe what you're trying to do should be fine. See this code from the Meteor react tutorial:
renderTasks() {
return this.getTasks().map((task) => (
<Task key={task._id} task={task} />
));
}
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
You could just as easily modify render() to be:
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
<Task task={this.props.myTask} />
{this.renderTasks()}
</ul>
</div>
);
}
Edit: in reply to your comment - yes you can specify alternate PropTypes as children. See this other answer:
static propTypes = {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.node),
React.PropTypes.node
]).isRequired
}

Related

How to join an array with folder images in React

I am trying to render a child component with images from local folder, but I don't know how to do it.
So I have a const array with details about several projects. Each of the project has its own folder with images. The project name is equal folder name with images
Parent component
import { myProjects } from '../lib/Projects'; //its array with projects
export default class Parent extends Component {
render() {
// function for images
function importAll(r) {
return r.keys().map(r);
}
const projectA = importAll(require.context('../../assets/images/projectA', false, /\.(png|jpe?g|svg)$/));
const projects = myProjects.map((project, i) =>
<Child id={i} key={i} project={project} />)
return (
<div className="main-container">
{projects}
</div>
)
}
}
Child component
export default class Child extends Component {
render() {
const { project } = this.props;
return (
<div className="item">
<div className="desc">
<div className="item-name">
<p>{project.name}</p>
</div>
<div className="item-description">
<p>{project.description}</p>
</div>
<div className="item-tools">
<p>{project.tools}</p>
</div>
</div>
// this part works well
// How to make below part work?
<div className="image-block">
<div className="item-image-first">
<img src={project.name[0]} alt=""/>
</div>
<div className="item-images">
{project.name ? project.name.map((image, index) => {
return (
<div className="image-block-small" key={index}>
<ModalImage
small={image}
large={image}
alt=""
hideDownload={true}
hideZoom={true}
className="modal-image"
/>
</div>
)
})
: null }
</div>
</div>
</div>
)
}
}
Maybe there is a way to add an extra array here?
const projects = myProjects.map((project, i) =>
<Child id={i} key={i} project={project} />)
Any suggestion?

Can't display mapped posts from an API using axios inside react component

I would like to show mapped posts from an API inside the OwlCarousel component (import OwlCarousel from 'react-owl-carousel') the code works just fine, but only outside the OwlCarousel component. You can test on this CodeSandbox https://codesandbox.io/s/friendly-rhodes-bv5ot, the code works when you remove the OwlCarousel tag.
renderPost = () => {
return this.state.posts
? this.state.posts.map(data => (
<div key={data.id} className="item">
<div className="heading">{data.subject}</div>
<div className="content">{data.message}</div>
</div>
))
: "Loading...";
};
render() {
return (
<div className="container">
<OwlCarousel className="owl-container owl-theme">
{this.renderPost()}
</OwlCarousel>
</div>
);
}
The code works only when i put the function outside the OwlCarousel component, i think it has something to do with scopes!
render() {
return (
<div className="container">
{this.renderPost()}
</div>
);
}
Try with following code
renderPost = () => {
return (
<div>
{this.state.posts
? this.state.posts.map(data => (
<div key={data.id} className="item">
<div className="heading">{data.subject}</div>
<div className="content">{data.message}</div>
</div>
))
: "Loading..."}
</div>
)
};
And In your render of the Component, you can do same as you have already done,
{this.renderPost()}

TypeError: author is undefined

creating react AuthorQuiz app
I have tow main file
1- AuthorQuiz.js
2- index.js
I have a problem with Turn component
AuthorQuiz.js
enter code here
function Turn({ author, books }) {
return (
<div className="row turn" style={{ backgroundColor: 'white' }}>
<div className="col-4 offset-1">
<img src={author.imageUrl} className="authorImage" alt="Author" />
</div>
<div className="col-6">
{books.map((title) => <p>{title}</p>)}
</div>
</div>);
}
function AuthorQuiz(turnData) {
return (
<div className="container-fluid">
<Hero/>
<Turn {...turnData}/>
<Continue/>
<Footer/>
</div>
);
}
index.js
enter code here
const authors = [
{
name: 'mark Twin',
imageUrl: 'images/authors/mark.jpg',
imageSource: 'google wiki',
books: ['The Advance of Finn']
}
];
const state={
turnData:{
author:authors[0],
books:authors[0].books
}
}
ReactDOM.render(<AuthorQuiz {...state}/>,
document.getElementById('root'));
but when I run my code I get an error
TypeError: author is undefined
Turn
C:/Users/HP/Desktop/React pro/authorquiz/src/AuthorQuiz.js:18
You should use {state.turnData} instead of {...state}.
Because the result of {...state} is like this: {turnData: {…}}
So { author, books } can't work correctly.
const state={
turnData:{
author:authors[0],
books:authors[0].books
}
}
<AuthorQuiz {...state}/>
Spreading the state over the AuthorQuiz is equivalent to:
<AuthorQuiz turnData={state.turnData}/>
But in the AuthorQuiz component:
function AuthorQuiz(turnData) {
return (
<div className="container-fluid">
<Hero/>
<Turn {...turnData}/>
<Continue/>
<Footer/>
</div>
);
}
function AuthorQuiz(turnData) {
...
<Turn {...turnData}/>
...
is equivalent to
function AuthorQuiz(props) {
...
<Turn turnData={
props.turnData
}}/>
...
So you need to add brackets to spread turnData instead of props:
function AuthorQuiz({ turnData }) {
...
<Turn { ...turnData}}/>
...
The AuthorQuiz component is getting the turnData variable as a prop. You should use the spread operator on the props.turnData variable instead.
function AuthorQuiz(props) {
return (
<div className="container-fluid">
<Hero/>
<Turn {...props.turnData}/>
<Continue/>
<Footer/>
</div>
);
}
The props in the AuthorQuiz component look like this:
{
turnData: {
author: { .. },
books: [ .. ]
}
}
Your Turn component wants the author and books props, so you could do this in your AuthorQuiz component.
function AuthorQuiz(props) {
return (
<div className="container-fluid">
<Hero/>
<Turn {...props.turnData} />
{/* or */}
<Turn author={props.turnData.author} books={props.turnData.books} />
<Continue/>
<Footer/>
</div>
);
}
You could also destructure the turnData prop in your component function directly. This makes it clear which props are being drilled down the Turn component without switching to multiple files.
function AuthorQuiz({ turnData: { author, books } }) {
return (
<div className="container-fluid">
<Hero/>
<Turn author={author} books={books} />
<Continue/>
<Footer/>
</div>
);
}

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>
)
}
}
)

Render React nested components

Is there a way to render the children of a React component under different divs?
<Page>
<Header> ... </Header>
<Content> ... </Content>
<Actions> ... </Actions>
</Page>
<div class="page">
<div class="header-wrapper>
<div class="header"> ... </div>
</div>
<div class="content-wrapper">
<div class="content"> ... </div>
<div class="actions"> ... </div>
</div>
</div>
Here I need the wrappers because the header is laid out differently from the area where content and actions are.
The obvious solution may be to introduce a Body component, but is there a more elegant way to abstract the concrete div tree which is needed to represent a certain layout from the logical components (Header, Content, Actions) which the Page declares.
Another solution which works is to pass the Header, Content, and Actions as properties as suggested in the React documentation:
<Page
header={
<Header> ... </Header>
}
content={
<Content> ... </Content>
}
actions={
<Actions> ... </Actions>
}
/>
Thanks,
George
React lets you access individual Children
<Page>
<Header> ... </Header>
<Content> ... </Content>
<Actions> ... </Actions>
</Page>
Inside <Page /> Component render
render() {
// when multiple elements are passed children prop is an array.
// extract each of the element passed from the array and place them
// wherever needed in the JSX
const [HeaderView, ContentView, ActionsView] = this.props.children;
<div class="page">
<div class="header-wrapper>
{HeaderView}
</div>
<div class="content-wrapper">
{ContentView}
{ActionsView}
</div>
</div>
}
Use React.Children.toArray to convert your props.children (that is an opaque data structure) to an array and safely reorganize them.
After convert to array, you can use find or forEach methods to get each one - Header, Content and Action - by testing the type of the item.
const children = React.Children.toArray(this.props.children);
const headerView = children.find(child => child.type.name === 'HeaderView');
const contentView = children.find(child => child.type.name === 'ContentView');
const actionsView = children.find(child => child.type.name === 'ActionsView');
<div class="page">
<div class="header-wrapper>
{headerView}
</div>
<div class="content-wrapper">
{contentView}
{actionsView}
</div>
</div>
Remember to declare HeaderView, ContentView and ActionsView with class or function declaration, not anonymous functions like:
const HeaderView = props => {
// some stateless rendering
};
Otherwise they might have no type.name to test against.
In Aurelia, you can use named slots to achieve this result, but in react the best workaround I have found is to do this:
// ...
const header = <Header someProp={someVal} />;
const content = <Content someOtherProp={someOtherVal} />;
const actions = <Actions action1={someAction} action2={someOtherAction} />;
return (
<Page header={header} content={content} actions={actions} />
);
and in page.jsx:
import React from 'react';
import PropTypes from 'prop-types';
export const Page = ({ header, content, actions }) => (
<div className="page">
<div className="header-wrapper">
<div className="header">
{header}
</div>
</div>
<div className="content-wrapper">
<div className="content">
{content}
</div>
<div className="actions">
{actions}
</div>
</div>
</div>
);
Page.propTypes = {
header: PropTypes.node.isRequired,
content: PropTypes.node.isRequired,
actions: PropTypes.node.isRequired,
};

Resources