I was watching Wes Bos' speech recognition application, and thought of developing that in React.
So I started and everything was going great, I used hooks and context for state management, but then I got stuck so here what is really happening:
So as the user says any color name that is mentioned, the app strike-through the name of the color (add a css class to that color).
But as soon as another name is called the already striked through color becomes inactive (removes the css class from the previous color name and add the css class to the new one), because that component (ColorList) is getting re-rendered again and again.
I want to persist that strike-through on that individual color name.
Colors component (Which renders all the list of colors)
import React, { useState, useEffect, useContext } from "react";
import { GlobalContext } from "../context/GlobalState";
import { colors } from "../utils/colors";
import { ColorList } from "./ColorList";
const Colors = () => {
const [colors1, setColors] = useState([]);
const colorArray = Object.keys(colors).sort((a, b) => a.length - b.length);
useEffect(() => {
setColors(colorArray);
}, []);
const { color, already } = useContext(GlobalContext);
// console.log("context", context.color);
return (
<div className="colors" style={{ backgroundColor: "green" }}>
{colors1.map((colors, index) => (
<ColorList key={index} colors={colors} />
))}
</div>
);
};
export default Colors;
Color List Component
import React, { useContext } from "react";
import { isDark } from "../utils/colors";
import { GlobalContext } from "../context/GlobalState";
export const ColorList = props => {
const { color} = useContext(GlobalContext);
return (
<span
className={`color ${isDark(props.colors) && "dark"} ${props.colors} ${
props.colors === color ? "got" : ""
}`}
style={{ backgroundColor: `${props.colors}` }}
>
{props.colors}
</span>
);
};
GlobalState Context
import React, { createContext, useReducer } from "react";
import AppReducer from "./AppReducer";
const initialState = {
colorName: "",
alreadyDone: []
};
export const GlobalContext = createContext(initialState);
export const GlobalState = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
function changeColorName(color) {
dispatch({
type: "CHANGE",
payload: color
});
}
function addToAlready(color) {
dispatch({
type: "ADDTO",
payload: color
});
}
console.log("from Context", state);
return (
<GlobalContext.Provider
value={{
color: state.colorName,
changeColor: changeColorName,
alreadyDone: state.alreadyDone,
already: addToAlready
}}
>
{children}
</GlobalContext.Provider>
);
};
Related
What is the correct way so that I can save the dark mode switch even after turning off the application?
I want to use the use-state-persist library to achieve the goal.
In my example I show app.tsx , Preferences.tsx, ViewField.tsx .
So that it will be possible to understand how the logic is built
DarkModeContext
import React from 'react';
interface DarkMode {
isDarkMode: boolean;
setDarkMode: () => void;
}
export const DarkModeContext = React.createContext<DarkMode>({} as DarkMode);
this is the app.tsx
import React, { useEffect, useState } from 'react';
import { syncStorage } from 'use-state-persist';
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const Drawer = createDrawerNavigator();
const initStorage = async () => await syncStorage.init();
const toggleDarkMode = () => {
setDarkMode(!isDarkMode);
};
return (
<DarkModeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
<NavigationContainer>
<Drawer.Navigator
drawerContent={SideMenu}
screenOptions={{
drawerPosition: 'right',
headerShown: false,
drawerType: 'front',
}}
>
<Drawer.Screen name='HomeScreen' component={StackNavigator} />
</Drawer.Navigator>
</NavigationContainer>
</DarkModeContext.Provider>
);
};
export default App;
this is the Preferences.tsx
import React, { useContext, useState } from 'react';
import ViewField from './ViewField';
import { DarkModeContext } from '~/context/DarkModeContext';
const Preferences = () => {
const { isDarkMode, toggleDarkMode } = useContext(DarkModeContext);
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggleDarkMode} />
</View>
);
};
export default Preferences;
this is the ViewField.tsx
import { View, Text, Switch } from 'react-native';
import React from 'react';
import styles from './ViewFieldStyles';
import { useContext } from 'react';
import { DarkModeContext } from '~/context/DarkModeContext';
type Props = {
title: string;
isEnabled: boolean;
setValue: () => void;
};
const ViewField = ({ title, isEnabled, setValue }: Props) => {
const { isDarkMode } = useContext(DarkModeContext);
return (
<View style={isDarkMode ? styles.optionViewDark : styles.optionView}>
<View style={styles.sameRowTextView}>
<Text style={isDarkMode ? styles.optionTextDark : styles.optionText}>{title}</Text>
<View style={styles.switchView}>
<Switch
trackColor={
isDarkMode
? { false: 'rgba(255, 255, 255, 0.38)', true: 'rgba(187, 134, 252, 0.38)' }
: { false: '#767577', true: 'rgba(4, 76, 163, 0.38)' }
}
onValueChange={setValue}
value={isEnabled}
/>
</View>
</View>
</View>
);
};
export default ViewField;
Keep in mind that there seems to be some problems in use-state-persist using Boolean values. Furthermore, the latest published version of this library is from 2020.
However, the use-state-persist library just seems to be a wrapper around AsyncStorage, which is very well maintained. I would encourage you to use this library instead.
In your case, this could be implemented as follows:
Store the actual setter of the state in the context,
Create an effect that accesses the async storage on mount of the application: if there exists a value for the corresponding key, set the state of the context, if not, then do nothing.
In the Preferences component, store a new state in the async storage as well.
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const contextValue = React.useMemo(() => ({
isDarkMode,
setDarkMode
}), [isDarkMode])
React.useEffect(() => {
const load = async () => {
const value = await AsyncStorage.getItem('isDarkMode');
if (value !== null) {
setDarkMode(JSON.parse(value));
}
}
load();
}, [])
return (
<DarkModeContext.Provider value={contextValue}>
...
};
In the Preferences component, set the state and save it to the local storage.
const Preferences = () => {
const { isDarkMode, setDarkMode } = useContext(DarkModeContext);
async function toggle() {
const newValue = JSON.stringify(!isDarkMode);
await AsyncStorage.setItem('isDarkMode', newValue);
setDarkMode(prev => !prev);
}
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggle} />
</View>
);
}
I am creating a Project where I need to get value of a component state value to another component.
How can I get the value?
Information
Both are functional component.
I need to send data from A (state) to B(state).
A state data are set from react dropdown menu.Which is a button.
I don't use Link or Route to A dropdown that's why I can't get it with useParams()
I need to set value in B component which will fetch data with passing language.
I have import all needed things & don't have any warnings & error.
Code of component A
Send value from this language state to B
const A = () => {
const [language, setLanguage] = useState('en');
return (
<Navbar expand="xl" bg='light' expand={false}>
<Container>
<DropdownButton id="dropdown-basic-button" title={<MdOutlineLanguage />}>
<Dropdown.Item as="button" onClick={() => setLanguage('en')}>English</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => setLanguage('ar')}>العربية</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => setLanguage('bn')}>বাংলা</Dropdown.Item>
</DropdownButton>
</Container>
</Navbar>
)
};
export default A
Code of Component B
I need to get here A state value & set it to B state. Then pass to useGetDataQuery & fetch data.
const B = () => {
let [language, setLanguage] = useState('en')
const { data } = useGetDataQuery({language })
return (
<>
</>
)
}
export default B
Redux Section
I'm using readux & #reduxjs/toolkit to store fetch data. Can I store my language data to here. Than how can get to from anywhere of my component.
react-rotuer-dom v6
export default configureStore({
reducer: {
[dataOne.reducerPath]: dataOne.reducer,
[data2.reducerPath]: dataTwo.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false
}).concat([dataOne.middleware, dataTwo.middleware]),
})
Maybe instead of using useState, you can use a global state by using useContext because I think your language will be use on several places as request body and of course to edit the state value, you can combine it with useReducer.
// UPDATE
working code: https://codesandbox.io/s/sleepy-monad-21wnc
MyContext
import { useReducer, useContext, createContext } from "react";
const initialValue = {
language: "id"
// other: "value",
};
const AppContext = createContext(initialValue);
const AppReducer = (state, action) => {
switch (action.type) {
case "CHANGE_LANGUAGE":
return {
...state,
...action.payload
};
default:
return state;
}
};
const MyContext = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialValue);
return (
<AppContext.Provider value={[state, dispatch]}>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => useContext(AppContext);
export default MyContext;
this is the context and reducer component later on use in App.js
App.js
import A from "./A";
import B from "./B";
import MyContext from "./MyContext";
import "./styles.css";
export default function App() {
return (
<MyContext>
<div className="App">
<A />
<B />
</div>
</MyContext>
);
}
A.js
import { useAppContext } from "./MyContext";
const A = () => {
const [globalState, dispatch] = useAppContext();
const onChange = (e) => {
dispatch({
type: "CHANGE_LANGUAGE",
payload: {
[e.target.name]: e.target.value
}
});
};
return (
<>
<p>Start Of Component A </p>
<input value={globalState.language} name="language" onChange={onChange} />
<p>End Of Component A </p>
</>
);
};
export default A;
B.js
import { useAppContext } from "./MyContext";
const B = () => {
const [globalState, dispatch] = useAppContext();
return (
<>
<p>Start Of Component B </p>
<h2>Language Val : {globalState.language}</h2>
<p>End Of Component B </p>
</>
);
};
export default B;
I am trying to get my child component to look for the global state. As of now, I can log out my global state, I can see that it has been updated, but my components are not updating with the new value. I am not getting any errors but also have had a hard time find a good solution. I have the full repo(very small with only 1 component) if it helps here: https://github.com/jaysnel/context-api-react
Is there any reason my ChangeColor.js file is not updating with the new state change?
UpdateColor.js
import React, { useReducer, useEffect } from 'react'
import { useAppContext } from '../public/context/context'
export default function UpdateColor() {
let { myGlobaData } = useAppContext();
const [state, dispatch] = useReducer(reducer, myGlobaData);
function reducer(state, action) {
let newState = state.slice();
console.log(state);
if(newState !== undefined) {
switch(action.type) {
case 'UPDATE_COLOR':
newState[0].color = 'green';
return newState;
default:
throw new Error();
}
}
}
return (
<div>
<button onClick={() => dispatch({type: 'UPDATE_COLOR'})}>Update Color</button>
</div>
)
}
ChangeColor.js
import React from 'react'
import { useAppContext } from '../public/context/context'
export default function ChangeColor() {
const { myGlobaData } = useAppContext();
console.log("ChangeColor.js", myGlobaData)
return (
<div>
<h2 style={{color: myGlobaData[0].color}}>I Change Colors Based On Global State.</h2>
</div>
)
}
context.js
import { createContext, useContext } from 'react';
const AppContext = createContext();
export function AppWrapper({ children }) {
const state = {
myGlobaData: [
{
color: 'red',
text: 'new text new me'
}
],
}
return (
<AppContext.Provider value={state}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
I guess, your issue where did you used the reducer. Component ChangeColor.js don't know what are you doing inside UpdateColor.js. The solution is if put the reducer into global context context.js and then you have to acsess your reducer globally.
I did created and pushed to github working example with two different approaches to using an actions reducer. working example
UpdateColor.js
import { useAppContext } from '../public/context/context'
export default function UpdateColor() {
const { dispatch } = useAppContext();
const withPayload = () => {
dispatch({
type: 'UPDATE_COLOR_WITH_PAYLOAD',
payload: {color: 'blue', text: 'new text from updateColor.js'}})
}
const intoReducer = () => {
dispatch({type: 'UPDATE_COLOR_INTO_REDUCER'})
}
return (
<div>
<button onClick={withPayload}>Update Color with payload</button>
<button onClick={intoReducer}>Update Color into reducer</button>
</div>
)
}
ChangeColor.js
import { useAppContext } from '../public/context/context'
export default function ChangeColor() {
const { state } = useAppContext();
return (
<div>
<h2 style={{color: state.color}}>I Change Colors Based On Global State.</h2>
<p style={{textAlign: 'center'}}>{state.text}</p>
</div>
)
}
context.js
import { createContext, useContext, useReducer } from 'react';
const AppContext = createContext();
export function useAppContext() {
return useContext(AppContext);
}
function reducer(state, action) {
switch (action.type) {
case 'UPDATE_COLOR_WITH_PAYLOAD':
return action.payload;
case 'UPDATE_COLOR_INTO_REDUCER':
action.color = 'green';
action.text = 'new text from reducer';
return action;
default:
return state;
}
}
export function AppWrapper({ children }) {
const initialState = { color: 'red', text: 'new text new me' };
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
I want to change context on the language that client is choose, but the the value of the context didn't change. What am I doing wrong?
This is the Context:
import React from 'react'
const LanguageContext = React.createContext({
language: "EN",
setLanguage: () => { }
})
export default LanguageContext
and this where I change language:
import React, { useState, useContext } from 'react'
import styled from 'styled-components'
import LanguageContext from '../../Context'
const LanguageButton = () => {
const [isClicked, setIsClicked] = useState(false)
const {language, setLanguage} = useContext(LanguageContext)
const changeLanguage = () => {
setIsClicked(!isClicked)
}
return (
<span>
<Span>
<Button style={{ padding: 0 }} onClick={changeLanguage}>
Language - {language}
</Button>
</Span>
{isClicked ?
<Div style={{ position: 'absolute' }}>
<div><Button onClick={() => setLanguage('BG')} >BG - Bulgarian</Button></div>
<div><Button onClick={() => setLanguage('EN')}>EN - English</Button></div>
</Div> : ''
}
</span>
)
}
As there is no context provider in the component tree, the default value will be used, so setLanguage refers to setLanguage: () => { } which does nothing.
You'll need a component that actually stores the state, and then provides that to all subcomponents through the context:
function WithLanguage({ children }) {
const [language, setLanguage] = useState("");
return <LanguageContext.Provider value={{ language, setLanguage }}>{ children }</LanguageContext.Provider>;
}
/* in some top component*/
<WithLanguage>
<LanguageButton />
</WithLanguage>
I'm trying to trigger a function from my CartContext Api upon a click, but it isn't happening. I have checked the method and it works, but when I add the context function it doesn't do anything... see below code:
Context file
import React, { useState } from 'react';
export const CartContext = React.createContext({
cart: [],
setCart: () => {},
});
const CartContextProvider = (props) => {
const [updateCart, setUdatedCart] = useState();
const updateCartHandler = () => {
console.log('click');
};
return (
<CartContext.Provider
value={{ cart: updateCart, setCart: updateCartHandler }}
>
{props.children}
</CartContext.Provider>
);
};
export default CartContextProvider;
Component where Im using the context:
import React, { useContext } from 'react';
import classes from './SingleProduct.css';
import AddToCartBtn from './AddToCartBtn/AddtoCartBtn';
import { CartContext } from '../context/cart-context';
const singleProduct = (props) => {
const cartContext = useContext(CartContext);
const addToCart = (id, productName, price, qty) => {
const productInCart = {
productId: id,
productName: productName,
productPrice: price,
productQty: qty,
};
cartContext.setCart();
};
return (
<article className={classes.SingleProduct}>
<div className={classes.ProductImgContainer}>
<img src={props.productImg} alt="" />
</div>
<div className={classes.ProductTitle}>
<h2>{props.productName}</h2>
</div>
<AddToCartBtn
clicked={() => {
addToCart(
props.productId,
props.productName,
props.productPrice,
props.productQty
);
}}
/>
</article>
);
};
export default singleProduct;
I'm just adding a console.log('click') to check if the method triggers at the moment. By the way, when I console.log the context variable it contains the properties and works. Any ideas why this isn't happening
Forgot to wrap the component with provider thanks!