rendering page contents depending on language set in AsyncStorage React Native - reactjs

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

Related

state doesn't update when linking to a url from toolbar in react-draft-wysiwyg

I am using react-draft-wysiwyg in my project. It works fine most cases.
Only problem is, when I try to link a text to url. In that case it doesn't update the state.
Let's say, I already have a text "Hello world!". Now if I add more text in this e.g. "Hello world! newText". I want to link this "newText" to url using link option from toolbar and click save. It doesn't update the state with the link and goes back previous text "Hello world!"
Interesting thing is that after adding a link if I add more text to that, it works just fine.
const createStateFromHtml = (html: string) => {
const blocksFromHtml = htmlToDraft(html)
const { contentBlocks, entityMap } = blocksFromHtml
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap)
return EditorState.createWithContent(contentState)
}
const createHtmlFromState = (editorState: EditorState) => {
const rawContentState = convertToRaw(editorState.getCurrentContent())
return draftToHtml(rawContentState)
}
const htmlSanitizeSettings = {
ADD_TAGS: ['iframe'],
ADD_ATTR: ['allowfullscreen'],
}
const BodyEditor = (props: BodyEditorProps) => {
DOMPurify.setConfig(htmlSanitizeSettings)
const initialState = props.html && props.html !== '' ? createStateFromHtml(props.html) : EditorState.createEmpty()
const [editorState, setEditorState] = React.useState(initialState)
const setEditorHtml = () => {
const html = createHtmlFromState(editorState)
const htmlWithIframeSettings = addPropertiesToIframes(html)
const purifiedHtml = DOMPurify.sanitize(htmlWithIframeSettings)
props.setBody(purifiedHtml)
}
/*
* Adds a div element around iframes and adds class="embedded-video" around so we can use CSS to make iframes reponsive
*/
const addPropertiesToIframes = (html: string) => {
return (
html
// Let's assume embedded iframes are videos
.replace(/<iframe/g, '<div class="iframe-container"><iframe class="embedded-video"')
.replace(/<\/iframe>/g, '</iframe></div>')
.replace(/iframe width=/g, 'iframe allowfullscreen width=')
// Let's remove embedded-video class from embedded spotify iframes
// This should be done for all non-video embeds because otherwise the iframe are made to have 16:9 aspect ratio
.replace(
// eslint-disable-next-line no-useless-escape
/class=\"embedded-video\" src=\"https:\/\/open\.spotify\.com/g,
'src="https://open.spotify.com',
)
)
}
return props.editable ? (
<Editor
editorStyle={editorStyle}
editorState={editorState}
onEditorStateChange={setEditorState}
onBlur={setEditorHtml}
toolbar={{
options: toolbarOptions,
image: {
...imageOptions,
uploadCallback: props.imageUploadCallback,
},
link: {
...linkOptions,
},
embedded: {
...embeddedOptions,
},
}}
placeholder={props.placeholder}
/>
) : (
<div
onClick={() => props.toggleEditable(props.name)}
dangerouslySetInnerHTML={
props.html
? { __html: DOMPurify.sanitize(props.html!) }
: { __html: DOMPurify.sanitize(props.placeholder!) }
}
/>
)
}
export default BodyEditor
Any help would be highly appreciated. I am stuck on this for a very long time.
I used this combine. It is working properly.
import { Editor } from "react-draft-wysiwyg";
import { useEffect } from "react";
import {convertToRaw } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
Maybe, you can try that.
For Example:
You can use to the state initial value below:
EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(<your_data>)
)
)
You can use to store the HTML data below:
draftToHtml(convertToRaw(<your_state>.getCurrentContent()))

Redux toolkit query trying... I don't know why there are too many re-renders?

