Dropdown menu to switch languages in React? - reactjs

i've been trying to make a dropdown menu component on react to use on my portfolio's landing page. I need the menu to change the text on the website from english to my native language and viceversa so it should contain the options "EN" and "IT" and an image of the two flags, like in the picture i attached.
here's how it looks currently and it works too but i can't/don't know how to add the flags with the < select > element.
import React, { useContext } from "react";
import { languageOptions } from "../languages";
import { LanguageContext } from "../container/Language";
export default function LanguageSelector() {
const { userLanguage, userLanguageChange } = useContext(LanguageContext);
const handleLanguageChange = (e) => userLanguageChange(e.target.value);
return (
<select
onChange={handleLanguageChange}
value={userLanguage}
>
{Object.entries(languageOptions).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
);
}
now:
vs how i want it to look like: (https://i.stack.imgur.com/QDugw.png)
here's what's inside languageContext :
import React, { useState, createContext, useContext } from "react";
import { languageOptions, dictionaryList } from "../languages";
export const LanguageContext = createContext({
userLanguage: "en",
dictionary: dictionaryList.en,
});
export function LanguageProvider({ children }) {
const defaultLanguage = window.localStorage.getItem("rcml-lang");
const [userLanguage, setUserLanguage] = useState(defaultLanguage || "en");
const provider = {
userLanguage,
dictionary: dictionaryList[userLanguage],
userLanguageChange: (selected) => {
const newLanguage = languageOptions[selected] ? selected : "en";
setUserLanguage(newLanguage);
window.localStorage.setItem("rcml-lang", newLanguage);
},
};
return (
<LanguageContext.Provider value={provider}>
{children}
</LanguageContext.Provider>
);
}
export function Text({ tid }) {
const languageContext = useContext(LanguageContext);
return languageContext.dictionary[tid] || tid;
}
and in languageOptions :
import en from "./en.json";
import it from "./it.json";
export const dictionaryList = { en, it };
export const languageOptions = {
en: "EN",
it: "IT",
};

In React I don't think the option element will take an img as a child element (nor will the select element). You could use an unordered list. Crude example below.
const {useState} = React;
const languageOptions = [
{
id: "en",
name: "English",
flagimg:
"https://upload.wikimedia.org/wikipedia/commons/8/83/Flag_of_the_United_Kingdom_%283-5%29.svg",
},
{
id: "it",
name: "Italian",
flagimg: "https://upload.wikimedia.org/wikipedia/en/0/03/Flag_of_Italy.svg",
},
];
const defaultLangFlag = <img src={languageOptions[0].flagimg} height="30" width="30" alt="nope" />;
const Example = () => {
const [cssDisplay, setCssDisplay] = useState('none');
const [langFlag, setLangFlag] = useState(defaultLangFlag)
const showDropdown = () => {
if (cssDisplay === 'none'){
setCssDisplay('block');
} else {
setCssDisplay('none');
}
};
const selectListItem = (event) => {
handleLanguageChange(event);
setCssDisplay('none');
setLangFlag(<img src={event.target.src} height="30" width="30" alt="nope" />)
};
const handleLanguageChange = (event) => userLanguageChange(event);
const userLanguageChange = (event) => {
console.log("Here grab event.target.id and propagate lang change to app");
}
return (
<div>
<button onClick={showDropdown} >{langFlag}</button>
<ul style={{ display: cssDisplay }}>
{languageOptions.map((lang) => (
<li id={lang.id} key={lang.id} disabled>
<img onClick={selectListItem} src={lang.flagimg} height="30" width="30" alt="flagpic" id={lang.id} />
{lang.name}
</li>
))}
</ul>
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Example />
);
li {list-style: none;)
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Related

Could'nt return maped item inside jsx component

Hey Guys I am trying to display a list of items according to categories,
this is my json structure. I want to display objects according to itemCategory. for e.g if there are two or more pizza they should come under one category heading.
{
"itemid": 3,
"itemName": "Veg OverLoaded",
"itemPrice": 300.0,
"itemDescription": "chessy, tasty, covered with fresh veggies",
"itemCategory": "pizza"
},
for this i created a map of objects and passed the data according to category as key.
import React, { forwardRef,useState } from 'react';
import MenuItem from './MenuItem';
import './styles.css';
import Category from '../../Home/Category'
const NewMap = new Map()
const Menu = forwardRef(({ list }, ref) => (
<>
<main ref={ref} >
{Object.values(list).map((k) => {
if (NewMap.has(k.itemCategory)){
const itemList = NewMap.get(k.itemCategory);
const newItemList = [...itemList, k];
NewMap.set(k.itemCategory, newItemList);
}else{
NewMap.set(k.itemCategory , [k]);
}
})}
<MenuItem itemMap = {NewMap}/>
</main>
</>
));
i am passing the map to MenuItem as props and trying to display objects here
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import {
cartAddItem,
cartRemoveItem,
} from '../../../../redux/cart/cart.action';
import {
selectCartItems,
selectCartItemsCount,
} from '../../../../redux/cart/cart.selector';
import ButtonAddRemoveItem from '../../ButtonAddRemoveItem';
import './styles.css';
import Accordion from 'react-bootstrap/Accordion'
import useFetchData from './newData'
const MenuItem = ({
itemMap,
cartCount,
cartList,
cartAddItem,
cartRemoveItem,
}) => {
const {
data,
loading,
} = useFetchData();
const handleQuantity = () => {
let quantity = 0;
if (cartCount !== 0) {
const foundItemInCart = cartList.find((item) => item.itemid === 1);
if (foundItemInCart) {
quantity = foundItemInCart.quantity;
}
}
return quantity;
};
return (
<>
{itemMap.forEach((key, value) => {
{console.log(value)}
<div className='container-menu' >
{console.log(value)}
<ul>
{Object.values(key).map((blah) => {
<li>
<h1>{blah.itemName}</h1>
<div className='item-contents'>
{blah.itemName}
<p className='item-contents' style={{ float: "right", fontSize: "12" }}> ₹ {blah.itemPrice}</p>
<div>
<p className='description'>{blah.itemDescription}</p>
<ButtonAddRemoveItem
quantity={handleQuantity()}
handleRemoveItem={() => cartRemoveItem(blah)}
handleAddItem={() => cartAddItem(blah)}
/>
</div>
</div>
</li>
})}
</ul>
</div>
})}
</>
);
};
const mapStateToProps = createStructuredSelector({
cartCount: selectCartItemsCount,
cartList: selectCartItems,
});
const mapDispatchToProps = (dispatch) => ({
cartAddItem: (item) => dispatch(cartAddItem(item)),
cartRemoveItem: (item) => dispatch(cartRemoveItem(item)),
});
export default connect(mapStateToProps, mapDispatchToProps)(MenuItem);
export default Menu;
i am able to console.log itemName but i am unable to display it inside jsx component. Any reason why
? what am i missing here
The foreach loop should return JSX. In your case there is no return. I suggest removing the curly brackets.
WRONG:
itemMap.forEach((key, value) => {
<>
</>
})
CORRECT:
itemMap.forEach((key, value) => (
<>
</>
))
That's valid for all the loops inside your JSX.
To return an array of elements, you should use map instead of forEach like below code, because forEach loop returns undefined, while map always returns an array.
{itemMap.map((key, value) => {
return (<div className='container-menu'>
...
</div>)
}
}
or
{itemMap.map((key, value) => (<div className='container-menu'>
...
</div>)
}

Component not rerendering after axios Get (React)

I'm trying to render List of items of my DB using React.Context.
All my request work pretty well.
when i console log my states first I get an empty array and then array with the data that I need but my component is not updating. I have to go to another page an then go back to this page to get the data. I don't really understand why... here are my files..
ArticlesContext.js :
import React, { useState, createContext, useEffect } from 'react';
import axios from 'axios'
export const ArticlesContext = createContext();
export function ArticlesProvider(props) {
const [articles, setArticles] = useState([]);
const [user, setUser] =useState(0)
async function getArticles () {
await axios.get(`/api/publicItem`)
.then(res => {
setArticles(res.data);
})
}
useEffect( () => {
getArticles()
}, [user])
console.log(articles);
return (
<ArticlesContext.Provider value={[articles, setArticles]}>
{props.children}
</ArticlesContext.Provider>
);
}
Inventaire.js :
import React, { useContext, useEffect, useState } from 'react';
import './Inventaire.css';
import { ArticlesContext } from '../../../context/ArticlesContext';
import DeleteAlert from './Delete/Delete';
import Modify from './Modify/Modify';
import Filter from './Filter/Filter';
import axios from 'axios'
import Crud from '../../Elements/Articles/Crud/Crud';
import List from './List/List';
export default function Inventaire() {
const [articles, setArticles] = useContext(ArticlesContext);
const [filter, setFilter] = useState(articles)
console.log(articles);
//list for Inputs
const cat = articles.map(a => a.category.toLowerCase());
const categoryFilter = ([...new Set(cat)]);
const gender = articles.map(a => a.gender.toLowerCase());
const genderFilter = ([...new Set(gender)]);
//Event Listenner
//Uncheck All checkboxes
function UncheckAll() {
const el = document.querySelectorAll("input.checkboxFilter");
console.log(el);
for (var i = 0; i < el.length; i++) {
var check = el[i];
if (!check.disabled) {
check.checked = false;
}
}
}
//SearchBar
const searchChange = (e) => {
e.preventDefault();
const stuff = articles.filter((i) => {
return i.name.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
UncheckAll(true)
}
const Types = (e) => {
if (e.target.checked === true) {
const stuff = filter.filter((i) => {
return i.category.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
console.log(articles);
} else if (e.target.checked === false) {
setFilter(articles)
}
}
const Gender = (e) => {
if (e.target.checked === true) {
const stuff = filter.filter((i) => {
console.log(i.category, e.target.value);
return i.gender.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
} else if (e.target.checked === false) {
setFilter(articles)
}
}
return (
<div className="inventaireContainer">
<input type="text" placeholder="Recherche un Article" onChange={searchChange} />
<div className="inventaireMenu">
<Crud />
<Filter
filter={Types}
categorys={categoryFilter}
genre={genderFilter}
target={Gender}
/>
</div>
<List filter={filter} articles={articles}/>
</div>
)
}
List.js :
import React from 'react';
import DeleteAlert from '../Delete/Delete';
import Modify from '../Modify/Modify';
export default function List({ filter, articles }) {
return (
<div>
{filter.map((details, i) => {
return (
<div className="inventaireBlock" >
<div className="inventaireGrid">
<div className="inventaireItemImg">
<img src={details.image} alt="ItemImg" />
</div>
<h2>{details.name}</h2>
<h3>{details.category}</h3>
<h3>{details.gender}</h3>
<div>
<p>S :{details.sizes[0].s}</p>
<p>M :{details.sizes[0].m}</p>
<p>L :{details.sizes[0].l}</p>
<p>XL :{details.sizes[0].xl}</p>
</div>
<h2> Prix: {details.price}</h2>
<div className="modify">
<Modify details={details._id} />
</div>
<div className="delete" >
<DeleteAlert details={details._id} articles={articles} />
</div>
</div>
</div>
)
})}
</div>
)
}
Thanks for your time

Having React Context in Separate File, Can't Get Component to Not re-render

I've got a simple example of React Context that uses useMemo to memoize a function and all child components re-render when any are clicked. I've tried several alternatives (commented out) and none work. Please see code at stackblitz and below.
https://stackblitz.com/edit/react-yo4eth
Index.js
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import { GlobalProvider } from "./GlobalState";
function App() {
return (
<GlobalProvider>
<Hello />
</GlobalProvider>
);
}
render(<App />, document.getElementById("root"));
GlobalState.js
import React, {
createContext,useState,useCallback,useMemo
} from "react";
export const GlobalContext = createContext({});
export const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
]);
const clickFunction = useCallback((speakerIdClicked) => {
setSpeakerList((currentState) => {
return currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
},[]);
// const provider = useMemo(() => {
// return { clickFunction: clickFunction, speakerList: speakerList };
// }, []);
//const provider = { clickFunction: clickFunction, speakerList: speakerList };
const provider = {
clickFunction: useMemo(() => clickFunction,[]),
speakerList: speakerList,
};
return (
<GlobalContext.Provider value={provider}>{children}</GlobalContext.Provider>
);
};
Hello.js
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id}></Speaker>;
})}
</div>
);
};
Speaker.js
import React, { useContext } from "react";
import { GlobalContext } from "./GlobalState";
export default React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const { clickFunction } = useContext(GlobalContext);
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});
Couple of problems in your code:
You already have memoized the clickFunction with useCallback, no need to use useMemo hook.
You are consuming the Context in Speaker component. That is what's causing the re-render of all the instances of Speaker component.
Solution:
Since you don't want to pass clickFunction as a prop from Hello component to Speaker component and want to access clickFunction directly in Speaker component, you can create a separate Context for clickFunction.
This will work because extracting clickFunction in a separate Context will allow Speaker component to not consume GlobalContext. When any button is clicked, GlobalContext will be updated, leading to the re-render of all the components consuming the GlobalContext. Since, Speaker component is consuming a separate context that is not updated, it will prevent all instances of Speaker component from re-rendering when any button is clicked.
Demo
const GlobalContext = React.createContext({});
const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = React.useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true }
]);
return (
<GlobalContext.Provider value={{ speakerList, setSpeakerList }}>
{children}
</GlobalContext.Provider>
);
};
const ClickFuncContext = React.createContext();
const ClickFuncProvider = ({ children }) => {
const { speakerList, setSpeakerList } = React.useContext(GlobalContext);
const clickFunction = React.useCallback(speakerIdClicked => {
setSpeakerList(currentState => {
return currentState.map(rec => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
}, []);
return (
<ClickFuncContext.Provider value={clickFunction}>
{children}
</ClickFuncContext.Provider>
);
};
const Speaker = React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const clickFunction = React.useContext(ClickFuncContext)
return (
<div>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</div>
);
});
function SpeakerList() {
const { speakerList } = React.useContext(GlobalContext);
return (
<div>
{speakerList.map(rec => {
return (
<Speaker speaker={rec} key={rec.id} />
);
})}
</div>
);
};
function App() {
return (
<GlobalProvider>
<ClickFuncProvider>
<SpeakerList />
</ClickFuncProvider>
</GlobalProvider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can also see this demo on StackBlitz
this will not work if you access clickFuntion in children from provider because every time you updating state, provider Object will be recreated and if you wrap this object in useMemolike this:
const provider = useMemo(()=>({
clickFunction,
speakerList,
}),[speakerList])
it will be recreated each time clickFunction is fired.
instead you need to pass it as prop to the children like this:
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList,clickFunction } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id} clickFunction={clickFunction }></Speaker>;
})}
</div>
);
};
and for provider object no need to add useMemo to the function clickFunction it's already wrapped in useCallback equivalent to useMemo(()=>fn,[]):
const provider = {
clickFunction,
speakerList,
}
and for speaker component you don't need global context :
import React from "react";
export default React.memo(({ speaker,clickFunction }) => {
console.log("render")
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});

