React Context not updating when calling the function - reactjs

I Implement multiple language on my website, using different json file for translation.
I want to import different json based on user select.
I use context Api to use Json file But
Initial context doesn't change when calling userLanguageChange function,
import {
dictionaryList,
languageOptions,
} from "../translations/LanguageSelector";
import React, { createContext, useState, useEffect } from "react";
export const LanguageBisContext = createContext({
userLanguage: "it",
dictionary: dictionaryList.it,
});
export function LanguageBisProvider({ children }) {
const defaultLanguage = window.localStorage.getItem("rcml-lang");
const [dictionary, setDictionary] = useState(dictionaryList[defaultLanguage]);
const [userLanguage, setUserLanguage] = useState(defaultLanguage);
const provider = {
userLanguage,
dictionary: dictionaryList[userLanguage],
userLanguageChange: (selected) => {
console.log("questa รจ la lingua selezionata:" + selected);
const newLanguage = languageOptions[selected] ? selected : "en";
setUserLanguage(newLanguage);
console.log(dictionaryList[newLanguage]);
/* window.localStorage.setItem("rcml-lang", newLanguage); */
},
};
return (
<LanguageBisContext.Provider value={provider}>
{children}
</LanguageBisContext.Provider>
);
}
then I use this component to change language
export default function LanguageSelector() {
const { userLanguage, userLanguageChange } = useContext(LanguageBisContext);
console.log(userLanguage);
// set selected language by calling context method
const handleLanguageChange = (e) => {
console.log(e.target.value);
userLanguageChange(e.target.value);
};
return (
<select onChange={handleLanguageChange} value={userLanguage}>
{Object.entries(languageOptions).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
);
}

Related

rendering page contents depending on language set in AsyncStorage React Native

I am creating an application that displays information in three languages without using any APIS. In the settings page , user can click change to spanish button which will be stored in the ASYNC storage .I am new to react native and would like to know whether this is best practice .
ACCOUNT SETTINGS
import { View, Text ,Button} from 'react-native'
import React, { useState , useEffect} from 'react'
import {AsyncStorage} from 'react-native';
const Account = () => {
const setspanish=()=> {
const lanugage = {
language:"spanish",
}
AsyncStorage.getItem('lang').then((datacart)=>{
if (datacart !== null) {
// We have data!!
const lang = JSON.parse(datacart)
lang.push(lanugage)
AsyncStorage.setItem('lang',JSON.stringify(lang));
}
else{
const lang = []
lang.push(lanugage)
AsyncStorage.setItem('lang',JSON.stringify(lang));
}
alert("ChangedLnag")
})
.catch((err)=>{
alert(err)
})
}
return (
<View>
<Button onPress={setspanish} title="spanish"/>
</View>
)
}
export default Account
I have create a state in different pages , but none update automatically . Once i navigate to other pages , i have used ternary operators to render out depending on the state which recieves async storage language value but none works .
You should set it on the context and save it, and for first time you should take and set it again to context
I write example about that:
interface IConfig{
lang: "en" | "lalala";
}
interface IContextConfig{
config: IConfig;
setConfig?: (val: any) => void;
}
export const ContextConfigApp = React.createContext<IContextConfig>({
config: {lang: "en"},
});
interface IPropsProvider{
init?: IConfig;
children: React.ReactNode;
}
const Provider = ({init = {lang: "en"}}) => {
const [config,setConfig] = useState<IConfig>(init);
useEfect(() => {
AsyncStorage.setItem('config',JSON.stringify(config));
},[config]);
useEfect(() => {
(async () => {
const tmp = await AsyncStorage.getItem('config');
if(!!tmp && tmp) setConfig({...config,...JSON.parse(tmp)});
})();
},[]);
return (
<ContextConfigApp.Provider value={{config,setConfig}}>
{children}
</ContextConfigApp.Provider>
)
}
const App = () => {
return (
<Provider>
<Header />
</Provider>
)
}
const Header = () => {
const {setConfig,config} = useContext(ContextConfigApp);
return (
<Button onPress={() => {
setConfig({...config,lang: "en"})
}}>EN</Button>
)
}
Usage
const Example = () => {
const {config} = useContext(ContextConfigApp)
return (
<Text>{config.lang}</Text>
)
}
this is just example I hope it help you

Filtering does not work automatically when changing the sorting

I am building an application that has a database of videos that can be filtered by category and sorted by rating.
Filtering works after changing the options. However, when I change the categories of the video the filtering does not start automatically. I added useEffect but I don't know what else I can change and why it happens. Please help how to make the sorting not disappear when changing the cateogry.
UPDATE:
import * as _ from "lodash";
import { useEffect, useState } from "react";
import { getAllPrograms } from "../../helpers/getData";
import { TVProgram } from "../../models/models";
import Filters from "../Filters/Filters";
import ProgramsList from "../ProgramsList/ProgramsList";
import Sorting from "../Sorting/Sorting";
import "./HomePage.scss";
const HomePage = () => {
const [programs, setPrograms] = useState<Array<TVProgram>>([]);
const [category, setCategory] = useState<string>("movie,series");
const [sortedPrograms, setSortedPrograms] = useState<TVProgram[]>(programs);
const getPrograms = async (category: string) => {
const programs = await getAllPrograms(category);
setPrograms(programs);
};
useEffect(() => {
getPrograms(category);
}, [category]);
const updateCategory = (categoryName: string): void => {
setCategory(categoryName);
console.log("catName", categoryName);
};
const updatePrograms = (sortedPrograms: TVProgram[]): void => {
setSortedPrograms(sortedPrograms);
console.log("sortedPrograms", sortedPrograms);
};
return (
<div className="container">
<div>
<Filters
updateCategory={updateCategory}
currentCategory={category}
></Filters>
<Sorting programs={programs} setPrograms={updatePrograms}></Sorting>
</div>
<ProgramsList programs={sortedPrograms}></ProgramsList>
</div>
);
};
export default HomePage;
import _ from "lodash";
import { ChangeEvent, useEffect, useState } from "react";
import { sortProgramsByOrder } from "../../helpers/helpers";
import { TVProgram } from "../../models/models";
import "./Sorting.scss";
interface SortingListProps {
programs: TVProgram[];
setPrograms: (programs: TVProgram[]) => void;
}
const Sorting = ({ programs, setPrograms }: SortingListProps) => {
const OPTIONS = ["imdb rating descending", "imdb rating ascending"];
const [selectedOption, setSelectedOption] = useState<string>("");
const [sortedPrograms, setSortedPrograms] = useState<TVProgram[]>([]);
useEffect(() => {
if (selectedOption === OPTIONS[0]) {
setSortedPrograms(sortProgramsByOrder(programs, "desc"));
} else if (selectedOption === OPTIONS[1]) {
setSortedPrograms(sortProgramsByOrder(programs, "asc"));
}
}, [selectedOption, programs]);
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
console.log();
setSelectedOption(event.target.value);
setPrograms(sortedPrograms);
};
return (
<div>
<select value={selectedOption} onChange={handleChange}>
<option selected>Sortuj</option>
{OPTIONS.map((option) => (
<option
key={option}
value={option}
selected={option === selectedOption}
>
{option}
</option>
))}
</select>
</div>
);
};
export default Sorting;
useEffect() is a hook that prevents updates to a variable except in specific cases. Any variable passed into the array at the end of the useEffect() hook will cause the code inside to be run again when its value changes. The problem looks, at first glance, to be in the following part of your code:
useEffect(() => {
if (selectedOption === OPTIONS[0]) {
sortPrograms(sortProgramsByOrder(programs, "desc"));
} else if (selectedOption === OPTIONS[1]) {
sortPrograms(sortProgramsByOrder(programs, "asc"));
}
}, [selectedOption]);
The [selectedOption] is telling the hook to only do the sorting if the sorting order has changed. However, you want to call this hook if the order or the contents changes. As such, you want to replace this array with [selectedOption, programs] so that changes to the contents of the programs variable will also lead to the sorting being re-run.
If programs is updated in the hook and also set by the hook, this leads to a recursive call which is not good. Instead, let's change the displayed value to be a new variable (defined with useState) called sortedPrograms. Then your hook should look like this:
useEffect(() => {
if (selectedOption === OPTIONS[0]) {
setSortedPrograms(sortProgramsByOrder(programs, "desc"));
} else if (selectedOption === OPTIONS[1]) {
setSortedPrograms(sortProgramsByOrder(programs, "asc"));
}
}, [selectedOption, programs]);