There is a Playlist component, user can choose different tag show different data. And some tag has banner and some not. The data are all fetched by reduxt-toolkit-query.
hightqualityTags.tags: a tag array which has banner
It seems setBannerVisibility() be used in wrong place...
Playlist.jsx
import { useState } from "react";
import HighqualityBanner from "../components/Playlist/HighqualityBanner";
import Catlist from "../components/Playlist/Catlist";
import PlaylistItem from "../components/Playlist/PlaylistItem";
import { useGetPlaylistHighqualityTagsQuery } from "../redux/services/neteaseCloudMusic";
const Playlist = () => {
// tag logic
const initialTag = "全部歌单";
const [currentTag, setCurrentTag] = useState(initialTag);
const onSelectTag = (tag) => {
setCurrentTag(tag);
};
//set banner visibility
const [bannerVisibility, setBannerVisibility] = useState(true);
const { data: hightqualityTags } = useGetPlaylistHighqualityTagsQuery();
if (hightqualityTags && hightqualityTags.tags) {
const exist = hightqualityTags.tags.find((tag) => tag.name === currentTag);
if (currentTag === initialTag) {
setBannerVisibility(true);
} else {
setBannerVisibility(!!exist);
}
}
return (
<div>
{bannerVisibility && <HighqualityBanner />}
<Catlist
initialTag={initialTag}
tag={currentTag}
onSelectTag={onSelectTag}
/>
<div>
<PlaylistItem />
</div>
</div>
);
};
api
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
export const musicApi = createApi({
reducerPath: "musicApi",
baseQuery: fetchBaseQuery({
baseUrl: "https://music-api-33.vercel.app",
}),
endpoints: (builder) => ({
getRecommendBanner: builder.query({ query: () => "/banner" }),
getPlaylistHotlist: builder.query({ query: () => "/playlist/hot" }),
getPlaylistCatlist: builder.query({ query: () => "/playlist/catlist" }),
getPlaylistHighqualityTags: builder.query({
query: () => "/playlist/highquality/tags",
}),
}),
});
export const {
useGetRecommendBannerQuery,
useGetPlaylistHotlistQuery,
useGetPlaylistCatlistQuery,
useGetPlaylistHighqualityTagsQuery,
} = musicApi;
I want to find currentTag is or isn't existing in the hightqualityTags.tags and according the result to set the banner's visiblity. I'm new to redux-toolkit-query, I try to read the docs but it's hard to me.
too many rerenders occur due to the fact that you change the state of the component every time its state changes in the if() block. I.e., looping occurs.
You need to wrap your if block in useEffect(() => {}) and everything should work out.
const Playlist = () => {
// tag logic
const initialTag = "全部歌单";
const [currentTag, setCurrentTag] = useState(initialTag);
const onSelectTag = (tag) => {
setCurrentTag(tag);
};
//set banner visibility
const [bannerVisibility, setBannerVisibility] = useState(true);
const { data: hightqualityTags } = useGetPlaylistHighqualityTagsQuery();
useEffect(() => {
if (hightqualityTags && hightqualityTags.tags) {
const exist = hightqualityTags.tags.find((tag) => tag.name === currentTag);
if (currentTag === initialTag) {
setBannerVisibility(true);
} else {
setBannerVisibility(!!exist);
}
}
}, [currentTag, hightqualityTags])
return (
<div>
{bannerVisibility && <HighqualityBanner />}
<Catlist
initialTag={initialTag}
tag={currentTag}
onSelectTag={onSelectTag}
/>
<div>
<PlaylistItem />
</div>
</div>
);
};
Do not allow the state of the component to change outside of functions or events. Remember that React functional components behave like normal functions, i.e. when the state changes, they are executed completely and render() is terminated.

Nextjs not correctly load GetStaticProps and not return props to page

