Adding React Component child to another React Component at runtime - javascript

I am super stuck with ReactJs in trying to add one Virtual Component to another Component at runtime and failing to do so. Here is what I am trying to do:
My App.js looks like this:
import React from 'react';
import './App.css';
// Components
import Header from './components/Header';
import Footer from './components/Footer';
import LeftSideSpace from './components/LeftSideSpace';
import RightSideSpace from './components/RightSideSpace';
import CenterSpace from './components/CenterSpace';
// main class name: App
class App extends React.Component {
// main function name: render
render() {
return (
<div className="App">
<Header title='My Blog'/>
<LeftSideSpace/>
<CenterSpace/>
<RightSideSpace/>
<Footer title='Welcome! This is my Blog site'/>
</div>
);
}
}
export default App;
My focus is on the component <CenterSpace/> which I am importing from here:
import React from 'react';
import PropTypes from 'prop-types'
class CenterSpace extends React.Component {
render() {
return (
<centerspace className="Site.CenterSpace">
<div id="Site.CenterSpace.Content">
{this.props.children}
</div>
</centerspace>
);
}
}
// props defaults
CenterSpace.defaultProps = {
title: 'Personal Blogger\'s site'
}
// props validations
CenterSpace.propTypes = {
title: PropTypes.string.isRequired
}
export default CenterSpace
Then I have a menu component like this, as of now, this is what I have in code, which I am sure contains bugs:
import React from 'react';
import PropTypes from 'prop-types'
import CenterSpace from '../CenterSpace'
import HomeLists from './HomeLists'
class MainMenu extends React.Component {
render() {
return (
<div className="Site.MainMenu">
<button onClick={this.props.onClickHome}>Home</button>
<button onClick={this.props.onClickBlogs}>Blogs</button>
<button onClick={this.props.onClickAboutMe}>About Me</button>
</div>
);
}
}
// props defaults
MainMenu.defaultProps = {
//control button clicks
onClickHome: () => {
var home_dom = new HomeLists();
var center_dom = new CenterSpace<String>("My Blog list");
console.log("say we went to home")
center_dom.appendChild(home_dom);
},
onClickBlogs:() => {
console.log("say we went to blogs")
},
onClickAboutMe:() => {
console.log("say we went to about me")
}
}
// props validations
MainMenu.propTypes = {
onClickHome: PropTypes.func.isRequired,
onClickBlogs: PropTypes.func.isRequired,
onClickAboutMe: PropTypes.func.isRequired,
}
export default MainMenu
This main-menu is used to dynamically add and remove components, but I am failing to do so. When I click Home button, the action I am trying achieveis to add <HomeList/> component to <CenterSpace/>. And futher, <HomeList/> is parsing some Json files and appending as child divs.
<HomeList/> looks like this (may have some issues, was not able to make it work, but that is something I can fix):
import React from 'react';
import PropTypes from 'prop-types'
class HomeLists extends React.Component {
render() {
const fs_obj = require('fs');
const fs_path = require('path');
const fs_jsons = fs_obj.readdirSync('../data').filter(file => fs_path.extname(file) === '.json');
fs_jsons.forEach(file => {
const file_data = fs_obj.readFileSync(fs_path.join('../data', file));
const json = JSON.parse(file_data.toString());
const blog_title = json.title;
var snippet_header = document.createElement('h3');
snippet_header.textContent(blog_title);
const blog_desp = json.blog.content[0].value;
var snippet_text = document.createElement('p');
snippet_text.textContent(blog_desp);
var snippet = document.createElement('div');
snippet.appendChild(snippet_header);
snippet.appendChild(snippet_text);
this.appendChild(snippet);
});
return (
<homelists className="Site.HomeLists">
<div id="Site.HomeLists.Content">{HomeLists}</div>
</homelists>
);
}
}
// props defaults
HomeLists.defaultProps = {
title: 'Personal Blogger\'s site'
}
// props validations
HomeLists.propTypes = {
title: PropTypes.string.isRequired
}
export default HomeLists
Right now when I click Home, all I get is the following error:
TypeError: center_dom.appendChild is not a function
onClickHome
src/components/complications/MainMenu.js:29
28 | console.log("say we went to home")
> 29 | center_dom.appendChild(home_dom);
| ^
30 | },
31 | onClickBlogs:() => {
32 |
console.log("say we went to blogs")
Can anyone help me get unblock from here.

Use the following component as an example for conditional rendering and it is based on your question as well.
import React from "react";
class MainMenu extends React.Component {
constructor(props) {
super(props);
this.state = { isHome: false, isBlogs: false, isAboutMe: false };
// Binding this keyword
this.onClickHome = this.onClickHome.bind(this);
this.onClickBlogs = this.onClickBlogs.bind(this);
this.onClickAboutMe = this.onClickAboutMe.bind(this);
}
onClickHome() {
this.setState({ isHome: true, isBlogs: false, isAboutMe: false });
}
onClickBlogs() {
this.setState({ isHome: false, isBlogs: true, isAboutMe: false });
}
onClickAboutMe() {
this.setState({ isHome: false, isBlogs: false, isAboutMe: true });
}
render() {
return (
<div className="Site.MainMenu">
<button onClick={this.onClickHome}>Home</button>
<button onClick={this.onClickBlogs}>Blogs</button>
<button onClick={this.onClickAboutMe}>About Me</button>
{this.state.isHome && <div>Home view is enabled</div>}
{this.state.isBlogs && <div>Blogs view is enabled</div>}
{this.state.isAboutMe && <div>AboutMe view is enabled</div>}
</div>
);
}
}
export default MainMenu;
Application View
Refer this link for more info on conditional rendering: https://reactjs.org/docs/conditional-rendering.html

Related

Getting error when destructure value from state in TypeScript

enter image description here
When I tried to destructure the notification_menu value am getting the error
like this
am using Redux for state management
enter image description here
import React, { Suspense,Component } from 'react';
import { Switch, Route } from "react-router-dom";
import { connect } from 'react-redux';
import { NotificationMenu } from "./Redux/NotificationMenu/nof_selector";
class App extends Component {
state = {
navOpen: false,
};
NavCall = () => {
this.setState({ navOpen: !this.state.navOpen });
};
render() {
console.log(this.props.notification_menu);
const { navOpen } = this.state;
const NavOpenStyleMargin = navOpen ? { marginLeft: "250px" } : {};
return (
<div>
</div>
)
}
}
const mapStateToProps = (state:any) => {
return {
// userID: selectCurrentUser(state),
// account_menu: AccountMenu(state),
notification_menu: NotificationMenu(state),
};
};
export default connect(mapStateToProps,null)(App);
when using typescript you need to declare props and state types to your class component like:
interface IProps {
notification_menu: string // define what type notification_menu is
}
interface IState {
navOpen: boolean
}
class MyComponent extends React.Component<IProps, IState> {
// code here
}

Why React returns "Object() is not a function" when using marked library?

I'm making a markdown editor using Marked library like this <div id="preview" dangerouslySetInnerHTML={ {__html: marked('Rendered by **marked**.''></div> but get TypeError: Object(...) is not a function.
Found two relevant posts on SO; first and second I'm using the same syntax as the answers but I get a TypeError. In both posts they used ReactDOM.render() method in the end. Full code:
import React, { Component } from 'react';
import './App.css';
import { Provider, connect } from 'react-redux';
import { createStore } from'redux';
import { marked } from "marked";
// Redux
const ADD = "ADD";
const addText = (text) => {
return {
type: ADD,
text: text
}
};
const textReducer = (state = {text: ''}, action) => {
switch(action.type) {
case ADD:
return Object.assign({},state, { text: action.text })
default:
return state
}
};
const store = createStore(textReducer);
// React
class App extends Component {
constructor(props){
super(props)
/*this.state = {
input : ''
}*/
this.handleChange = this.handleChange.bind(this);
};
handleChange(e){
/*this.setState({
input: e.target.value
})*/
this.props.addText(e.target.value)
};
render(){
return(
<div className="App-header">
<textarea id="editor" value={this.props.text} onChange={this.handleChange}></textarea>
<div id="preview" dangerouslySetInnerHTML={ {__html: marked('Rendered by **marked**.') } }></div>
</div>
)
}
};
// React-Redux
const mapStateToProps = (state) => {
return {
text: state.text
}
};
const mapDispatchToProps = (dispatch) => {
return {
addText: (text) => {
dispatch(addText(text))
}
}
};
const Container = connect(mapStateToProps, mapDispatchToProps)(App);
// eslint-disable-next-line
export default class AppWrapper extends Component {
render() {
return(
<Provider store={store}>
<Container />
</Provider>
);
}
};
The markdown text suppose to be rendered as html in preview element but instead I get TypeError: Object(...) is not a function.
UPDATE: apparently redux was not setup properly and was set to an array instead of object. I fixed that but I still get the same error.
I found the solution, problem was I imported marked as named import {import {marked} from 'marked' instead of import as default like this import marked from 'marked'

How to pass in an instance variable from a React component to its HOC?

I typically use component composition to reuse logic the React way. For example, here is a simplified version on how I would add interaction logic to a component. In this case I would make CanvasElement selectable:
CanvasElement.js
import React, { Component } from 'react'
import Selectable from './Selectable'
import './CanvasElement.css'
export default class CanvasElement extends Component {
constructor(props) {
super(props)
this.state = {
selected: false
}
this.interactionElRef = React.createRef()
}
onSelected = (selected) => {
this.setState({ selected})
}
render() {
return (
<Selectable
iElRef={this.interactionElRef}
onSelected={this.onSelected}>
<div ref={this.interactionElRef} className={'canvas-element ' + (this.state.selected ? 'selected' : '')}>
Select me
</div>
</Selectable>
)
}
}
Selectable.js
import { Component } from 'react'
import PropTypes from 'prop-types'
export default class Selectable extends Component {
static propTypes = {
iElRef: PropTypes.shape({
current: PropTypes.instanceOf(Element)
}).isRequired,
onSelected: PropTypes.func.isRequired
}
constructor(props) {
super(props)
this.state = {
selected: false
}
}
onClick = (e) => {
const selected = !this.state.selected
this.setState({ selected })
this.props.onSelected(selected)
}
componentDidMount() {
this.props.iElRef.current.addEventListener('click', this.onClick)
}
componentWillUnmount() {
this.props.iElRef.current.removeEventListener('click', this.onClick)
}
render() {
return this.props.children
}
}
Works well enough. The Selectable wrapper does not need to create a new div because its parent provides it with a reference to another element that is to become selectable.
However, I've been recommended on numerous occasions to stop using such Wrapper composition and instead achieve reusability through Higher Order Components. Willing to experiment with HoCs, I gave it a try but did not come further than this:
CanvasElement.js
import React, { Component } from 'react'
import Selectable from '../enhancers/Selectable'
import flow from 'lodash.flow'
import './CanvasElement.css'
class CanvasElement extends Component {
constructor(props) {
super(props)
this.interactionElRef = React.createRef()
}
render() {
return (
<div ref={this.interactionElRef}>
Select me
</div>
)
}
}
export default flow(
Selectable()
)(CanvasElement)
Selectable.js
import React, { Component } from 'react'
export default function makeSelectable() {
return function decorateComponent(WrappedComponent) {
return class Selectable extends Component {
componentDidMount() {
// attach to interaction element reference here
}
render() {
return (
<WrappedComponent {...this.props} />
)
}
}
}
}
The problem is that there appears to be no obvious way to connect the enhanced component's reference (an instance variable) to the higher order component (the enhancer).
How would I "pass in" the instance variable (the interactionElRef) from the CanvasElement to its HOC?
I came up with a different strategy. It acts roughly like the Redux connect function, providing props that the wrapped component isn't responsible for creating, but the child is responsible for using them as they see fit:
CanvasElement.js
import React, { Component } from "react";
import makeSelectable from "./Selectable";
class CanvasElement extends Component {
constructor(props) {
super(props);
}
render() {
const { onClick, selected } = this.props;
return <div onClick={onClick}>{`Selected: ${selected}`}</div>;
}
}
CanvasElement.propTypes = {
onClick: PropTypes.func,
selected: PropTypes.bool,
};
CanvasElement.defaultProps = {
onClick: () => {},
selected: false,
};
export default makeSelectable()(CanvasElement);
Selectable.js
import React, { Component } from "react";
export default makeSelectable = () => WrappedComponent => {
const selectableFactory = React.createFactory(WrappedComponent);
return class Selectable extends Component {
state = {
isSelected: false
};
handleClick = () => {
this.setState({
isSelected: !this.state.isSelected
});
};
render() {
return selectableFactory({
...this.props,
onClick: this.handleClick,
selected: this.state.isSelected
});
}
}
};
https://codesandbox.io/s/7zwwxw5y41
I know that doesn't answer your question. I think you're trying to let the child get away without any knowledge of the parent.
The ref route feels wrong, though. I like the idea of connecting the tools to the child. You can respond to the click in either one.
Let me know what you think.
Just as you did on DOM element for CanvasElement, Ref can be attached to class component as well, checkout the doc for Adding a Ref to a Class Component
export default function makeSelectable() {
return function decorateComponent(WrappedComponent) {
return class Selectable extends Component {
canvasElement = React.createRef()
componentDidMount() {
// attach to interaction element reference here
console.log(this.canvasElement.current.interactionElRef)
}
render() {
return (
<WrappedComponent ref={this.canvasElement} {...this.props} />
)
}
}
}
}
Also, do checkout Ref forwarding if you need child instance reference in ancestors that's multiple levels higher in the render tree. All those solutions are based on assumptions that you're on react 16.3+.
Some caveats:
In rare cases, you might want to have access to a child’s DOM node from a parent component. This is generally not recommended because it breaks component encapsulation, but it can occasionally be useful for triggering focus or measuring the size or position of a child DOM node.
While you could add a ref to the child component, this is not an ideal solution, as you would only get a component instance rather than a DOM node. Additionally, this wouldn’t work with functional components. https://reactjs.org/docs/forwarding-refs.html
I've now come up with an opinionated solution where the HoC injects two callback functions into the enhanced component, one to register the dom reference and another to register a callback that is called when an element is selected or deselected:
makeElementSelectable.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import movementIsStationary from '../lib/movement-is-stationary';
/*
This enhancer injects the following props into your component:
- setInteractableRef(node) - a function to register a React reference to the DOM element that should become selectable
- registerOnToggleSelected(cb(bool)) - a function to register a callback that should be called once the element is selected or deselected
*/
export default function makeElementSelectable() {
return function decorateComponent(WrappedComponent) {
return class Selectable extends Component {
static propTypes = {
selectable: PropTypes.bool.isRequired,
selected: PropTypes.bool
}
eventsAdded = false
state = {
selected: this.props.selected || false,
lastDownX: null,
lastDownY: null
}
setInteractableRef = (ref) => {
this.ref = ref
if (!this.eventsAdded && this.ref.current) {
this.addEventListeners(this.ref.current)
}
// other HoCs may set interactable references too
this.props.setInteractableRef && this.props.setInteractableRef(ref)
}
registerOnToggleSelected = (cb) => {
this.onToggleSelected = cb
}
componentDidMount() {
if (!this.eventsAdded && this.ref && this.ref.current) {
this.addEventListeners(this.ref.current)
}
}
componentWillUnmount() {
if (this.eventsAdded && this.ref && this.ref.current) {
this.removeEventListeners(this.ref.current)
}
}
/*
keep track of where the mouse was last pressed down
*/
onMouseDown = (e) => {
const lastDownX = e.clientX
const lastDownY = e.clientY
this.setState({
lastDownX, lastDownY
})
}
/*
toggle selected if there was a stationary click
only consider clicks on the exact element we are making interactable
*/
onClick = (e) => {
if (
this.props.selectable
&& e.target === this.ref.current
&& movementIsStationary(this.state.lastDownX, this.state.lastDownY, e.clientX, e.clientY)
) {
const selected = !this.state.selected
this.onToggleSelected && this.onToggleSelected(selected, e)
this.setState({ selected })
}
}
addEventListeners = (node) => {
node.addEventListener('click', this.onClick)
node.addEventListener('mousedown', this.onMouseDown)
this.eventsAdded = true
}
removeEventListeners = (node) => {
node.removeEventListener('click', this.onClick)
node.removeEventListener('mousedown', this.onMouseDown)
this.eventsAdded = false
}
render() {
return (
<WrappedComponent
{...this.props}
setInteractableRef={this.setInteractableRef}
registerOnToggleSelected={this.registerOnToggleSelected} />
)
}
}
}
}
CanvasElement.js
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import PropTypes from 'prop-types'
import flowRight from 'lodash.flowright'
import { moveSelectedElements } from '../actions/canvas'
import makeElementSelectable from '../enhancers/makeElementSelectable'
class CanvasElement extends PureComponent {
static propTypes = {
setInteractableRef: PropTypes.func.isRequired,
registerOnToggleSelected: PropTypes.func
}
interactionRef = React.createRef()
componentDidMount() {
this.props.setInteractableRef(this.interactionRef)
this.props.registerOnToggleSelected(this.onToggleSelected)
}
onToggleSelected = async (selected) => {
await this.props.selectElement(this.props.id, selected)
}
render() {
return (
<div ref={this.interactionRef}>
Select me
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const {
canvas: {
selectedElements
}
} = state
const selected = !!selectedElements[ownProps.id]
return {
selected
}
}
const mapDispatchToProps = dispatch => ({
selectElement: bindActionCreators(selectElement, dispatch)
})
const ComposedCanvasElement = flowRight(
connect(mapStateToProps, mapDispatchToProps),
makeElementSelectable()
)(CanvasElement)
export default ComposedCanvasElement
This works, but I can think of at least one significant issue: the HoC injects 2 props into the enhanced component; but the enhanced component has no way of declaratively defining which props are injected and just needs to "trust" that these props are magically available
Would appreciate feedback / thoughts on this approach. Perhaps there is a better way, e.g. by passing in a "mapProps" object to makeElementSelectable to explicitly define which props are being injected?

Uncaught TypeError: TodoActions.createTodo is not a function at Todos.createTodo

I have an error as posted in title. I've tried to put some code into flux pattern so I have:
TodoActions file which represents actions
Todos file which represents view
I have also Dispatcher file and Store file, but i think the problem doesn't touch
them.
While clicking the button in Todos.js it calls the createTodo() function from Todos.js and while executing
TodoActions.createTodo('x');
An error occurs -> TodoActions.createTodo is not a function at Todos.createTodo
Todos.js
import React from "react";
import Todo from "../components/Todo";
import TodoStore from "../stores/TodoStore";
import * as TodoActions from "../actions/TodoActions";
export default class Todos extends React.Component {
constructor() {
super();
this.state = {
todos: TodoStore.getAll(),
};
}
componentWillMount() {
TodoStore.on("change", () =>{
this.setState({
todos: TodoStore.getAll(),
});
});
}
createTodo(){
TodoActions.createTodo('x');
}
render() {
const { todos } = this.state;
const TodoComponents = todos.map((todo) => {
return <Todo key={todo.id} {...todo}/>;
});
return (
<div>
<button onClick={this.createTodo.bind(this)}>Create!</button>
<h1>Todos</h1>
<ul>{TodoComponents}</ul>
</div>
);
}
}
TodoActions.js
import dispatcher from "../thedispatcher/dispatcher";
export function createTodo(text){
dispatcher.dispatch({
type: "CREATE_TODO",
text,
});
}
project structure
Thanks in advance!
I think that the file TodoActions is bing imported instead of TodoActions.js, so remove it if you don't need it or rename it so the TodoActions.js 'll be imported.

Reactjs, reducers, language switching

I have just taken over a new reactjs project -- and I am trying to review how language switching has been invoked.
so like there are two links in the footer to do this language switch.
//footer.js
import React from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { selectLanguage, getLangDetails } from '../../actions/action_language'
import langObject from './Footer.lang'
class Footer extends React.Component {
constructor (props) {
super(props)
this.changeLanguageToGerman = this.changeLanguageToGerman.bind(this)
this.changeLanguageToEnglish = this.changeLanguageToEnglish.bind(this)
}
changeLanguageToGerman () {
this.props.selectLanguage('de')
}
changeLanguageToEnglish () {
this.props.selectLanguage('en')
}
render () {
let activeLang = 'language--active'
let alternativeLang = 'language--hover'
const lang = getLangDetails(this.props.active_language, langObject)
return (
<div>
<footer className='main-footer show-for-medium-only'>
<div className='medium-15 columns'>
<p className='text--white grid__row--offset--15 footer-text'>
<Link to={this.props.deURL} className={`text--white footer-text ${this.props.active_language === 'de' ? activeLang : alternativeLang}`} onClick={this.changeLanguageToGerman}>DE</Link>
|
<Link to={this.props.enURL} className={`text--white footer-text ${this.props.active_language === 'en' ? activeLang : alternativeLang}`} onClick={this.changeLanguageToEnglish}>EN</Link>
</p>
</div>
</footer>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
active_language: state.active_language
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({selectLanguage: selectLanguage}, dispatch)
}
const { string, func } = React.PropTypes
Footer.propTypes = {
deURL: string,
enURL: string,
selectLanguage: func,
active_language: string
}
export default connect(mapStateToProps, mapDispatchToProps)(Footer)
// header.js
import React from 'react'
import { connect } from 'react-redux'
import { getLangDetails } from '../../actions/action_language'
import langObject from './Header.lang'
class Header extends React.Component {
render () {
let transparent
transparent = this.props.transparent ? 'transparent' : ''
const lang = getLangDetails(this.props.active_language, langObject)
return (
<div>
<header className={` main_headerbar__landing transition show-for-large-up ${transparent} `}>
<div className='contain-to-grid'>
{lang}
</div>
</header>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
active_language: state.active_language
}
}
const { bool, string } = React.PropTypes
Header.propTypes = {
transparent: bool,
active_language: string
}
export default connect(mapStateToProps)(Header)
--- so these are the header/footer components - and each has a json file that splits into an array of lang.
there is a file that looks like some global js that I think hooks into this - but I am struggling to extend this functionality into the rest of the site components/pages
//action_language.js
export const LANGUAGE_SELECTED = 'LANGUAGE_SELECTED'
export function selectLanguage (language) {
return {
type: LANGUAGE_SELECTED,
payload: language
}
}
export function getLangDetails (language = 'de', langObject) {
const langData = langObject.langs.filter((langVar) => langVar.lang === language)
return langData['0'].lines
}
ok - so here is the first page -- called services. Now what throws me first here is rather than use active_language its now just language.
//services.js
import React from 'react'
import Header from '../HeaderLanding/Header'
import Footer from '../Footer/Footer'
import NoBundle from './NoBundle'
import HowTiles from './HowTiles'
import CarouselTiles from './CarouselTiles'
import YourAdvantages from './YourAdvantages'
import InformationTiles from './InformationTiles'
import ContactTiles from './ContactTiles'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { selectLanguage, getLangDetails } from '../../actions/action_language'
// language file
import langObject from './services.lang.json'
// services css
import './services.css'
// getting the distinct URLs from the lang files
const deURL = langObject.langs[0].pageURL
const enURL = langObject.langs[1].pageURL
const Spacers = () => (
<div>
<div className='row show-for-large-up' style={{ height: '250px' }} />
<div className='row show-for-medium-only' style={{ height: '150px' }} />
<div className='row show-for-small-only' style={{ height: '80px' }} />
</div>
)
class Services extends React.Component {
constructor (props) {
super(props)
this.language = props.match.params.langURL
}
componentWillMount () {
document.getElementById('body').className = 'overlay-background-services'
this.updateLanguage()
}
updateLanguage () {
console.log('updatelang', this.language)
if (this.language === 'de' || !this.language) {
this.props.selectLanguage('de')
} else {
this.props.selectLanguage('en')
}
}
componentWillUnmount () {
document.getElementById('body').className = ''
}
render () {
const lang = getLangDetails(this.language, langObject)
return (
<div>
<Header transparent />
<Spacers />
<NoBundle lang={lang} />
<HowTiles />
<CarouselTiles />
<YourAdvantages />
<InformationTiles />
<ContactTiles />
<Footer deURL={deURL} enURL={enURL} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
language: state.language
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({selectLanguage: selectLanguage}, dispatch)
}
const { func, string, object } = React.PropTypes
Services.propTypes = {
selectLanguage: func,
langURL: string,
params: object,
match: object
}
export default connect(mapStateToProps, mapDispatchToProps)(Services)
The Footer component deals with setting the current language by invoking the Redux action creator selectLanguage. Essentially this dispatches an action (you can think of this as a custom event with some corresponding data - the language) to the store that will persist the user's language selection for use elsewhere.
In order to consume the language in other components, that language selection needs to be passed into the component (in this case the Header) from the Redux store.
This is the code of interest in header that does that...
const mapStateToProps = (state) => {
return {
active_language: state.active_language
}
}
export default connect(mapStateToProps)(Header)
Here you are connecting the Header to the store, with a function that describes how the store should map values into props on your react component. state.active_language is where the language that the user has selected is stored, and this is telling it to be passed as a prop called active_language on your Header component
The connect function is a decorator that will create what's know as a Higher Order Component (HOC) which is essentially a component with props or functionality automatically injected into it (decorated in this case with an automatically passed value for the active_language prop from the store)
You can do the same for any other component that need this language setting, or go a step or two further
Instead of passing the active language name, pass the corresponding language itself...
import { getLangDetails } from '../../actions/action_language'
import langObject from './Header.lang'
const mapStateToProps = (state) => {
return {
active_language: getLangDetails(state.active_language, langObject)
}
}
export default connect(mapStateToProps)(Header)
OR better yet write another HOC that wraps any component you pass with this info...
import { getLangDetails } from '../../actions/action_language'
export default const injectLanguage = (component, langObject) => connect((state) => ({
language: getLangDetails(state.active_language, langObject)
})
)(component)
Then in subsequent components with a language prop, decorate with this
import injectLanguage from './some/where'
import langObject from './MyLanguageDetailsAwareComponent.lang'
class MyLanguageDetailsAwareComponent extends React.Component {
render() {
return this.props.language
}
}
export default injectLanguage(MyLanguageDetailsAwareComponent, langObject)

Categories