Using function defined in custom hook does not update some value in the state

i recently started using react and i'm trying to write a custom hook used for translating some ui elements. I'm not using a library because it is a larger project and it is expected to have a lot of custom loading/ translating parts.
Goal:
call the hook in every translatable component (in a very light syntax)
the hook will return a function used to translate elements in that component
So far, i tried this https://codesandbox.io/s/react-playground-forked-jt90ii?file=/MyComponent.js
Here is the custom hook:
import { useState, useEffect } from "react";
export default function useTranslation({ className, callbackFunction }) {
const [translations, setTranslations] = useState([]);
useEffect(() => {
async function fetchTranslationsForClass(
endpoint,
setTranslations,
className
) {
// HERE WILL BE API CALL
setTranslations([{ code: "HM", value: "Home" }]);
if (callbackFunction) callbackFunction();
}
fetchTranslationsForClass("", setTranslations, className);
}, []);
return [
(code) => {
var a = internalT(translations, code);
return a;
},
translations,
setTranslations
];
}
function internalT(translations, translationCode, interpolationParam) {
var tr = translations.filter((x) => x.code == translationCode);
if (!interpolationParam) return tr && tr[0] && tr[0].value;
else return tr && tr[0] && strInterpolate(tr[0].value);
}
const strInterpolate = (template, args = {}) => {
const interpolateHandler = new Function(
"params",
"const __ = (" +
Object.keys(args).join(",") +
") => `" +
template +
"`\nreturn __(...Object.values(params))"
);
return interpolateHandler(args);
};
That i'm trying to use like this:
import useTranslation from "./useTranslation";
import MyComponent2 from "./MyComponent2.js";
import { useState } from "react";
export default function MyComponent() {
const [t, tr, setTr] = useTranslation("MyComponent", () => {});
const [translatedItems, setTranslatedItems] = useState([{ name: t("HM") }]);
return (
<>
<div>{t("HM")}</div>
<MyComponent2 translatedItems={translatedItems} />
</>
);
}
And it works fine for string embedded in ui but it does not work for a string passed in component state in translatedItems;
In MyComponent2 item.name is always undefined:
export default function MyComponent2({ translatedItems }) {
return (
<>
{translatedItems.map((item, index) => {
return (
<>
<div>{"index" + index}</div>
<div key={index}> {"'" + item.name + "'"}</div>
</>
);
})}
</>
);
}
Basically the state is initialized at first render and it does not update after the 't' function is defined;
If i try to set the state with useEffect this will create an infinite loop.