I can't call getStaticProps function in my Next.js/React project. I want to load getStaticProps when the page path changes to /blog so in my pages/blogs
type BlogStaticInputs = {
blogs: BlogsType[]
}
export const getStaticProps: GetStaticProps = async () => {
console.log('hello')
const blogs = getAllBlogs(['date', 'slug', 'title'])
return {
props: { blogs }
}
}
export const Index = ({ blogs }: BlogStaticInputs) => {
return <Blogs blogs={blogs} />
}
export default Index
and my BlogType is
export type BlogType = {
date?: string
slug: string
title: string
}
And I cannot see console.log('hello') in my console so I believe the getStaticProps is not working currently.
In my _app.tsx - and I think this is correctly loading props and components.
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
</>
)
}
export default MyApp
And my getAllBlogs looks like this
export function getAllBlogs(fields: string[] = []): BlogItems[] {
console.log('I am getting blogs now...')
const slugs = getBlogSlugs()
const posts = slugs
.map((slug) => getBlogBySlug(slug, fields))
// sort posts by date in descending order
.sort((post1, post2) => (post1.date > post2.date ? -1 : 1))
return posts
}
and supporting functions are these.
export function getBlogSlugs(): string[] {
return fs.readdirSync(POSTS_PATH)
}
type BlogItems = {
[key: string]: string
}
export function getBlogBySlug(slug: string, fields: string[] = []): BlogItems {
const realSlug = slug.replace(/\.mdx$/, '')
const fullPath = join(POSTS_PATH, `${realSlug}.mdx`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
const { data, content } = matter(fileContents)
const items: BlogItems = {}
// Ensure only the minimal needed data is exposed
fields.forEach((field) => {
if (field === 'slug') {
items[field] = realSlug
}
if (field === 'content') {
items[field] = content
}
if (data[field]) {
items[field] = data[field]
}
})
return items
}
and supporting function to get blogs directory path
export const POSTS_PATH = path.join(process.cwd(), 'blogs')
I think getAllBlogs function is not yet called since I cannot see console.log('I am getting blogs now...').
Before starting in with a more strongly typed solution, I did notice that your BlogType type is renamed BlogTypes in the pages/index.tsx file? Is the s at the end referencing the same type as BlogType?
// BlogType
export type BlogType = {
date?: string
slug: string
title: string
}
// versus
// In your Index (pages/index.tsx) file
type BlogStaticInputs = {
blogs: BlogsType[]
}
At any rate, I'll be using the BlogType type that is defined above going forward.
First, define this in a types directory in your root or inside of the src directory if you're using the src setup.
/**
* #type {ParsedUrlQuery}
* #description url query params
*/
export type ParsedUrlQuery<T = string, N = NodeJS.Dict<T | T[]>> = N;
I've done something similar, and I think I see the issue. Just adding a few bonus tips for working with Next+TypeScript (my specialization for over a year was Nextjs + headless wordpress builds)
The ParsedUrlQuery type can be used with GetStaticProps, GetStaticPaths, and GetServerSideProps.
This is a reusable and useful component for these kinds of situations. I wrote it using Tailwindcss as my styles framework so if you're using something else simply replace the content of className
import { FC } from "react";
const DataInspector: FC = ({ children }) => {
return (
<div
lang='json'
className='font-gothamLight container max-w-7xl break-normal'>
<pre className='container flex-col mx-auto text-[12px] bg-[#151515] text-yellow-400 w-8xl max-w-8xl overflow-x-hidden'>
{JSON.stringify(children, null, 2)}
</pre>
</div>
);
};
export default DataInspector;
Once that is defined, give this a try with GetStaticProps -- it will error if you don't have the established types being passed correctly. You will also be able to see the inferred types returned to the client as follows:
import type {
GetStaticPropsContext,
GetStaticPropsResult,
InferGetStaticPropsType
} from "next";
// update import paths below accordingly
import type { ParsedUrlQuery } from "#/types/index";
import type { BlogType } from "#/types/blog-type";
import DataInspector from "#/components/data-inspector";
type BlogStaticInputs = {
blogs: BlogType[]
}
// try hovering over `blogs` -- you'll notice it's strongly typed
export default function Index<T extends typeof getStaticProps>({
blogs
}: InferGetStaticPropsType<T>) {
const tempErrObj = { error: new Error(`[Blogs data handling error]: ${blogs}`).message };
return (
<>
{blogs && blogs.length > 0
? <Blogs blogs={blogs} />
: <DataInspector>{blogs ? blogs : tempErrObj}</DataInspector>
}
</>
);
}
export const getStaticProps = async (
ctx: GetStaticPropsContext<ParsedUrlQuery>
): Promise<GetStaticPropsResult<BlogStaticInput>> => {
const blogs = getAllBlogs(['date', 'slug', 'title']);
return {
props: { blogs }
};
}

UseEffect doesn't trigger on second change, but trigger twice on launch.(React hooks and apollo-graphql hooks)

useEffect doesn't trigger on second change, but triggers twice on launch (React hooks and apollo-graphql hooks).
In console.logs I described when the changes are triggered and when not.
I don't have any more clue to add.
Here's my page (Next.js pages)
import React, { useEffect, useState } from 'react'
import Calendar from '../components/Calendar';
import { useHallsQuery } from '../generated/graphql';
const schedule = () => {
const { data, loading, error, refetch:refetchSessions } = useHallsQuery();
const [sessions, setSessions] = useState([] as any);
const [owners, setOwners] = useState([] as any);
useEffect(() => {
console.log('On launch trigger twice, on first change trigger once, on second doesn't trigger and later trigger correctly, but it's one change delay');
if(loading === false && data){
const colors: any = [
'#FF3333',
'#3333FF',
'#FFFF33',
'#33FF33',
'#33FFFF',
'#9933FF',
'#FF9933',
'#FF33FF',
'#FF3399',
'#A0A0A0'
];
let pushSessions:any = [];
let owners:any = [];
data?.halls?.map(({sessions, ...o}, index) =>{
owners.push({id:o.id,
text:o.name,
color: colors[index%10]});
sessions.map((session:any) => {
pushSessions.push({...session,
ownerId: o.id});
})
})
setSessions(pushSessions);
setOwners(owners);
}
}, [loading, data])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<Calendar sessions={sessions} owners={owners} refetchSessions={refetchSessions} />
</div>
)
}
export default schedule
and my component part where I get props and trigger refetchSessions.
const Calendar = (props: any) => {
let { sessions, owners, refetchSessions } = props;
const [moveSession] = useMoveSessionMutation();
...
const commitChanges = ({ added, changed, deleted }: any) => {
if (added) {
//
}
if (changed) {
console.log('trigger on all changes! Correct')
const id = Object.keys(changed)[0];
moveSession({variables:{
input: {
id: parseInt(id),
...changed[id]
}
}, refetchQueries: refetchSessions
})
}
if (deleted !== undefined) {
//
}
};
return (
// Some material-ui components and #devexpress/dx-react-scheduler-material-ui components in which commitChanges function is handled
)
export default Calendar;
Hook was generated with graphql-codegen(generated/graphql.tsx):
export function useHallsQuery(baseOptions?: Apollo.QueryHookOptions<HallsQuery, HallsQueryVariables>) {
return Apollo.useQuery<HallsQuery, HallsQueryVariables>(HallsDocument, baseOptions);
}
export function useHallsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<HallsQuery, HallsQueryVariables>) {
return Apollo.useLazyQuery<HallsQuery, HallsQueryVariables>(HallsDocument, baseOptions);
}
export type HallsQueryHookResult = ReturnType<typeof useHallsQuery>;
and here's the schema(graphql/hall.graphql):
query Halls {
halls {
id
name
sessions{
id
title
startDate
endDate
}
}
}