React Function Components - following parent component state

I'm new to react and I'm trying component functional style.
I have simple todo list. I would like to strike out todo item from list using style property. From Chrome debug mode I do not see immediate reaction on checkbox changes, also Item is not striked out... It seams to me, that it is problem with how I manage state of components. I would appreciate some guidance.
App.js
import React, {useState} from 'react';
import Todos from "./components/Todos";
import './App.css'
const App = () => {
const [todos, setTodos] = useState(
[
{id: 1, title: 'Take out the trash', completed: false},
{id: 2, title: 'Dinner with wife', completed: false},
{id: 3, title: 'Meeting boss', completed: false}
]
);
const markComplete = id => {
console.log((new Date()).toString());
todos.map(todo => {
if (todo.id === id) {
todo.completed = ! todo.completed;
}
return todo;
});
setTodos(todos);
};
return (
<div className="App">
<Todos todos={todos} markComplete={markComplete}/>
</div>
);
};
export default App;
Todos.js
import React from "react";
import TodoItem from "./TodoItem";
const Todos = ({todos, markComplete}) => {
return (
todos.map(todo => (
<TodoItem key={todo.id} todoItem={todo} markComplete={markComplete} />
))
);
};
export default Todos;
TodoItem.js
import React from "react";
const TodoItem = ({todoItem, markComplete}) => {
const getStyle = () => {
console.log("style: " + todoItem.completed);
return {
background: '#f4f4f4',
padding: '10px',
borderBottom: '1px #ccc dotted',
textDecoration: todoItem.completed ? 'line-through' : 'none'
}
};
return (
<div style={getStyle()}>
<p>
<input type="checkbox" onChange={markComplete.bind(this, todoItem.id)}/>{' '}
{todoItem.title}
</p>
</div>
);
};
export default TodoItem;
I expect that this getStyle() will follow state... somehow...
Don't mutate state. In markComplete function, you are mutating the todos array directly. Change your function like this to avoid mutation
const markComplete = id => {
console.log((new Date()).toString());
let newTodos = todos.map(todo => {
let newTodo = { ...todo };
if (newTodo.id === id) {
newTodo.completed = !newTodo.completed;
}
return newTodo;
});
setTodos(newTodos);
};
Array.prototype.map() returns a new Array, which you are throwing away. You need to use the new array, e.g.:
const markComplete = id => {
...
setTodos(totos.map(...))

React State returning out of sync data

I have the below component which is using React hooks:
import React, {useState} from 'react';
// import components
import TagManagementRow from './TagManagementRow';
const TagManagementList = (props) => {
const [tagData, setTagData] = useState(props.data);
const deleteAction = (id) => {
// Call to backend to delete tag
const currentData = [];
for( var i = 0; i <= tagData.length; i++){
if(i < tagData.length && tagData[i].id !== id) {
currentData.push(tagData[i]);
}
if(i === tagData.length) setTagData(currentData);
};
};
return (
<ul className="tagManagement">
{tagData.map( (tag,i) => {
return <TagManagementRow name={tag.name} key={i} id={tag.id} delete={() => deleteAction(tag.id)} />
})}
</ul>
);
}
export default TagManagementList;
It renders 4 TagManagementRow child components, each have a delete button. When I click the delete button, everything looks good if I log out the changed state to the console, however, in the actual browser the last item in the list is removed. I feel like its some kind of render/timing issue but I can't seem to figure it out. Any assistance from those who better understand hooks would be greatly appreciated.
By the way, here is the code for the TagManagementRow component:
import React, { useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
const TagManagementRow = (props) => {
const [editTag, setEdit] = useState(false);
const [tagName, setTagName] = useState(props.name);
const [tempName, setTempName] = useState('');
const handleEdit = (e) => {
setTempName(e.target.value);
};
const switchToEdit = () => {
setEdit(!editTag);
}
const saveEdit = () => {
setTagName(tempName);
setTempName('');
switchToEdit();
}
return (
<li>
<span>
{tagName}
<FontAwesomeIcon icon={["fas","pen"]} onClick={switchToEdit} />
</span>
<span>
<FontAwesomeIcon icon={["fas","trash-alt"]} onClick={props.delete} />
</span>
</li>
);
}
export default TagManagementRow;
Instead of updating the state inside the loop, you could use filter to filter out the object with the matching id.
Also make sure you use tag.id as key instead of the array index, since that will change when you remove an element.
const { useState } = React;
const TagManagementList = props => {
const [tagData, setTagData] = useState(props.data);
const deleteAction = id => {
setTagData(prevTagData => prevTagData.filter(tag => tag.id !== id));
};
return (
<ul className="tagManagement">
{tagData.map((tag, i) => {
return (
<TagManagementRow
name={tag.name}
key={tag.id}
id={tag.id}
delete={() => deleteAction(tag.id)}
/>
);
})}
</ul>
);
};
const TagManagementRow = props => {
const [editTag, setEdit] = useState(false);
const [tagName, setTagName] = useState(props.name);
const [tempName, setTempName] = useState("");
const handleEdit = e => {
setTempName(e.target.value);
};
const switchToEdit = () => {
setEdit(!editTag);
};
const saveEdit = () => {
setTagName(tempName);
setTempName("");
switchToEdit();
};
return (
<li>
{tagName}
<button onClick={props.delete}>Delete</button>
</li>
);
};
ReactDOM.render(
<TagManagementList data={[{ id: 1, name: "foo" }, { id: 2, name: "bar" }]} />,
document.getElementById("root")
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

Resources