I'm trying to get values from context api, but, i getting an default value (without the updates made during the context call) and a undefined value (that is supposed to get a value in the context provider)
sortListContext.tsx
import React from 'react'
type Props = {
actualSort?:string;
sorts:string[];
}
export const SortListContext = React.createContext<Props>({
sorts: []
})
export function SortListProvider({children}:any){
const [actualSort, setActualSort] = React.useState<string>()
const sorts = ['numberAsc','numberDesc','nameAsc','nameDesc']
function getSort(){
if(typeof window !== 'undefined'){
const local = localStorage.getItem('sort')
if(local){
return ('A')
}
else{
return ('B')
}
}
}
React.useEffect(()=>{
setActualSort(getSort)
},[])
return (
<SortListContext.Provider value={{ sorts, actualSort}}>
{children}
</SortListContext.Provider>
)
}
export function useSortListContext(){
return React.useContext(SortListContext)
}
favorites.tsx
import React from 'react'
import Container from '../components/pokedex/container/'
import Main from '../components/pokedex/main/'
import PokemonList from '../components/pokedex/pokemonList/'
import {useFavoritesContext} from '../contexts/favoritesContext'
import {useSortListContext} from '../contexts/sortListContext'
interface Types {
type: {
name:string;
}
}
interface PokemonProps {
id:number;
name:string;
types: Types[];
}
const Favorites = () => {
const {favorites} = useFavoritesContext()
const {sorts, actualSort} = useSortListContext()
const [localFavorites, setLocalFavorites] = React.useState<string[]>()
const [pokemonList, setPokemonList] = React.useState<PokemonProps[]>([])
React.useEffect(()=>{
if(typeof window !== 'undefined'){
console.log(actualSort, sorts)
}
},[actualSort, sorts])
React.useEffect(()=>{
if(localFavorites && localFavorites.length > 0){
localFavorites?.forEach((item)=>{
async function getPokemon() {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${item}`)
const data = await response.json()
setPokemonList((oldPokemonList)=> [...oldPokemonList, data])
}
getPokemon()
})
}
},[localFavorites])
React.useEffect(()=>{
setLocalFavorites(favorites)
setPokemonList([])
}, [favorites])
return (
<Container>
<Main>
<PokemonList pokemonList={pokemonList} />
</Main>
</Container>
)
}
export default Favorites
i've already tried put an initial value to actualSort but doesn't work, the error probably is on the provider
I just forgot wrapping my app with the provider, thanks #yousoumar, that's my first project using Context.
Here is the code fixed _app.tsx
import type { AppProps } from 'next/app';
import GlobalStyle from '../styles/global';
import { FavoritesProvider } from '../contexts/favoritesContext';
import { SortListProvider } from '../contexts/sortListContext';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<GlobalStyle />
<FavoritesProvider>
<SortListProvider>
<Component {...pageProps} />
</SortListProvider>
</FavoritesProvider>
</>
);
}
Which technique is recommended to use when making a React App International?
I am thinking of coding the following:
Create a React Context "Languages"
Create different modules exporting a map with all strings for each language "pt", "en", "fr", "sp", "it"...
Try to load the default language from AsyncStorage in my Splash Screen, using a method provided by the Languages Context Provider.
If not found, get the user language based on his location.
For me this has sense, but there might be other easier ways to achieve the same, feeling more profesional.
// This is my Languages Context
import React, { createContext, useState } from "react";
import * as Localization from "expo-localization";
import { VALID_LANGUAGES } from "../../utils/languages/constants"; // "English", "Spanish", "Portuguese"...
import languages from "../../languages"; // en, sp, pt, ... modules with all texts (key, value)
import AsyncStorage from "../../lib/async-storage/AsyncStorage";
const LanguagesContext = createContext(null);
export default LanguagesContext;
export function LanguagesProvider({ children }) {
const [language, setLanguage] = useState(undefined);
const loadLanguage = async () => {
// Get the default language from async storage
const language = await AsyncStorage.getData("language");
// TODO - if undefined -> get user location and use the corresponding lang
// TODO - if not supported, use english
if (!language) {
setLanguage(VALID_LANGUAGES[0]);
}
};
const changeLanguage = async (language) => {
// Update state
setLanguage(language);
// Save language in async storage
await AsyncStorage.storeData("language", language);
};
return (
<LanguagesContext.Provider
value={{ language, loadLanguage, changeLanguage }}
>
{children}
</LanguagesContext.Provider>
);
}
export { LanguagesProvider };
What I am doing is using the method "loadLanguage" in my Splash Screen Component, something that ensures the app to be ready for use before rendering any content. What do you think about this technique?
How can I use the texts in the app? I thought to make another method getAppTexts() in the Context provider, just to return the correct map from my languages modules ("en", "it", "pt", ...)
Any library and an example?
Thanks you.
First you just init a new react-native project
$ npx react-native init rn_example_translation
i would like to prefer to create a src folder to put all our JS code so we modify the index.js at the project root dir below like this:
import {AppRegistry} from 'react-native';
import App from './src/App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
Manage translations
We will translate the app using 'i18n-js' module so we install it with:
$ npm install --save i18n-js
Then create a file 'i18n.js' with:
import {I18nManager} from 'react-native';
import i18n from 'i18n-js';
import memoize from 'lodash.memoize';
export const DEFAULT_LANGUAGE = 'en';
export const translationGetters = {
// lazy requires (metro bundler does not support symlinks)
en: () => require('./assets/locales/en/translations.json'),
fr: () => require('./assets/locales/fr/translations.json'),
};
export const translate = memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key),
);
export const t = translate;
export const setI18nConfig = (codeLang = null) => {
// fallback if no available language fits
const fallback = {languageTag: DEFAULT_LANGUAGE, isRTL: false};
const lang = codeLang ? {languageTag: codeLang, isRTL: false} : null;
const {languageTag, isRTL} = lang ? lang : fallback;
// clear translation cache
translate.cache.clear();
// update layout direction
I18nManager.forceRTL(isRTL);
// set i18n-js config
i18n.translations = {[languageTag]: translationGetters[languageTag]()};
i18n.locale = languageTag;
return languageTag;
};
Then Create Empty Translations files like below:
'./src/assets/locales/en/translations.json' and './src/assets/locales/fr/translations.json'
now we can translate app JS string in french and english just like below:
i18n.t('Hello world!')
Switch locale in app
Now we'll setup a react context to keep the current user language and a switch to give the option to the user to change the language. It is cool to translate the strings but the translated strings have to match with user language.
To keep current user language along the app with a react context, we need to create a file 'LocalisationContext.js' in context folder with:
import React from 'react';
const LocalizationContext = React.createContext();
export default LocalizationContext;
Now in your App.js
import React, {useEffect, useCallback} from 'react';
import {StyleSheet} from 'react-native';
import * as i18n from './i18n';
import LocalizationContext from './context/LocalizationContext';
import HomeScreen from './HomeScreen';
const App: () => React$Node = () => {
const [locale, setLocale] = React.useState(i18n.DEFAULT_LANGUAGE);
const localizationContext = React.useMemo(
() => ({
t: (scope, options) => i18n.t(scope, {locale, ...options}),
locale,
setLocale,
}),
[locale],
);
return (
<>
<LocalizationContext.Provider value={localizationContext}>
<HomeScreen localizationChange={handleLocalizationChange} />
</LocalizationContext.Provider>
</>
);
};
and create the 'HomeScreen.js' file:
import React, {useContext} from 'react';
import {StyleSheet, SafeAreaView, Text, Button} from 'react-native';
import LocalizationContext from './context/LocalizationContext';
function HomeScreen(props) {
const {localizationChange} = props;
const {t, locale, setLocale} = useContext(LocalizationContext);
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>React-Native example translation</Text>
<Text style={styles.subtitle}>{t('Home screen')}</Text>
<Text style={styles.paragraph}>Locale: {locale}</Text>
{locale === 'en' ? (
<Button title="FR" onPress={() => localizationChange('fr')} />
) : (
<Button title="EN" onPress={() => localizationChange('en')} />
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
textAlign: 'center',
fontSize: 22,
marginBottom: 40,
},
subtitle: {
textAlign: 'center',
fontSize: 18,
marginBottom: 10,
},
paragraph: {
fontSize: 14,
marginBottom: 10,
},
langButton: {
flex: 1,
},
});
export default HomeScreen;
here we can translate the strings in js.
Handle localization system change
now we have to install localization module now:
$ npm install --save react-native-localize
then modify the app.js to this
import React, {useEffect, useCallback} from 'react';
import {StyleSheet} from 'react-native';
import * as RNLocalize from 'react-native-localize';
import * as i18n from './i18n';
import LocalizationContext from './context/LocalizationContext';
import HomeScreen from './HomeScreen';
const App: () => React$Node = () => {
const [locale, setLocale] = React.useState(i18n.DEFAULT_LANGUAGE);
const localizationContext = React.useMemo(
() => ({
t: (scope, options) => i18n.t(scope, {locale, ...options}),
locale,
setLocale,
}),
[locale],
);
const handleLocalizationChange = useCallback(
(newLocale) => {
const newSetLocale = i18n.setI18nConfig(newLocale);
setLocale(newSetLocale);
},
[locale],
);
useEffect(() => {
handleLocalizationChange();
RNLocalize.addEventListener('change', handleLocalizationChange);
return () => {
RNLocalize.removeEventListener('change', handleLocalizationChange);
};
}, []);
return (
<>
<LocalizationContext.Provider value={localizationContext}>
<HomeScreen localizationChange={handleLocalizationChange} />
</LocalizationContext.Provider>
</>
);
};
then 'i18n.js' file like this:
import {I18nManager} from 'react-native';
import * as RNLocalize from 'react-native-localize';
import i18n from 'i18n-js';
import memoize from 'lodash.memoize';
export const DEFAULT_LANGUAGE = 'en';
export const translationGetters = {
// lazy requires (metro bundler does not support symlinks)
en: () => require('./assets/locales/en/translations.json'),
fr: () => require('./assets/locales/fr/translations.json'),
};
export const translate = memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key),
);
export const t = translate;
export const setI18nConfig = (codeLang = null) => {
// fallback if no available language fits
const fallback = {languageTag: DEFAULT_LANGUAGE, isRTL: false};
const lang = codeLang ? {languageTag: codeLang, isRTL: false} : null;
# Use RNLocalize to detect the user system language
const {languageTag, isRTL} = lang
? lang
: RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
fallback;
// clear translation cache
translate.cache.clear();
// update layout direction
I18nManager.forceRTL(isRTL);
// set i18n-js config
i18n.translations = {[languageTag]: translationGetters[languageTag]()};
i18n.locale = languageTag;
return languageTag;
};
Generate translations
in order to generate language files you can use i18next-scanner.
we need to install it globally
npm install -g i18next-scanner
create a 'i18next-scanner.config.js' file at your project dir root with:
const fs = require('fs');
const chalk = require('chalk');
module.exports = {
input: [
'src/**/*.{js,jsx}',
// Use ! to filter out files or directories
'!app/**/*.spec.{js,jsx}',
'!app/i18n/**',
'!**/node_modules/**',
],
output: './',
options: {
debug: false,
removeUnusedKeys: true,
func: {
list: ['i18next.t', 'i18n.t', 't'],
extensions: ['.js', '.jsx'],
},
trans: {
component: 'Trans',
i18nKey: 'i18nKey',
defaultsKey: 'defaults',
extensions: [],
fallbackKey: function (ns, value) {
return value;
},
acorn: {
ecmaVersion: 10, // defaults to 10
sourceType: 'module', // defaults to 'module'
// Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options
},
},
lngs: ['en', 'fr'],
ns: ['translations'],
defaultLng: 'en',
defaultNs: 'translations',
defaultValue: '__STRING_NOT_TRANSLATED__',
resource: {
loadPath: 'src/assets/locales/{{lng}}/{{ns}}.json',
savePath: 'src/assets/locales/{{lng}}/{{ns}}.json',
jsonIndent: 2,
lineEnding: '\n',
},
nsSeparator: false, // namespace separator
keySeparator: false, // key separator
interpolation: {
prefix: '{{',
suffix: '}}',
},
},
transform: function customTransform(file, enc, done) {
'use strict';
const parser = this.parser;
const options = {
presets: ['#babel/preset-flow'],
plugins: [
'#babel/plugin-syntax-jsx',
'#babel/plugin-proposal-class-properties',
],
configFile: false,
};
const content = fs.readFileSync(file.path, enc);
let count = 0;
const code = require('#babel/core').transform(content, options);
parser.parseFuncFromString(
code.code,
{list: ['i18next._', 'i18next.__']},
(key, options) => {
parser.set(
key,
Object.assign({}, options, {
nsSeparator: false,
keySeparator: false,
}),
);
++count;
},
);
if (count > 0) {
console.log(
`i18next-scanner: count=${chalk.cyan(count)}, file=${chalk.yellow(
JSON.stringify(file.relative),
)}`,
);
}
done();
},
};
Here we can use the command :
$ i18next-scanner
now it will generate prefill translation files
'./src/assets/locales/en/translations.json' and './src/assets/locales/fr/translations.json'.
we just need to change in these files by right translation
now run the App;
npx react-native run-android
It will run succesfully.
if you want to switch language then This is the top Solution. try this one.
i18n.locale = "ar";
i18n.reset();
I'm quite new to React Hooks/Context so I'd appreciate some help. Please don' t jump on me with your sharp teeth. I Checked other solutions and some ways i've done this before but can't seem to get it here with the 'pick from the list' way.
SUMMARY
I need to get the municipios list of names inside of my const 'allMunicipios'(array of objects) inside of my Search.js and then display a card with some data from the chosen municipio.
TASK
Get the data from eltiempo-net REST API.
Use Combobox async element from Elastic UI to choose from list of municipios.
Display Card (from elastic UI too) with some info of chosen municipio.
It has to be done with function components / hooks. No classes.
I'd please appreciate any help.
WHAT I'VE DONE
I've created my reducer, context and types files in a context folder to fecth all data with those and then access data from the component.
I've created my Search.js file. Then imported Search.js in App.js.
I've accesed the REST API and now have it in my Search.js
PROBLEM
Somehow I'm not beeing able to iterate through the data i got.
Basically i need to push the municipios.NOMBRE from api to the array const allMunicipios in my search.js component. But when i console log it it gives me undefined. Can;t figure out why.
I'll share down here the relevant code/components. Thanks a lot for whoever takes the time.
municipiosReducer.js
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
export default (state, action) => {
switch (action.type) {
case SEARCH_MUNICIPIOS:
return {
...state,
municipios: action.payload,
};
case GET_MUNICIPIO:
return {
...state,
municipio: action.payload,
};
case CLEAR_MUNICIPIOS:
return {
...state,
municipios: [],
};
case GET_WEATHER: {
return {
...state,
weather: action.payload,
};
}
default:
return state;
}
};
municipiosContext.js
import { createContext } from "react";
const municipiosContext = createContext();
export default municipiosContext;
MunicipiosState.js
import React, { createContext, useReducer, Component } from "react";
import axios from "axios";
import MunicipiosContext from "./municipiosContext";
import MunicipiosReducer from "./municipiosReducer";
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
const MunicipiosState = (props) => {
const initialState = {
municipios: [],
municipio: {},
};
const [state, dispatch] = useReducer(MunicipiosReducer, initialState);
//Search municipios
//In arrow functions 'async' goes before the parameter.
const searchMunicipios = async () => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios`
// 08 means barcelona province. This should give me the list of all its municipios
);
dispatch({
type: SEARCH_MUNICIPIOS,
payload: res.data.municipios,
});
};
//Get Municipio
const getMunicipio = async (municipio) => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios/${municipio.CODIGOINE}`
//CODIGOINE is in this REST API kind of the ID for each municipio.
//I intent to use this later to get the weather conditions from each municipio.
);
dispatch({ type: GET_MUNICIPIO, payload: res.municipio });
};
const dataMunicipiosArray = [searchMunicipios];
//Clear Municipios
const clearMunicipios = () => {
dispatch({ type: CLEAR_MUNICIPIOS });
};
return (
<MunicipiosContext.Provider
value={{
municipios: state.municipios,
municipio: state.municipio,
searchMunicipios,
getMunicipio,
clearMunicipios,
dataMunicipiosArray,
}}
>
{props.children}
</MunicipiosContext.Provider>
);
};
export default MunicipiosState;
Search.js
import "#elastic/eui/dist/eui_theme_light.css";
import "#babel/polyfill";
import MunicipiosContext from "../contexts/municipiosContext";
import MunicipiosState from "../contexts/MunicipiosState";
import { EuiComboBox, EuiText } from "#elastic/eui";
import React, { useState, useEffect, useCallback, useContext } from "react";
const Search = () => {
const municipiosContext = useContext(MunicipiosContext);
const { searchMunicipios, municipios } = MunicipiosState;
useEffect(() => {
return municipiosContext.searchMunicipios();
}, []);
const municipiosFromContext = municipiosContext.municipios;
const bringOneMunicipio = municipiosContext.municipios[0];
let municipiosNames = municipiosFromContext.map((municipio) => {
return { label: `${municipio.NOMBRE}` };
});
console.log(`municipiosFromContext`, municipiosFromContext);
console.log(`const bringOneMunicipio:`, bringOneMunicipio);
console.log(`municipiosNames:`, municipiosNames);
const allMunicipios = [
{ label: "santcugat" },
{ label: "BARCELONETA" },
{ label: "BARCE" },
];
const [selectedOptions, setSelected] = useState([]);
const [isLoading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
let searchTimeout;
const onChange = (selectedOptions) => {
setSelected(selectedOptions);
};
// combo-box
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, []);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
return (
<div>
<EuiComboBox
placeholder="Search asynchronously"
async
options={options}
selectedOptions={selectedOptions}
isLoading={isLoading}
onChange={onChange}
onSearchChange={onSearchChange}
/>
<button>Lista de municipios</button>
</div>
);
};
export default Search;
also the
Home.js
import React, { useState } from "react";
import { EuiComboBox, EuiText } from "#elastic/eui";
// import { DisplayToggles } from "../form_controls/display_toggles";
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import Search from "./Search";
import MunicipioCard from "./MunicipioCard";
const Home = () => {
return (
<div>
<EuiText grow={false}>
<h1>Clima en la provincia de Barcelona</h1>
<h2>Por favor seleccione un municipio</h2>
</EuiText>
<Search />
<MunicipioCard />
</div>
);
};
export default Home;
App.js
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import { EuiText } from "#elastic/eui";
import React from "react";
import Home from "./components/Home";
import MunicipiosState from "./contexts/MunicipiosState";
import "./App.css";
function App() {
return (
<MunicipiosState>
<div className="App">
<EuiText>
<h1>App Component h1</h1>
</EuiText>
<Home />
</div>
</MunicipiosState>
);
}
export default App;
You are using forEach and assigning the returned value to a variable, however forEach doesn't return anything. You should instead use map
let municipiosNames = municipiosFromContext.map((municipio) => {
return `label: ${municipio.NOMBRE}`;
});
As per your comment:
you data is loaded asynchronously, so it won't be available on first render and since functional components depend on closures, you onSearchChange function takes the value from the closure at the time of creation and even if you have a setTimeout within it the updated value won't reflect
The solution here is to add municipiosFromContext as a dependency to useEffect
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, [municipiosFromContext]);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
I have the following component in my React-Native application. I have been asked to write a unit test using jest and enzyme for this, but I'm new to unit test. So, how to break this down and write proper tests is beyond my knowledge. Could someone help me with this??
import React, { Component } from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';
import { Button, Loader, ScreenContainer } from '../../../../../components';
import {
decodeBase64,
hasWordsInString,
setFirstAndFamilyName,
} from '../../../../../library/Utils';
import { url, searchWords, signUpMethods } from '../../../../../config';
import { SIGN_UP_FORM } from '../../../../constants/forms';
// tslint:disable-next-line: max-line-length
const response = 'some-token';
class MyWebView extends Component {
state = {
loaderStatus: true,
};
stopLoader = () => {
this.setState({ loaderStatus: false });
}
startLoader = () => {
this.setState({ loaderStatus: true });
}
displayLoader = () => {
const { loaderStatus } = this.state;
return loaderStatus && <Loader />;
}
render() {
const { navigation, addFormData, setSignUpMethod } = this.props;
return (
<View style={{ flex: 1 }}>
<WebView
source={{ uri: url }}
onLoadStart={() => this.startLoader()}
onLoad={() => this.stopLoader()}
onLoadEnd={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
if (nativeEvent.title === 'Consent Platform') {
if (hasWordsInString(nativeEvent.url, searchWords)) {
const { address, ...rest } = decodeBase64(response).data;
const userName = setFirstAndFamilyName(rest.name);
rest.firstName = userName.firstName;
rest.familyName = userName.familyName;
addFormData({ form: SIGN_UP_FORM, data: { values: { ...rest, ...address } } });
setSignUpMethod(signUpMethods.MY_INFO);
navigation.replace('ConfirmName', rest);
}
}
}}
/>
{this.displayLoader()}
</View>
);
}
}
export default MyWebView;
How can I write some proper unit tests for the above code using jest and enzyme? What are the principles that I have to follow? What makes a unit test a better one?
Here is a shallow test, then you can pass in your own dummy data and compare the snapshots, so basically if anyone changes any of your custom components that are used in this component your test will fail
import "react-native"
import React from "react"
import { shallow } from "enzyme"
import MyWebView from "../../App/Components/MyWebView"
test("Should render CustomHeader", () => {
const wrapper = shallow(<MyWebView />)
expect(wrapper).toMatchSnapshot()
})
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)