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>
);
}
Related
I want to write context with UseReducer hook but an error
error this:dispatch is not a function
what is problem?
please help me guys
almost is correct but not working it.
I want to see the appropriate action by clicking on the buttons,
one is increment, one is decrement and the other is reset.
CounterOne
import { UseCount, UseCountActions } from "./CounterProvider";
const CounterOne = () => {
const count = UseCount();
const dispatch = UseCountActions();
return (
<div>
<h2>count is:{count}</h2>
<button onClick={() => dispatch({ type: "add", value: 1 })}>
Addone{" "}
</button>
<button onClick={() => dispatch({ type: "decrement", value: 1 })}>
decrement
</button>
<button onClick={() => dispatch({ type: "reset" })}>reset</button>
</div>
);
};
export default CounterOne;
CounterProvider
import React, { useReducer, useState } from "react";
import { useContext } from "react";
const CounterContext = React.createContext();
const CounterContextDispather = React.createContext();
const initialState = 0;
const reducer = (state, action) => {
switch (action.type) {
case "add":
return state + action.value;
case "decrement":
return state - action.value;
case "reset":
return initialState;
default:
return state;
}
};
const CounterProvider = ({ children }) => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<CounterContext.Provider value={count}>
<CounterContextDispather.Provider value={dispatch}>
{children}
</CounterContextDispather.Provider>
</CounterContext.Provider>
);
};
export default CounterProvider;
export const UseCount = () => useContext(CounterContext);
export const UseCountActions = () => {
return CounterContextDispather;
};
export const UseCountActions = () => {
return useContext(CounterContextDispather);
};
There is a official example
I'm trying to figure out how to change global state from a componenet using useContext and useReducer dispatch method.
The component is simply should change the backgournd of the page on a click
Here is how I defined the context ThemeContext.js
import { createContext, useReducer } from "react";
import ThemeReducer from './ThemeReducer'
const INITIAL_STATE = {
isLightTheme: true,
light: {syntax: '#555', ui: '#ddd', bg: '#eee'},
dark: {syntax: '#ddd', ui: '#333', bg: '#555'},
}
export const ThemeContext = createContext(INITIAL_STATE);
const ThemeContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(ThemeReducer, INITIAL_STATE);
return (
<ThemeContext.Provider value={{
isLightTheme: state.isLightTheme,
light: state.light,
dark: state.dark,
dispatch,
}}>
{children}
</ThemeContext.Provider>
);
}
export default ThemeContextProvider;
The ThemeReducer.js is:
const ThemeReducer = (state, action) => {
switch (action.type) {
case "SET_DARK":
return {
isLightTheme: false,
};
case "SET_LIGHT":
return {
isLightTheme: true,
};
default:
return state;
}
};
export default ThemeReducer;
app.js:
function App() {
return (
<div className="App">
<ThemeContextProvider>
<Navbar />
<BookList />
<ThemeToggle />
</ThemeContextProvider>
</div>
);
}
export default App;
And the ThemeToggle.js compoenent
const ThemeToggle = () => {
return (
<button onClick={()=>dispatch({type: "SET_DARK"})}>Change theme</button>
);
}
export default ThemeToggle;
However I get this error:
src/components/ThemeToggle.jsx
Line 6:30: 'dispatch' is not defined
I don't understand why. Because dispatch is supposed to be in the context. I'm wondering what is wrong here and how can I fix it?
P.S BooKList compoenent.
import { useContext } from 'react'
import { ThemeContext } from '../context/ThemeContext';
const BookList = () => {
const {isLightTheme, light, dark} = useContext(ThemeContext)
const theme = isLightTheme ? light : dark;
return (
<div style={{background : theme.ui , color: theme.syntax}}>
<ul>
<li stryle={{background: theme.ui}} >The way of kings</li>
<li stryle={{background: theme.ui}} >The namoe fot the wind</li>
<li stryle={{background: theme.ui}} >The Final empire</li>
</ul>
</div>
);
}
It appears you are missing accessing the ThemeContext in ThemeToggle. Use the useContext hook to access the ThemeContext Context value and destructure the dispatch function.
const ThemeToggle = () => {
const { dispatch } = useContext(ThemeContext);
return (
<button onClick={() => dispatch({type: "SET_DARK"})}>
Change theme
</button>
);
}
export default ThemeToggle;
And for completeness' sake, add dispatch to the default context value.
const INITIAL_STATE = {
isLightTheme: true,
light: {syntax: '#555', ui: '#ddd', bg: '#eee'},
dark: {syntax: '#ddd', ui: '#333', bg: '#555'},
dispatch: () => {},
}
export const ThemeContext = createContext(INITIAL_STATE);
The ThemeReducer reducer function is stripping out state in the set light/dark cases. You need to preserve the existing state.
const ThemeReducer = (state, action) => {
switch (action.type) {
case "SET_DARK":
return {
...state,
isLightTheme: false,
};
case "SET_LIGHT":
return {
...state,
isLightTheme: true,
};
default:
return state;
}
};
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 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>
);
};
|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...