React.js: How to set default value in Select option?

I am trying to implement select-option in React using custom hooks and encountered an issue while trying to set a default value in select option. From the fetched data in UI, that comes from web API, I was able to show selected data based on category(in my case it's cuisine). But when I select default value to show All data, state doesn't update.
Another problem is about the duplicated values in select option. I need to have unique values as option values. I was thinking about to get unique values this way
<option key={restaurant.id}>{[...new Set(restaurant.cuisine)]}</option>
But this removes duplicated characters,but not the duplicated values.
Code below.
Hooks/useRestaurants component
import React, { useState, useEffect } from "react";
const useRestaurants = (cuisine) => {
const [allRestaurants, setAllRestaurants] = useState([]);
useEffect(() => {
fetch("https://redi-final-restaurants.herokuapp.com/restaurants")
.then((res) => res.json())
.then((result) => setAllRestaurants(result.results))
.catch((e) => console.log("error"));
}, []);
useEffect(() => {
if (cuisine === "All") {
const filterRestaurants = [...allRestaurants].filter((restaurant) => // here is my try
restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())//code here doesn't work
);
setAllRestaurants(filterRestaurants);
} else {
const filterRestaurants = [...allRestaurants].filter((restaurant) =>
restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())
);
setAllRestaurants(filterRestaurants);
}
}, [cuisine]);
return [allRestaurants];
};
export default useRestaurants;
App.js component
import React, { useState } from "react";
import useRestaurants from "./useRestaurants";
import Form from "./Form";
import Restaurant from "./Restaurant";
import "./styles.css";
export default function App() {
const [cuisine, setCuisine] = useState("All");
const [allRestaurants] = useRestaurants(cuisine);
const onChangeHandler = (e) => {
setCuisine(e.target.value);
};
return (
<div className="App">
<Form
onChangeHandler={onChangeHandler}
allRestaurants={allRestaurants}
cuisine={cuisine}
setCuisine={setCuisine}
/>
{allRestaurants &&
allRestaurants.map((restaurant) => (
<Restaurant restaurant={restaurant} key={restaurant.id} />
))}
</div>
);
}
And Form.js component
import React from "react";
const Form = ({ allRestaurants, cuisine, onChangeHandler }) => {
return (
<select onChange={onChangeHandler} value={cuisine}>
<option value={cuisine}>All</option>
{allRestaurants.map((restaurant) => (
<option key={restaurant.id}>{restaurant.cuisine}</option>
))}
</select>
);
};
export default Form;
Any help will be appreciated.
The useEffect in useRestaurants that is performing the filtering is missing allRestaurants from the dependency array. This means that the initial value (an empty array) will always be used within that useEffect. Thus, changing the cuisine will set allRestaurants to an empty array. However, you can't add allRestaurants to the dependency array and set it from within the effect. That will cause it to loop infinitely. The solution is to not use an effect - just create the filtered result and return it either as a separate value or in place of allRestaurants
// useRestaurants.js
import { useState, useMemo, useEffect } from "react";
const useRestaurants = (cuisine) => {
const [allRestaurants, setAllRestaurants] = useState([]);
useEffect(() => {
fetch("https://redi-final-restaurants.herokuapp.com/restaurants")
.then((res) => res.json())
.then((result) => setAllRestaurants(result.results))
.catch((e) => console.log("error"));
}, []);
const filteredRestaurants = useMemo(() => {
return cuisine === "All"
? allRestaurants
: allRestaurants.filter((restaurant) =>
restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())
);
}, [cuisine, allRestaurants]);
return [allRestaurants, filteredRestaurants];
};
export default useRestaurants;
To fix the duplicate cuisine values you need to create the Set and then filter over that result. Your form is still filtering over all allRestaurants and {[...new Set(restaurant.cuisine)]} is just creating an array with a single value.
// Form.js
import React from "react";
const Form = ({ allRestaurants, cuisine, onChangeHandler }) => {
const cuisines = Array.from(new Set(allRestaurants.map((r) => r.cuisine)));
return (
<select onChange={onChangeHandler} value={cuisine}>
<option value='All'}>All</option>
{cuisines.map((cuisine) => (
<option id={cuisine}>{cuisine}</option>
))}
</select>
);
};
export default Form;
Remember to loop over the filtered restaurants in App.js
...
const [allRestaurants, filteredRestaurants] = useRestaurants(cuisine);
...
return (
...
{filteredRestaurants &&
filteredRestaurants.map((restaurant) => (
<Restaurant restaurant={restaurant} key={restaurant.id} />
))}
)

