🐛 Issue
I created a Custom Hook to switch themes, but I'm facing this problem and I don't exactly what could it be.
Basically, when I try to change the theme, it works perfectly. But, after refreshing the page, it doesn't stay with the correct one and gives me this error.
⚙️ Reproduce the issue
You can reproduce this issue, cloning the Edite GitHub repository and follow the guide to set up the services.
💻 Code
Note: see that src is the root
hooks/useThemeSwitcher.js
import { useState, useEffect } from 'react';
function useThemeSwitcher(key, initialTheme) {
const [theme, setTheme] = useState(
() => {
let storagedTheme = localStorage.getItem(key);
storagedTheme = JSON.parse(storagedTheme) || initialTheme;
}
);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(theme));
}, [key, theme]);
return [theme, setTheme];
}
export default useThemeSwitcher;
App.js
function App() {
// Current theme state (light/dark);
const [theme, setTheme] = useThemeSwitcher('theme', dark);
const toggleTheme = () => {
setTheme(theme.title === 'dark' ? light : dark)
}
return (
<ThemeProvider theme={theme}>
<Global />
{/* ...components */}
<ToolbarLeft toggleTheme={toggleTheme} />
</ThemeProvider>
);
}
components/Switch/index.js
// Components (styles)
import { CustomSwitch } from './switch.styles';
function Switch({ isToggled, onSwitch }) {
return (
<CustomSwitch>
<input
type="checkbox"
checked={isToggled}
onChange={onSwitch}
/>
<span />
</CustomSwitch>
)
}
export default Switch;
components/Toolbar/Left/index.js
// Components (styles)
import { LeftContainer } from '../toolbar.styles';
// Components (children)
import ToolsList from './tools';
function ToolbarLeft({ toggleTheme }) {
return (
<LeftContainer>
<ul>
<ToolsList toggleTheme={toggleTheme} />
</ul>
</LeftContainer>
)
}
export default ToolbarLeft;
components/Toolbar/Left/tools.js
function ToolsList({ toggleTheme }) {
// Access and set the theme colors
const { title } = useContext(ThemeContext);
return (
<>
{/* ...components */}
{/* Theme switcher */}
<Switch
isToggled={title === 'dark'}
onSwitch={toggleTheme}
</Switch>
</>
)
}
The problem is that you are not returning storagedTheme inside your useThemeSwitcher hook.
So you could change your useThemeSwitcher.js to something like this:
import { useState, useEffect } from "react";
function useThemeSwitcher(key, initialTheme) {
const [theme, setTheme] = useState(() => {
let storagedTheme = localStorage.getItem(key);
return JSON.parse(storagedTheme) || initialTheme;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(theme));
}, [key, theme]);
return [theme, setTheme];
}
export default useThemeSwitcher;
I had the same issue. I had not imported the theme from my components folder. So I had to :
import { lightTheme } from "./components/Themes";
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've got a bug with LocalStorage on react.js. I try to set a todo into it, but it doesn't load. This is the code:
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if (storedTodos) setTodos(storedTodos)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
function HandleAddTodo(e){
const name = TodoNameRef.current.value
if (name==='') return
setTodos(prevTodos => {
return[...prevTodos, { id:uuidv4(), name:name, complete:false}]
})
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
This is TodoList.js
import React from 'react'
import Todo from './Todo';
export default function TodoList({ todos }) {
return (
todos.map(todo =>{
return <Todo key ={todo.id} todo={todo} />
})
)
}
And as last Todo.js:
import React from 'react'
export default function Todo({ todo }) {
return (
<div>
<label>
<input type="checkbox" checked={todo.complete}/>
{todo.name}
</label>
</div>
)
}
What the code has to do is load a todo into the local storage, and after refreshing the page reload it into the document. The code I implemented
I just started with react but I hope anyone can pass me the right code to make it work. If anyone need extra explenation, say it to me.
Kind regards, anonymous
Try to decouple your local storage logic into it's own react hook. That way you can handle getting and setting the state and updating the local storage along the way, and more importantly, reuse it over multiple components.
The example below is way to implement this with a custom hook.
const useLocalStorage = (storageKey, defaultValue = null) => {
const [storage, setStorage] = useState(() => {
const storedData = localStorage.getItem(storageKey);
if (storedData === null) {
return defaultValue;
}
try {
const parsedStoredData = JSON.parse(storedData);
return parsedStoredData;
} catch(error) {
console.error(error);
return defaultValue;
}
});
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(storage));
}, [storage]);
return [storage, setStorage];
};
export default useLocalStorage;
And you'll use it just like how you would use a useState hook. (Under the surface it is not really more than a state with some side effects.)
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useLocalStorage(LOCAL_STORAGE_KEY, []);
const handleAddTodo = event => {
setTodos(prevTodos => {
return[...prevTodos, {
id: uuidv4(),
name,
complete: false
}]
})
};
return (
<button onClick={HandleAddTodo}>Add todo</button>
);
}
You added the getItem and setItem methods of localStorage in two useEffect hooks.
The following code intializes the todo value in localStorage when reloading the page.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
So you need to set the todo value in HandleAddTodo event.
I edited your code and look forward it will help you.
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storageItem = localStorage.getItem(LOCAL_STORAGE_KEY);
const storedTodos = storageItem ? JSON.parse(storageItem) : [];
if (storedTodos) setTodos(storedTodos)
}, []);
function HandleAddTodo(e){
const name = TodoNameRef.current.value;
if (name==='') return;
const nextTodos = [...todos, { id:uuidv4(), name:name, complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));//replace todos to nextTodos
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
There is no need of adding the second useEffect.
You can set your local Storage while submitting in the handleTodo function.
Things you need to add or remove :
Remove the Second useEffect.
Modify your handleTodo function :
const nextTodos = [...todos, { id:uuidv4(), name:name,complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));
Note: Make sure you won't pass todos instead of nextTodos as we know setTodos is an async function There might be a chance we are setting a previous copy of todos
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 learning react native, have been getting this error setState is not a function in react native
I searched a lot but nothing was helpful enough.
I have created this simplified code to show the issue
import React, { useState } from "react";
import { Text, View, Button } from "react-native";
const Test = ({ Test1 }) => {
return (
<Button
onPress={() => {
Test1.setState(true);
}}
/>
);
};
const Test1 = () => {
const [state, setState] = useState(false);
if (state) {
return <Text>Test Working</Text>;
} else {
return <Text>Test Not Working</Text>;
}
};
const App = () => {
return (
<View>
<Test Test1={Test1} />
</View>
);
};
export default App;
this is the error: TypeError: Test1.setState is not a function
Please help me fix this.
States can be transferred to other component only as props. You need to call the Test1 component from the App and the Test component from the Test1, then you can pass the props to the Test from Test1. By this you don't need to move the state to other component. you can not pass any component as props and access state or methods from there. You can try this code:
import React, { useState } from "react";
import { Text, View, Button } from "react-native";
const Test = ({ setState}) => {
return (
<Button
onPress={() => {
setState(true);
}}
/>
);
};
const Test1 = () => {
const [state, setState] = useState(false);
if (state) {
return <Text>Test Working</Text>;
} else {
return <Test setState={setState} />;
}
};
const App = () => {
return (
<View>
<Test1 />
</View>
);
};
export default App;
import React, { useState } from "react";
import { Text, View, Button } from "react-native";
const Test = ({ setState }) => {
return (
<Button
onPress={() => {
setState(true);
}}
);
};
const Test1 = ({state}) => {
if (state) {
return <Text>Test Working</Text>;
} else {
return <Text>Test Not Working</Text>;
}
};
const App = () => {
const [state, setState] = useState(false);
return (
<View>
<Test1 state={state} />
<Test setState={setState} />
</View>
);
};
export default App;
There are two problems here.
Your Test1 component is not being used at all
Hooks, and local functions in general, may not be called outside of the component that they are declared on
If you want to manage some local state in your Test component, it needs to live in that component.
I am currently following a tutorial on youtube building a whatsapp clone with react and socket.io from webDevSimplified: https://www.youtube.com/watch?v=tBr-PybP_9c
and here the main repo : 'https://github.com/WebDevSimplified/Whatsapp-Clone/tree/master/client
I got stuck halfway through as for some reason my custom useConversation hook returns undefined while my useContacts hook works without any problems.
Here the setup:
App.js :
import Dashboard from "./Dashboard";
import { ContactsProvider } from "../contexts/ContactsProvider";
import { ConversationsProvider } from "../contexts/ConversationsProvider";
import Test from "../components/test";///test
console.log(Test)//test purpose
function App() {
const [id, setId] = useLocalStorage("id");
const dashboard = (
<ContactsProvider>
<ConversationsProvider id={id}>
<Dashboard id={id} />
</ConversationsProvider>
</ContactsProvider>
);
return id ? dashboard : <Login onIdSubmit={setId} />;
}
export default App;
ContactsProvider.js
import React, { useContext } from "react";
import useLocalStorage from "../hooks/useLocalStorage";
const ContactsContext = React.createContext();
export function useContacts() {
return useContext(ContactsContext);
}
export function ContactsProvider({ children }) {
const [contacts, setContacts] = useLocalStorage("contacts", []);//initalValue an empty array
function createContact(id, name) {
setContacts((prevContacts) => {
return [...prevContacts, { id, name }];
});
}
return (
<ContactsContext.Provider value={{ contacts, createContact }}>
{children}
</ContactsContext.Provider>
);
}
Contacts.js - here my useContacts works
import React from "react";
import { ListGroup } from "react-bootstrap";
import { useContacts } from "../contexts/ContactsProvider";
export default function Contacts() {
const { contacts } = useContacts();
console.log(`contacts: ${contacts}`); //returns object, object as expected
return (
<ListGroup variant="flush">
{contacts.map((contact) => (//visibly working in UI when commenting out the ListGroup in conversations.js
<ListGroup.Item key={contact.id}>{contact.name}</ListGroup.Item>
))}
</ListGroup>
);
}
Here the problematic part:
ConversationsProvider.js
import React, { useContext } from "react";
import useLocalStorage from "../hooks/useLocalStorage";
import { useContacts } from "./ContactsProvider";
const ConversationsContext = React.createContext();
export function useConversations() {
return useContext(ConversationsContext);
}
export function ConversationsProvider({ children }) {
const [conversations, setConversations] = useLocalStorage(
"conversations", []);//as well empty array
const { contacts } = useContacts();
function createConversation(recipients) {
setConversations((prevConversations) => {
return [...prevConversations, { recipients, messages: [] }];
});
}
const formattedConversations = conversations.map((conversation) => {
const recipients = conversation.recipients.map((recipient) => {
const contact = contacts.find((contact) => {
return contact.id === recipient;
});
const name = (contact && contact.name) || recipient;
return { id: recipient, name };
});
return { ...conversation, recipients };
});
const value = {
conversations: formattedConversations,
createConversation,
};
return (
<ConversationsContext.Provider value={{ value }}>
{children}
</ConversationsContext.Provider>
);
}
and the component that causes the error:
Conversations.js:
import React from "react";
import { ListGroup } from "react-bootstrap";
import { useConversations } from "../contexts/ConversationsProvider";
export default function Conversations() {
const { conversations } = useConversations();
console.log( `conversations: ${conversations}`)//returns undefined
return (
<ListGroup variant="flush">
{conversations.map((conversation, index) => (//can't map because conversations is undefined
<ListGroup.Item key={index}>
{conversation.recipients
.map(r => r.name)
.join(", ")}
</ListGroup.Item>
))}
</ListGroup>
);
}
Here the localStorage setup for clarity:
import { useEffect, useState } from 'react'
const PREFIX = 'whatsapp-clone-'
export default function useLocalStorage(key, initialValue) {
const prefixedKey = PREFIX + key;
const [value, setValue] = useState(() => {
const jsonValue = localStorage.getItem(prefixedKey);
if (jsonValue != null) return JSON.parse(jsonValue);
if (typeof initialValue === "function") {
return initialValue();
} else {
return initialValue;
}
});
useEffect(() => {
localStorage.setItem(prefixedKey, JSON.stringify(value));
}, [prefixedKey, value]);
return [value, setValue];
}
I have been trying to solve this problem for hours and running out of ideas. I setup a test.js and imported the hooks in a Test.js function. Both return their objects respectively.
I have created the application using npx create-react-app and running it via yarn.
The problem lies within your ConversationsProvider.js:
const value = {
conversations: formattedConversations,
createConversation,
};
return (
<ConversationsContext.Provider value={{ value }}>
{children}
</ConversationsContext.Provider>
);
The following lines are all the same:
<ConversationsContext.Provider value={{ value }}>
<ConversationsContext.Provider value={{ value: value }}>
<ConversationsContext.Provider value={{ value: { conversations: formattedConversations, createConversation: createConversation } }}>
When you execute:
const { conversations } = useConversations();
conversations will be set to undefined because the object returned will only have the property value.
To fix the issue remove the nesting by changing the line:
<ConversationsContext.Provider value={{ value }}>
// to
<ConversationsContext.Provider value={value}>
PS. Here a tip to improve your debugging skills. You already did:
const { conversations } = useConversations();
console.log(conversations);
Which logged undefined. The next step would be to not destruct the object immediately and log the whole context value instead.
const contextValue = useConversations();
console.log(contextValue);
This would have shown that the object returned by useConversations is not what you expect it to be.