Cant access objects within state

I have a component that uses axios to access the PubMed api (in componentDidMount), retrieves some publication ids then stores them in state as "idlist". A second function is then called (addPapers) which passes in this id list and makes a second api call to retrieve further details (title, journal, authors) for each id. All this seems to work fine and when I use react tools to check state there is an array ("paperList") full of objects that have the expected key:value pairs. However, when I try to map over this array and access the values within the objects in the render function (ie paper.title, paper.author, paper.journal) they are returning as undefined. I haven't been using react for long and suspect I am making a basic mistake but cant figure it out.
I have tried console.logging each step and the expected data is in state and correct in react tools
import axios from 'axios'
import './App.css';
import rateLimit from 'axios-rate-limit';
class App extends Component {
state= {
idlist: [],
papersList : ""
}
componentDidMount () {
console.log("incomponent")
axios.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)")
.then (response =>
this.setState({idlist: response.data.esearchresult.idlist}, () => {
this.addPapers(this.state.idlist)
}
)
)}
addPapers = (idlist) => {
if (idlist) {
const http = rateLimit(axios.create(), { maxRequests: 6, perMilliseconds: 1000 })
const list = this.state.idlist.map(id => {
let paperObj ={};
let paperList =[]
http.get(`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`)
.then (response2 => {
const title = response2.data.result[id].title
const journal = response2.data.result[id].fulljournalname
const authorList = []
const authors = response2.data.result[id].authors
authors.map((author, idx) =>
idx > 0 ? authorList.push(" " + author.name) : authorList.push(author.name))
paperObj.title = title
paperObj.journal = journal
paperObj.authors = authorList.toString()
paperList.push(paperObj)
})
return paperObj
})
this.setState({papersList: list})
}
}
render () {
let article = ""
if (this.state.papersList.length){
article = this.state.papersList.map(paper =>
console.log (paper.title)
console.log (paper.authors)
console.log (paper.journal)
)
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}
}
export default App;
I expect that when I map over paperList and extract each paper I should be able to return the title, journal or authors using console.log(paper.title), console.log(paper.title), console.log(paper.title). These are all returning undefined.
You have two issues in code
1) paperList array declaration should be out of map loop.
2) paperList should be returned instead of paperObj
Working code below make some enhancements in render function
Also codesandbox link
import React from "react";
import ReactDOM from "react-dom";
import rateLimit from "axios-rate-limit";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
state = {
idlist: [],
papersList: ""
};
componentDidMount() {
console.log("incomponent");
axios
.get(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)"
)
.then(response =>
this.setState({ idlist: response.data.esearchresult.idlist }, () => {
this.addPapers(this.state.idlist);
})
);
}
addPapers = idlist => {
if (idlist) {
const http = rateLimit(axios.create(), {
maxRequests: 6,
perMilliseconds: 1000
});
let paperList = [];
this.state.idlist.forEach(id => {
let paperObj = {};
http
.get(
`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`
)
.then(response2 => {
const title = response2.data.result[id].title;
const journal = response2.data.result[id].fulljournalname;
const authorList = [];
const authors = response2.data.result[id].authors;
authors.map((author, idx) =>
idx > 0
? authorList.push(" " + author.name)
: authorList.push(author.name)
);
paperObj.title = title;
paperObj.journal = journal;
paperObj.authors = authorList.toString();
paperList.push(paperObj);
})
.then(result => {
this.setState({ papersList: paperList });
});
});
}
};
render() {
return (
<div className="App">
<h1>Publications</h1>
{this.state.papersList.length &&
this.state.papersList.map(data => {
return <div>{data.title}</div>;
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope it helps!!!
Do it like this:
render () {
let article;
if (this.state.papersList.length){
article = this.state.papersList.map(paper => <p>span>Title is {paper.title}</span></p> )
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}

Resources