How do you submit an API request from the front-end based on the result of a previous API request on an Apollo graphql server?

I'm working with two API endpoints. The first one returns a list of dates in a string format for which data is available. The date can then be added to the second endpoint and renders additional data. On the Graphql Playground I have been able to make it all work. On the front-end I have a select option drop down for the dates, but I have not been able to fire off the second API call when I click on any given date. It's the first time I'm using graphql mutation and I haven't been able to get the second API request to return any data when I select a date. Thank you.
Front-end code:
app.tsx
import * as React from 'react'
import { useState } from 'react'
import { useMutation } from '#apollo/react-hooks'
import { IrriSatQuery } from '../../generated/graphql'
import { MAP_LAYER } from './query'
interface Props {
data: IrriSatQuery;
}
const IrriSat: React.FC<Props> = ({ data }) => {
const [option, setOption] = useState((data?.mapDates as any)[0].date!)
const [getLayer] = useMutation(MAP_LAYER)
return (
<>
<ContentWrapper>
<select value={option} onChange={( e: React.ChangeEvent<HTMLSelectElement>, ): void => {setOption(e.target.value, getLayer(e.target.value)}} onSelect={() => getLayer({variables: {type: option}})}>
{data?.mapDates?.slice(0,52).map(res =>
<option key={res?.date!} value={res?.date!}>{res?.date}</option>
)
}
</select>
</ContentWrapper>
</>
)
}
export default IrriSat
query.ts
export const QUERY_IRR_SAT = gql`
query IrriSat {
mapDates {
date
dateurl
}
}
`
export const MAP_LAYER = gql`
mutation MapLayer($date: String!) {
mapDate(date: $date) {
token
mapid
name
}
}
`
Back-end code:
server.js
class IrriSatAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://irrisat-cloud.appspot.com/_ah/api/irrisat/v1/services/'
}
async getMapsDates() {
const response = await this.get('maps/dates')
return Array.isArray(response.items) ? response.items.map(response => this.mapsDatesReducer(response)) : []
}
mapsDatesReducer(response) {
return {
date: response.date,
dateurl: response.dateurl,
}
}
async getMapsLayer(date) {
const response = await this.get(`maps/layers/${date}`)
return Array.isArray(response.items) ? response.items.map(response => this.mapsLayerReducer(response)) : []
}
mapsLayerReducer(response) {
return {
token: response.token,
mapid: response.mapid,
name: response.name
}
}
}
}
schema.js
type MapDates {
date: String
dateurl: String
}
type Mutation {
mapDate(date: String): [MapsLayers]
}
type Query {
mapDates: [MapDates]
resolver.js
module.exports = {
Query: {
mapDates: (_, __, { dataSources }) => dataSources.irriSatAPI.getMapsDates(),
},
Mutation: {
mapDate: (_, { date }, { dataSources }) => dataSources.irriSatAPI.getMapsLayer(date)
}
}
There are a few issues with your onChange function.
You are calling getLayer twice? You should only need to call it once, at the same time as you set the value of the dropdown. Also, as far as I know, you don't really need the onSelect.
import * as React from 'react';
import { useState } from 'react';
import { useMutation } from '#apollo/react-hooks';
import gql from 'graphql-tag';
const MAP_LAYER = gql`
mutation MapLayer($date: String!) {
mapDate(date: $date) {
token
mapid
name
}
}
`;
const ContentWrapper = ({ children }) => <div>{...children}</div>;
const IrriSat: React.FC<any> = ({ data }) => {
const [option, setOption] = useState((data?.mapDates as any)[0].date!);
const [getLayer]: any = useMutation(MAP_LAYER);
return (
<ContentWrapper>
<select
value={option}
onChange={(e: React.ChangeEvent<HTMLSelectElement>): void => {
setOption(e.target.value);
getLayer({ variables: { date: e.target.value } });
}}
>
{data?.mapDates?.slice(0, 52).map(res => (
<option key={res?.date!} value={res?.date!}>
{res?.date}
</option>
))}
</select>
</ContentWrapper>
);
};
export default IrriSat;
Obviously, I changed a few things to get rid of some of the editor warnings, but pay particular attention to the onChange property.
A tip: you are probably experiencing these issues due to the extreme length of the line that you add all of this logic condensed into. Install the "Prettier - Code formatter" VS Code extension. Enable VS Code's format on save option. Profit.

Resources