I would like to set the document title (in the browser title bar) for my React application. I have tried using react-document-title (seems out of date) and setting document.title in the constructor and componentDidMount() - none of these solutions work.
For React 16.8+ you can use the Effect Hook in function components:
import React, { useEffect } from 'react';
function Example() {
useEffect(() => {
document.title = 'My Page Title';
}, []);
}
To manage all valid head tags, including <title>, in declarative way, you can use React Helmet component:
import React from 'react';
import { Helmet } from 'react-helmet';
const TITLE = 'My Page Title';
class MyComponent extends React.PureComponent {
render () {
return (
<>
<Helmet>
<title>{ TITLE }</title>
</Helmet>
...
</>
)
}
}
import React from 'react'
import ReactDOM from 'react-dom'
class Doc extends React.Component{
componentDidMount(){
document.title = "dfsdfsdfsd"
}
render(){
return(
<b> test </b>
)
}
}
ReactDOM.render(
<Doc />,
document.getElementById('container')
);
This works for me.
Edit: If you're using webpack-dev-server set inline to true
For React 16.8, you can do this with a functional component using useEffect.
For Example:
useEffect(() => {
document.title = "new title"
}, []);
Having the second argument as an array calls useEffect only once, making it similar to componentDidMount.
As others have mentioned, you can use document.title = 'My new title' and React Helmet to update the page title. Both of these solutions will still render the initial 'React App' title before scripts are loaded.
If you are using create-react-app the initial document title is set in the <title> tag /public/index.html file.
You can edit this directly or use a placeholder which will be filled from environmental variables:
/.env:
REACT_APP_SITE_TITLE='My Title!'
SOME_OTHER_VARS=...
If for some reason I wanted a different title in my development environment -
/.env.development:
REACT_APP_SITE_TITLE='**DEVELOPMENT** My TITLE! **DEVELOPMENT**'
SOME_OTHER_VARS=...
/public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
...
<title>%REACT_APP_SITE_TITLE%</title>
...
</head>
<body>
...
</body>
</html>
This approach also means that I can read the site title environmental variable from my application using the global process.env object, which is nice:
console.log(process.env.REACT_APP_SITE_TITLE_URL);
// My Title!
See: Adding Custom Environment Variables
Since React 16.8. you can build a custom hook to do so (similar to the solution of #Shortchange):
export function useTitle(title) {
useEffect(() => {
const prevTitle = document.title
document.title = title
return () => {
document.title = prevTitle
}
})
}
this can be used in any react component, e.g.:
const MyComponent = () => {
useTitle("New Title")
return (
<div>
...
</div>
)
}
It will update the title as soon as the component mounts and reverts it to the previous title when it unmounts.
import React from 'react';
function useTitle(title: string): void => {
React.useEffect(() => {
const prevTitle = document.title;
document.title = title;
return () => {
document.title = prevTitle;
};
}, []);
}
function MyComponent(): JSX.Element => {
useTitle('Title while MyComponent is mounted');
return <div>My Component</div>;
}
This is a pretty straight forward solution, useTitle sets the document title and when the component unmounts it's reset to whatever it was previously.
If you are wondering, you can set it directly inside the render function:
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
render() {
document.title = 'wow'
return <p>Hello</p>
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
For function component:
function App() {
document.title = 'wow'
return <p>Hello</p>
}
But, this is a bad practice because it will block the rendering (React prioritize the rendering first).
The good practice:
Class component:
class App extends React.Component {
// you can also use componentDidUpdate() if the title is not static
componentDidMount(){
document.title = "good"
}
render() {
return <p>Hello</p>
}
}
Function component:
function App() {
// for static title, pass an empty array as the second argument
// for dynamic title, put the dynamic values inside the array
// see: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
useEffect(() => {
document.title = 'good'
}, []);
return <p>Hello</p>
}
React Portals can let you render to elements outside the root React node (such at <title>), as if they were actual React nodes. So now you can set the title cleanly and without any additional dependencies:
Here's an example:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Title extends Component {
constructor(props) {
super(props);
this.titleEl = document.getElementsByTagName("title")[0];
}
render() {
let fullTitle;
if(this.props.pageTitle) {
fullTitle = this.props.pageTitle + " - " + this.props.siteTitle;
} else {
fullTitle = this.props.siteTitle;
}
return ReactDOM.createPortal(
fullTitle || "",
this.titleEl
);
}
}
Title.defaultProps = {
pageTitle: null,
siteTitle: "Your Site Name Here",
};
export default Title;
Just put the component in the page and set pageTitle:
<Title pageTitle="Dashboard" />
<Title pageTitle={item.name} />
you should set document title in the life cycle of 'componentWillMount':
componentWillMount() {
document.title = 'your title name'
},
update for hooks:
useEffect(() => {
document.title = 'current Page Title';
}, []);
Helmet is really a great way of doing it, but for apps that only need to change the title, this is what I use:
(modern way React solution - using Hooks)
Create change page title component
import React, { useEffect } from "react";
const ChangePageTitle = ({ pageTitle }) => {
useEffect(() => {
const prevTitle = document.title;
document.title = pageTitle;
return () => {
document.title = prevTitle;
};
});
return <></>;
};
export default ChangePageTitle;
Use the component
import ChangePageTitle from "../{yourLocation}/ChangePageTitle";
...
return (
<>
<ChangePageTitle pageTitle="theTitleYouWant" />
...
</>
);
...
You have multiple options for this problem I would highly recommend to either use React Helmet or create a hook using useEffect. Instead of writing your own hook, you could also use the one from react-use:
React Helmet
import React from 'react';
import { Helmet } from 'react-helmet';
const MyComponent => () => (
<Helmet>
<title>My Title</title>
</Helmet>
)
react-use
import React from 'react';
import { useTitle } from 'react-use';
const MyComponent = () => {
useTitle('My Title');
return null;
}
For React v18+, custom hooks will be the simplest approach.
Step 1: Create a hook. (hooks/useDocumentTitle.js)
import { useEffect } from "react";
export const useDocumentTitle = (title) => {
useEffect(() => {
document.title = `${title} - WebsiteName`;
}, [title]);
return null;
}
Step 2: Call the hook on every page with a custom title according to that page. (pages/HomePage.js)
import { useDocumentTitle } from "../hooks/useDocumentTitle";
const HomePage = () => {
useDocumentTitle("Website Title For Home Page");
return (
<>
<main>
<section>Example Text</section>
</main>
</>
);
}
export { HomePage };
Works well for dynamic pages as well, just pass the product title or whatever content you want to display.
Simply you can create a function in a js file and export it for usages in components
like below:
export default function setTitle(title) {
if (typeof title !== "string") {
throw new Error("Title should be an string");
}
document.title = title;
}
and use it in any component like this:
import React, { Component } from 'react';
import setTitle from './setTitle.js' // no need to js extension at the end
class App extends Component {
componentDidMount() {
setTitle("i am a new title");
}
render() {
return (
<div>
see the title
</div>
);
}
}
export default App
You can use the following below with document.title = 'Home Page'
import React from 'react'
import { Component } from 'react-dom'
class App extends Component{
componentDidMount(){
document.title = "Home Page"
}
render(){
return(
<p> Title is now equal to Home Page </p>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
or You can use this npm package npm i react-document-title
import React from 'react'
import { Component } from 'react-dom'
import DocumentTitle from 'react-document-title';
class App extends Component{
render(){
return(
<DocumentTitle title='Home'>
<h1>Home, sweet home.</h1>
</DocumentTitle>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Happy Coding!!!
I haven't tested this too thoroughly, but this seems to work. Written in TypeScript.
interface Props {
children: string|number|Array<string|number>,
}
export default class DocumentTitle extends React.Component<Props> {
private oldTitle: string = document.title;
componentWillUnmount(): void {
document.title = this.oldTitle;
}
render() {
document.title = Array.isArray(this.props.children) ? this.props.children.join('') : this.props.children;
return null;
}
}
Usage:
export default class App extends React.Component<Props, State> {
render() {
return <>
<DocumentTitle>{this.state.files.length} Gallery</DocumentTitle>
<Container>
Lorem ipsum
</Container>
</>
}
}
Not sure why others are keen on putting their entire app inside their <Title> component, that seems weird to me.
By updating the document.title inside render() it'll refresh/stay up to date if you want a dynamic title. It should revert the title when unmounted too. Portals are cute, but seem unnecessary; we don't really need to manipulate any DOM nodes here.
You can use ReactDOM and altering <title> tag
ReactDOM.render(
"New Title",
document.getElementsByTagName("title")[0]
);
the easiest way is to use react-document-configuration
npm install react-document-configuration --save
Example:
import React from "react";
import Head from "react-document-configuration";
export default function Application() {
return (
<div>
<Head title="HOME" icon="link_of_icon" />
<div>
<h4>Hello Developers!</h4>
</div>
</div>
);
};```
you can create TabTittleHelper.js and
export const TabTittle = (newTitle) => {
document.title=newTitle;
return document.title;
};
later you writed all screens
TabTittle('tittleName');
I am not sure if it is a good practice or not, but In index.js headers I put:
document.title="Page Title";
const [name, setName] = useState("Jan");
useEffect(() =>
{document.title = "Celebrate " + {name}.name ;}
);
I wanted to use page title to my FAQ page. So I used react-helmet for this.
First i installed react-helmet using npm i react-helmet
Then i added tag inside my return like this:
import React from 'react'
import { Helmet } from 'react-helmet'
const PAGE_TITLE = 'FAQ page'
export default class FAQ extends Component {
render () {
return (
{ PAGE_TITLE }
This is my faq page
)
}
}
If you're a beginner you can just save yourself from all that by going to the public folder of your react project folder and edit the title in "index.html" and put yours. Don't forget to save so it will reflect.
Related
I would like to render a component directly without a Target container, i need to get the content of the component right in the place where i put the generated JS file.
Here is my index.js file, i have removed the "document.getElementById('target-container')" is there a way to render this component without a target container or a way to append a target container without inserting it in the HTML template file.
var React = require('react');
var ReactDOM = require('react-dom');
import axios from 'axios';
import '../node_modules/cleanslate/cleanslate.css';
import './style.scss';
class Widget extends React.Component {
constructor(props){
super(props);
this.state = {}
}
componentDidMount() {
let id = this.props.id;
axios.get(`https://api.com/${id}`)
.then((res) => {
const brand = res.data;
this.setState({
rating: brand.rating,
logo : brand.logo,
name: brand.name,
stars: brand.stars,
url: brand.url
});
});
}
render() {
return (
<div className="cleanslate">
<a href={this.state.url} target="_blank">
<img src="https://img/.svg" className="" alt="" />
<div className="rating-box">
<img src={this.state.logo} className="logo" alt={this.state.name} />
<span className="note">{this.state.rating}/10</span>
<div id="Selector" className={`selected-${this.state.stars}`}></div>
</div>
</a>
</div>
)
}
}
ReactDOM.render(
<Widget id="7182" />
)
Here is an example (https://github.com/seriousben/embeddable-react-widget) of appending the component in another one :
import React from 'react';
import ReactDOM from 'react-dom';
import Widget from '../components/widget';
import '../../vendor/cleanslate.css';
export default class EmbeddableWidget {
static el;
static mount({ parentElement = null, ...props } = {}) {
const component = <Widget {...props} />;
function doRender() {
if (EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is already mounted, unmount first');
}
const el = document.createElement('div');
el.setAttribute('class', 'cleanslate');
if (parentElement) {
document.querySelector(parentElement).appendChild(el);
} else {
document.body.appendChild(el);
}
ReactDOM.render(
component,
el,
);
EmbeddableWidget.el = el;
}
if (document.readyState === 'complete') {
doRender();
} else {
window.addEventListener('load', () => {
doRender();
});
}
}
static unmount() {
if (!EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is not mounted, mount first');
}
ReactDOM.unmountComponentAtNode(EmbeddableWidget.el);
EmbeddableWidget.el.parentNode.removeChild(EmbeddableWidget.el);
EmbeddableWidget.el = null;
}
}
You could generate a container for your app with JS before calling ReactDOM.render (for instance with appendChild as described here) and then call ReactDOM.render passing just generated element as container.
UPD:
Even though it feels strange, you actually can get the script tag of your bundle before ReactDOM.render is called.
Knowing this, you could do something like:
// Create a container dynamically
const appContainer = document.createElement('div');
// Get all <script>s on the page and put them into an array.
const scriptTags = Array.from(document.scripts);
// Filter scripts to find the one we need.
const targetScript = scriptTags.filter(
scriptTag => scriptTag.src === 'https://example.com/bundle.js'
);
// Uh oh, we got either too many or too few,
// it might be bad, better stop right here.
if (targetScript.length !== 1) {
return;
}
// Inserts app container before the script.
document.body.insertBefore(appContainer, targetScript[0]);
// Renders things inside the container
ReactDOM.render(
<MyComponent />,
appContainer
);
New to React - I am trying to use multiple contexts within my App component, I tried following the official guide on multiple contexts.
Here is my current code:
App.js
import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import AuthContext from "./AuthContext";
import LayoutContext from "./LayoutContext";
import LoadingScreen from "./LoadingScreen";
class App extends React.Component {
render() {
const { auth, layout } = this.props;
return (
<LayoutContext.Provider value={layout}>
<LoadingScreen />
<AuthContext.Provider value={auth}>
<AuthContext.Consumer>
{auth => (auth.logged_in ? console.log("logged in") : <Login />)}
</AuthContext.Consumer>
</AuthContext.Provider>
</LayoutContext.Provider>
);
}
}
render(<App />, document.getElementById("root"));
Login.js
import React from "react";
class Login extends React.Component {
render() {
return (
<div></div>
);
}
}
export default Login;
AuthContext.js
import React from "react";
const AuthContext = React.createContext({
logged_in: false
});
export default AuthContext;
LayoutContext.js
import React from "react";
const LayoutContext = React.createContext({
show_loading: false
});
export default LayoutContext;
LoadingScreen.js
import React from "react";
import LayoutContext from "./LayoutContext";
class LoadingScreen extends React.Component {
render() {
return (
<LayoutContext.Consumer>
{layout =>
layout.show_loading ? (
<div id="loading">
<div id="loading-center">
<div className="sk-chasing-dots">
<div className="sk-child sk-dot1"></div>
<div className="sk-child sk-dot2"></div>
</div>
</div>
</div>
) : null
}
</LayoutContext.Consumer>
);
}
}
export default LoadingScreen;
Following the example, I never really understood how this.props (in App.js) could hold my different contexts.
Both auth and layout show up as undefined, this.props is empty, which will in turn cause my app to throw errors such as Cannot read property 'show_loading' of undefined
I immediately liked the example provided in the React documentation, but I can't get this to work.
I've made a small snippet to show you how you could structure your context providers and consumers.
My App component in this case is the root of the app. It has all the providers, along with the value for each one of them. I am not changing this value, but I could if I wanted to.
This then has a single child component, MyOutsideComponent, containing all the chained consumers. There are better ways to do this, I just wanted to show you, one by one, how chaining consumers work. In practice you can neatly reduce this using a few techniques.
This MyOutsideComponent has the actual component, MyComponent, which takes all the context elements and just puts their value on the page. Nothing fancy, the point was to show how the values get passed.
let FirstContext = React.createContext('first');
let SecondContext = React.createContext('second');
let ThirdContext = React.createContext('third');
let FourthContext = React.createContext('fourth');
let MyComponent = (props) => {
return (<span >{Object.values(props).join(" ")}</span>);
};
let App = (props) => {
return (
<FirstContext.Provider value="this is">
<SecondContext.Provider value="how you">
<ThirdContext.Provider value="pass context">
<FourthContext.Provider value="around">
<MyOutsideComponent />
</FourthContext.Provider>
</ThirdContext.Provider>
</SecondContext.Provider>
</FirstContext.Provider>
);
};
let MyOutsideComponent = () => {
return ( < FirstContext.Consumer >
{first =>
(< SecondContext.Consumer >
{second =>
(< ThirdContext.Consumer >
{third =>
(<FourthContext.Consumer >
{fourth =>
(<MyComponent first={first} second={second} third={third} fourth={fourth} />)
}
</FourthContext.Consumer>)
}
</ThirdContext.Consumer>)
}
</SecondContext.Consumer>)
}
</FirstContext.Consumer>);
}
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>
Now, for the actual explanation. createContext gives you two actual components: a Provider and Consumer. This Provider, as you found out, has the value. The Consumer takes as child a single function taking one argument, which is your context's value.
This is where the docs are a bit unclear, and a bit which I hope I can help a bit. This does not get passed automatically in props unless the Provider is the direct parent of the component. You have to do it yourself. So, in the example above, I chained four consumers and then lined them all up in the props of my component.
You've asked about class-based components, this is how it ends up looking like:
let FirstContext = React.createContext('first');
let SecondContext = React.createContext('second');
let ThirdContext = React.createContext('third');
let FourthContext = React.createContext('fourth');
class MyComponent extends React.Component {
render() {
return ( < span > {Object.values(this.props).join(" ")} < /span>);
}
}
class App extends React.Component {
render() {
return (
<FirstContext.Provider value = "this is" >
<SecondContext.Provider value = "how you" >
<ThirdContext.Provider value = "pass context" >
<FourthContext.Provider value = "around" >
<MyOutsideComponent / >
</FourthContext.Provider>
</ThirdContext.Provider >
</SecondContext.Provider>
</FirstContext.Provider >
);
}
}
class MyOutsideComponent extends React.Component {
render() {
return (
<FirstContext.Consumer >
{ first =>
(< SecondContext.Consumer >
{ second =>
( < ThirdContext.Consumer >
{ third =>
( < FourthContext.Consumer >
{ fourth =>
( < MyComponent first = {first} second={second} third={third} fourth={fourth} />)
}
</FourthContext.Consumer>)
}
</ThirdContext.Consumer>)
}
</SecondContext.Consumer>)
}
</FirstContext.Consumer>
);
}
}
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" />
In my react app when I make a serverside update I return a response which I use to update the state of the parent component. But for my components where I use react-responsive-tabs they don't get updated.
Here's my react code:
import React, {Component, Fragment} from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import PageTitle from '../../../Layout/AppMain/PageTitle';
import {
faAngleUp,
faAngleDown,
faCommentDots,
faBullhorn,
faBusinessTime,
faCog
} from '#fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '#fortawesome/react-fontawesome';
import Tabs from 'react-responsive-tabs';
import Roles from './Roles';
import Priviledges from './Priviledges';
export default class Apage extends Component {
constructor(props) {
super(props);
this.state = {
api: this.props.api,
session: this.props.session
}
}
componentWillMount() {
this.tabsContent = [
{
title: 'Roles',
content: <Roles api={this.state.api} session={this.state.session} />
},
{
title: 'Priviledges',
content: <Priviledges api={this.state.api} session={this.state.session} />
}
];
}
getTabs() {
return this.tabsContent.map((tab, index) => ({
title: tab.title,
getContent: () => tab.content,
key: index,
}));
}
onTabChange = selectedTabKey => {
this.setState({ selectedTabKey });
};
render() {
return (
<Fragment>
<PageTitle
heading="Roles & Priviledges"
subheading=""
icon="lnr-apartment icon-gradient bg-mean-fruit"
/>
<Tabs selectedTabKey={this.state.selectedTabKey} onChange={this.onTabChange} tabsWrapperClass="body-tabs body-tabs-layout" transform={false} showInkBar={true} items={this.getTabs()}/>
</Fragment>
)
}
}
I have tried using this within my <Roles /> tag:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.session!= this.props.session;
}
but I couldn't get it to work for me. Any clue?
I'm running my React JS within laravel using laravel-mix. I actually intend to update a dropdown whenever I submit a form using setState. I've done this many other times when I use React JSas a REST API.
I ended up using socket IO to trigger a setSate within my component after a response comes from the server. Although i'd prefer something neater.
You need to onChange like this - onChange={() => this.onTabChange()}
see below-
<Tabs onChange={() => this.onTabChange()} selectedTabKey={this.state.selectedTabKey} tabsWrapperClass="body-tabs body-tabs-layout" transform={false} showInkBar={true} items={this.getTabs()}/>
I'm totally stuck with passing data from container to component When using Meteor and React. I thought I had just copied the code from Meteor React tutorial and customized a little bit, but it doesn't work somehow. What I wanna do is getting data from a database(QuestionModel) and simply output in a view. According to an error message in browser, this.props.questions in renderQuestions() is undefined... But I explicitly passed the data to the component.
Could anyone help me out? Thank you.
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Questions as QuestionsModel } from '../../api/questions.js';
import { QuestionList } from '../QuestionList.jsx';
class Questions extends Component{
renderQuestions(){
let filteredQuestions = this.props.questions;
//This doesn't work at all...
console.log(this.props.questions);
return filteredQuestions.map((question) => (
<QuestionList key={question._id} question={question} />
));
}
render(){
return (
<div className="questionlist">
<ul>{this.renderQuestions()}</ul>
</div>
);
}
}
Questions.propTypes = {
questions: PropTypes.array.isRequired,
};
export default QuestionsContainer = createContainer(() => {
//This console.log outputs the data correctly.
console.log(QuestionsModel.find({}).fetch());
const questions = QuestionsModel.find({}).fetch();
return {
questions,
};
}, Questions);
What is the code of your QuestionList component?
I just replaced it with <li key={question._id}>{question.question}</li> and it works perfectly for me.
Something else: do you import /imports/api/questions.js'; server-side?
Here is my code:
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Questions as QuestionsModel } from '../api/questions.js';
// import { QuestionList } from '../QuestionList.jsx';
class Questions extends Component{
renderQuestions(){
let filteredQuestions = this.props.questions;
//This doesn't work at all...
console.log(this.props.questions);
return filteredQuestions.map((question) => (
<li key={question._id}>{question.question}</li>
));
}
render(){
return (
<div className="questionlist">
<ul>{this.renderQuestions()}</ul>
</div>
);
}
}
Questions.propTypes = {
questions: PropTypes.array.isRequired,
};
export default QuestionsContainer = createContainer(() => {
//This console.log outputs the data correctly.
console.log(QuestionsModel.find().fetch());
const questions = QuestionsModel.find({}).fetch();
return {
questions,
};
}, Questions);
I'm working on my first React/Redux project and I have a little question. I've read the documentation and watched the tutorials available at https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist.
But I still have one question. It's about a login page.
So I have a presentational component named LoginForm :
components/LoginForm.js
import { Component, PropTypes } from 'react'
class LoginForm extends Component {
render () {
return (
<div>
<form action="#" onSubmitLogin={(e) => this.handleSubmit(e)}>
<input type="text" ref={node => { this.login = node }} />
<input type="password" ref={node => { this.password = node }} />
<input type="submit" value="Login" />
</form>
</div>
)
}
handleSubmit(e) {
e.preventDefault();
this.props.onSubmitLogin(this.login.value, this.password.value);
}
}
LoginForm.propTypes = {
onSubmitLogin: PropTypes.func.isRequired
};
export default LoginForm;
And a container component named Login which pass data to my component. Using react-redux-router, I call this container (and not the presentationnal component) :
containers/Login.js
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
export default connect(null, mapDispatchToProps)(LoginForm);
As you can see, I'm using the connect method provide by redux to create my container.
My question is the following one :
If I want my Login container to use multiple views (for example : LoginForm and errorList to display errors), I need to do it by hand (without connect because connect take only one argument). Something like :
class Login extends Component {
render() {
return (
<div>
<errorList />
<LoginForm onSubmitLogin={ (id, pass) => dispatch(login(id, pass)) } />
</div>
)
}
}
Is it a bad practice ? Is it better to create another presentational component (LoginPage) which use both errorList and LoginForm and create a container (Login) which connect to LoginPage ?
EDIT: If I create a third presentational component (LoginPage), I'll have to pass data twice. Like this : Container -> LoginPage -> LoginForm & ErrorList.
Even with context, it don't seems to be the way to go.
I think that what you have in your second example is very close. You can create just one container component that's connected and render multiple presentational components.
In your first example, there actually isn't a separate container component:
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
// `LoginForm` is being passed, so it would be the "container"
// component in this scenario
export default connect(null, mapDispatchToProps)(LoginForm);
Even though it's in a separate module, what you're doing here is connecting your LoginForm directly.
Instead, what you can do is something like this:
containers/Login.js
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
import ErrorList from '../components/ErrorList'
class Login extends Component {
render() {
const { onSubmitLogin, errors } = this.props;
return (
<div>
<ErrorList errors={errors} />
<LoginForm onSubmitLogin={onSubmitLogin} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
const mapStateToProps = (state) => {
return {
errors: state.errors
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Note that the Login component is now being passed to connect, making it the "container" component and then both the errorList and LoginForm can be presentational. All of their data can be passed via props by the Login container.
I truly believe that you need to build all your components as Presentational Components. At the moment you need a Container Component, you might use {connect} over one of the existing Presentational and convert into a Container one.
But, that is only my view with short experience in React so far.